2 #include <comdef.h> // For _bstr_t
10 static char THIS_FILE
[] = __FILE__
;
15 // Change directory before opening file?
16 #define CD_SOURCE 0 // Cd to source path
17 #define CD_SOURCE_PARENT 1 // Cd to parent directory of source path
18 #define CD_NONE 2 // No cd
21 static BOOL g_bEnableVim
= TRUE
; // Vim enabled
22 static BOOL g_bDevStudioEditor
= FALSE
; // Open file in Dev Studio editor simultaneously
23 static BOOL g_bNewTabs
= FALSE
;
24 static int g_ChangeDir
= CD_NONE
; // CD after file open?
26 static void VimSetEnableState(BOOL bEnableState
);
27 static BOOL
VimOpenFile(BSTR
& FileName
, long LineNr
);
28 static DISPID
VimGetDispatchId(COleAutomationControl
& VimOle
, char* Method
);
29 static void VimErrDiag(COleAutomationControl
& VimOle
);
30 static void VimChangeDir(COleAutomationControl
& VimOle
, DISPID DispatchId
, BSTR
& FileName
);
31 static void DebugMsg(char* Msg
, char* Arg
= NULL
);
34 /////////////////////////////////////////////////////////////////////////////
37 CCommands::CCommands()
39 // m_pApplication == NULL; M$ Code generation bug!!!
40 m_pApplication
= NULL
;
41 m_pApplicationEventsObj
= NULL
;
42 m_pDebuggerEventsObj
= NULL
;
45 CCommands::~CCommands()
47 ASSERT(m_pApplication
!= NULL
);
50 m_pApplication
->Release();
51 m_pApplication
= NULL
;
55 void CCommands::SetApplicationObject(IApplication
* pApplication
)
57 // This function assumes pApplication has already been AddRef'd
58 // for us, which CDSAddIn did in it's QueryInterface call
59 // just before it called us.
60 m_pApplication
= pApplication
;
64 // Create Application event handlers
65 XApplicationEventsObj::CreateInstance(&m_pApplicationEventsObj
);
66 if (! m_pApplicationEventsObj
)
68 ReportInternalError("XApplicationEventsObj::CreateInstance");
71 m_pApplicationEventsObj
->AddRef();
72 m_pApplicationEventsObj
->Connect(m_pApplication
);
73 m_pApplicationEventsObj
->m_pCommands
= this;
76 // Create Debugger event handler
77 CComPtr
< IDispatch
> pDebugger
;
78 if (SUCCEEDED(m_pApplication
->get_Debugger(&pDebugger
))
81 XDebuggerEventsObj::CreateInstance(&m_pDebuggerEventsObj
);
82 m_pDebuggerEventsObj
->AddRef();
83 m_pDebuggerEventsObj
->Connect(pDebugger
);
84 m_pDebuggerEventsObj
->m_pCommands
= this;
88 // Get settings from registry HKEY_CURRENT_USER\Software\Vim\VisVim
89 HKEY hAppKey
= GetAppKey("Vim");
92 HKEY hSectionKey
= GetSectionKey(hAppKey
, "VisVim");
95 g_bEnableVim
= GetRegistryInt(hSectionKey
, "EnableVim",
97 g_bDevStudioEditor
= GetRegistryInt(hSectionKey
,
98 "DevStudioEditor", g_bDevStudioEditor
);
99 g_bNewTabs
= GetRegistryInt(hSectionKey
, "NewTabs",
101 g_ChangeDir
= GetRegistryInt(hSectionKey
, "ChangeDir",
103 RegCloseKey(hSectionKey
);
105 RegCloseKey(hAppKey
);
109 void CCommands::UnadviseFromEvents()
111 ASSERT(m_pApplicationEventsObj
!= NULL
);
112 if (m_pApplicationEventsObj
)
114 m_pApplicationEventsObj
->Disconnect(m_pApplication
);
115 m_pApplicationEventsObj
->Release();
116 m_pApplicationEventsObj
= NULL
;
120 if (m_pDebuggerEventsObj
)
122 // Since we were able to connect to the Debugger events, we
123 // should be able to access the Debugger object again to
124 // unadvise from its events (thus the VERIFY_OK below--see
126 CComPtr
< IDispatch
> pDebugger
;
127 VERIFY_OK(m_pApplication
->get_Debugger(&pDebugger
));
128 ASSERT(pDebugger
!= NULL
);
129 m_pDebuggerEventsObj
->Disconnect(pDebugger
);
130 m_pDebuggerEventsObj
->Release();
131 m_pDebuggerEventsObj
= NULL
;
137 /////////////////////////////////////////////////////////////////////////////
140 // Application events
142 HRESULT
CCommands::XApplicationEvents::BeforeBuildStart()
144 AFX_MANAGE_STATE(AfxGetStaticModuleState());
148 HRESULT
CCommands::XApplicationEvents::BuildFinish(long nNumErrors
, long nNumWarnings
)
150 AFX_MANAGE_STATE(AfxGetStaticModuleState());
154 HRESULT
CCommands::XApplicationEvents::BeforeApplicationShutDown()
156 AFX_MANAGE_STATE(AfxGetStaticModuleState());
160 // The open document event handle is the place where the real interface work
162 // Vim gets called from here.
164 HRESULT
CCommands::XApplicationEvents::DocumentOpen(IDispatch
* theDocument
)
166 AFX_MANAGE_STATE(AfxGetStaticModuleState());
169 // Vim not enabled or empty command line entered
172 // First get the current file name and line number
174 // Get the document object
175 CComQIPtr
< ITextDocument
, &IID_ITextDocument
> pDoc(theDocument
);
182 // Get the document name
183 if (FAILED(pDoc
->get_FullName(&FileName
)))
188 // Get a selection object dispatch pointer
189 if (SUCCEEDED(pDoc
->get_Selection(&pDispSel
)))
191 // Get the selection object
192 CComQIPtr
< ITextSelection
, &IID_ITextSelection
> pSel(pDispSel
);
195 // Get the selection line number
196 pSel
->get_CurrentLine(&LineNr
);
201 // Open the file in Vim and position to the current line
202 if (VimOpenFile(FileName
, LineNr
))
204 if (! g_bDevStudioEditor
)
206 // Close the document in developer studio
207 CComVariant vSaveChanges
= dsSaveChangesPrompt
;
210 pDoc
->Close(vSaveChanges
, &Saved
);
215 SysFreeString(FileName
);
219 HRESULT
CCommands::XApplicationEvents::BeforeDocumentClose(IDispatch
* theDocument
)
221 AFX_MANAGE_STATE(AfxGetStaticModuleState());
225 HRESULT
CCommands::XApplicationEvents::DocumentSave(IDispatch
* theDocument
)
227 AFX_MANAGE_STATE(AfxGetStaticModuleState());
231 HRESULT
CCommands::XApplicationEvents::NewDocument(IDispatch
* theDocument
)
233 AFX_MANAGE_STATE(AfxGetStaticModuleState());
236 // Vim not enabled or empty command line entered
239 // First get the current file name and line number
241 CComQIPtr
< ITextDocument
, &IID_ITextDocument
> pDoc(theDocument
);
248 hr
= pDoc
->get_FullName(&FileName
);
252 // Open the file in Vim and position to the current line
253 if (VimOpenFile(FileName
, 0))
255 if (! g_bDevStudioEditor
)
257 // Close the document in developer studio
258 CComVariant vSaveChanges
= dsSaveChangesPrompt
;
261 pDoc
->Close(vSaveChanges
, &Saved
);
265 SysFreeString(FileName
);
269 HRESULT
CCommands::XApplicationEvents::WindowActivate(IDispatch
* theWindow
)
271 AFX_MANAGE_STATE(AfxGetStaticModuleState());
275 HRESULT
CCommands::XApplicationEvents::WindowDeactivate(IDispatch
* theWindow
)
277 AFX_MANAGE_STATE(AfxGetStaticModuleState());
281 HRESULT
CCommands::XApplicationEvents::WorkspaceOpen()
283 AFX_MANAGE_STATE(AfxGetStaticModuleState());
287 HRESULT
CCommands::XApplicationEvents::WorkspaceClose()
289 AFX_MANAGE_STATE(AfxGetStaticModuleState());
293 HRESULT
CCommands::XApplicationEvents::NewWorkspace()
295 AFX_MANAGE_STATE(AfxGetStaticModuleState());
301 HRESULT
CCommands::XDebuggerEvents::BreakpointHit(IDispatch
* pBreakpoint
)
303 AFX_MANAGE_STATE(AfxGetStaticModuleState());
308 /////////////////////////////////////////////////////////////////////////////
311 class CMainDialog
: public CDialog
314 CMainDialog(CWnd
* pParent
= NULL
); // Standard constructor
316 //{{AFX_DATA(CMainDialog)
317 enum { IDD
= IDD_ADDINMAIN
};
319 BOOL m_bDevStudioEditor
;
323 //{{AFX_VIRTUAL(CMainDialog)
325 virtual void DoDataExchange(CDataExchange
* pDX
); // DDX/DDV support
329 //{{AFX_MSG(CMainDialog)
330 afx_msg
void OnEnable();
331 afx_msg
void OnDisable();
333 DECLARE_MESSAGE_MAP()
336 CMainDialog::CMainDialog(CWnd
* pParent
/* =NULL */ )
337 : CDialog(CMainDialog::IDD
, pParent
)
339 //{{AFX_DATA_INIT(CMainDialog)
341 m_bDevStudioEditor
= FALSE
;
346 void CMainDialog::DoDataExchange(CDataExchange
* pDX
)
348 CDialog::DoDataExchange(pDX
);
349 //{{AFX_DATA_MAP(CMainDialog)
350 DDX_Radio(pDX
, IDC_CD_SOURCE_PATH
, m_ChangeDir
);
351 DDX_Check(pDX
, IDC_DEVSTUDIO_EDITOR
, m_bDevStudioEditor
);
352 DDX_Check(pDX
, IDC_NEW_TABS
, m_bNewTabs
);
356 BEGIN_MESSAGE_MAP(CMainDialog
, CDialog
)
357 //{{AFX_MSG_MAP(CMainDialog)
362 /////////////////////////////////////////////////////////////////////////////
365 STDMETHODIMP
CCommands::VisVimDialog()
367 AFX_MANAGE_STATE(AfxGetStaticModuleState());
369 // Use m_pApplication to access the Developer Studio Application
371 // and VERIFY_OK to see error strings in DEBUG builds of your add-in
374 VERIFY_OK(m_pApplication
->EnableModeless(VARIANT_FALSE
));
378 Dlg
.m_bDevStudioEditor
= g_bDevStudioEditor
;
379 Dlg
.m_bNewTabs
= g_bNewTabs
;
380 Dlg
.m_ChangeDir
= g_ChangeDir
;
381 if (Dlg
.DoModal() == IDOK
)
383 g_bDevStudioEditor
= Dlg
.m_bDevStudioEditor
;
384 g_bNewTabs
= Dlg
.m_bNewTabs
;
385 g_ChangeDir
= Dlg
.m_ChangeDir
;
387 // Save settings to registry HKEY_CURRENT_USER\Software\Vim\VisVim
388 HKEY hAppKey
= GetAppKey("Vim");
391 HKEY hSectionKey
= GetSectionKey(hAppKey
, "VisVim");
394 WriteRegistryInt(hSectionKey
, "DevStudioEditor",
396 WriteRegistryInt(hSectionKey
, "NewTabs",
398 WriteRegistryInt(hSectionKey
, "ChangeDir", g_ChangeDir
);
399 RegCloseKey(hSectionKey
);
401 RegCloseKey(hAppKey
);
405 VERIFY_OK(m_pApplication
->EnableModeless(VARIANT_TRUE
));
409 STDMETHODIMP
CCommands::VisVimEnable()
411 AFX_MANAGE_STATE(AfxGetStaticModuleState());
412 VimSetEnableState(true);
416 STDMETHODIMP
CCommands::VisVimDisable()
418 AFX_MANAGE_STATE(AfxGetStaticModuleState());
419 VimSetEnableState(false);
423 STDMETHODIMP
CCommands::VisVimToggle()
425 AFX_MANAGE_STATE(AfxGetStaticModuleState());
426 VimSetEnableState(! g_bEnableVim
);
430 STDMETHODIMP
CCommands::VisVimLoad()
432 AFX_MANAGE_STATE(AfxGetStaticModuleState());
434 // Use m_pApplication to access the Developer Studio Application object,
435 // and VERIFY_OK to see error strings in DEBUG builds of your add-in
439 // Define dispatch pointers for document and selection objects
440 CComPtr
< IDispatch
> pDispDoc
, pDispSel
;
442 // Get a document object dispatch pointer
443 VERIFY_OK(m_pApplication
->get_ActiveDocument(&pDispDoc
));
450 // Get the document object
451 CComQIPtr
< ITextDocument
, &IID_ITextDocument
> pDoc(pDispDoc
);
456 // Get the document name
457 if (FAILED(pDoc
->get_FullName(&FileName
)))
460 // Get a selection object dispatch pointer
461 if (SUCCEEDED(pDoc
->get_Selection(&pDispSel
)))
463 // Get the selection object
464 CComQIPtr
< ITextSelection
, &IID_ITextSelection
> pSel(pDispSel
);
467 // Get the selection line number
468 pSel
->get_CurrentLine(&LineNr
);
471 // Open the file in Vim
472 VimOpenFile(FileName
, LineNr
);
474 SysFreeString(FileName
);
480 // Here we do the actual processing and communication with Vim
483 // Set the enable state and save to registry
485 static void VimSetEnableState(BOOL bEnableState
)
487 g_bEnableVim
= bEnableState
;
488 HKEY hAppKey
= GetAppKey("Vim");
491 HKEY hSectionKey
= GetSectionKey(hAppKey
, "VisVim");
493 WriteRegistryInt(hSectionKey
, "EnableVim", g_bEnableVim
);
494 RegCloseKey(hAppKey
);
498 // Open the file 'FileName' in Vim and goto line 'LineNr'
499 // 'FileName' is expected to contain an absolute DOS path including the drive
501 // 'LineNr' must contain a valid line number or 0, e. g. for a new file
503 static BOOL
VimOpenFile(BSTR
& FileName
, long LineNr
)
506 // OLE automation object for com. with Vim
507 // When the object goes out of scope, it's destructor destroys the OLE
509 // This is important to avoid blocking the object
510 // (in this memory corruption would be likely when terminating Vim
511 // while still running DevStudio).
512 // So keep this object local!
513 COleAutomationControl VimOle
;
515 // :cd D:/Src2/VisVim/
517 // Get a dispatch id for the SendKeys method of Vim;
518 // enables connection to Vim if necessary
520 DispatchId
= VimGetDispatchId(VimOle
, "SendKeys");
522 // OLE error, can't obtain dispatch id
525 OLECHAR Buf
[MAX_OLE_STR
];
526 char FileNameTmp
[MAX_OLE_STR
];
527 char VimCmd
[MAX_OLE_STR
];
530 // Prepend CTRL-\ CTRL-N to exit insert mode
536 // Update the current file in Vim if it has been modified.
537 // Disabled, because it could write the file when you don't want to.
538 sprintf(VimCmd
+ 2, ":up\n");
540 if (! VimOle
.Method(DispatchId
, "s", TO_OLE_STR_BUF(VimCmd
, Buf
)))
543 // Change Vim working directory to where the file is if desired
544 if (g_ChangeDir
!= CD_NONE
)
545 VimChangeDir(VimOle
, DispatchId
, FileName
);
547 // Make Vim open the file.
548 // In the filename convert all \ to /, put a \ before a space.
551 sprintf(VimCmd
, ":tab drop ");
556 sprintf(VimCmd
, ":drop ");
559 sprintf(FileNameTmp
, "%S", (char *)FileName
);
560 for (p
= FileNameTmp
; *p
!= '\0' && s
< FileNameTmp
+ MAX_OLE_STR
- 4;
573 if (! VimOle
.Method(DispatchId
, "s", TO_OLE_STR_BUF(VimCmd
, Buf
)))
579 sprintf(VimCmd
, ":%d\n", LineNr
);
580 if (! VimOle
.Method(DispatchId
, "s", TO_OLE_STR_BUF(VimCmd
, Buf
)))
584 // Make Vim come to the foreground
585 if (! VimOle
.Method("SetForeground"))
592 // There was an OLE error
593 // Check if it's the "unknown class string" error
598 // Return the dispatch id for the Vim method 'Method'
599 // Create the Vim OLE object if necessary
600 // Returns a valid dispatch id or null on error
602 static DISPID
VimGetDispatchId(COleAutomationControl
& VimOle
, char* Method
)
604 // Initialize Vim OLE connection if not already done
605 if (! VimOle
.IsCreated())
607 if (! VimOle
.CreateObject("Vim.Application"))
611 // Get the dispatch id for the SendKeys method.
612 // By doing this, we are checking if Vim is still there...
613 DISPID DispatchId
= VimOle
.GetDispatchId("SendKeys");
616 // We can't get a dispatch id.
617 // This means that probably Vim has been terminated.
618 // Don't issue an error message here, instead
619 // destroy the OLE object and try to connect once more
621 // In fact, this should never happen, because the OLE aut. object
622 // should not be kept long enough to allow the user to terminate Vim
623 // to avoid memory corruption (why the heck is there no system garbage
624 // collection for those damned OLE memory chunks???).
625 VimOle
.DeleteObject();
626 if (! VimOle
.CreateObject("Vim.Application"))
627 // If this create fails, it's time for an error msg
630 if (! (DispatchId
= VimOle
.GetDispatchId("SendKeys")))
631 // There is something wrong...
638 // Output an error message for an OLE error
639 // Check on the classstring error, which probably means Vim wasn't registered.
641 static void VimErrDiag(COleAutomationControl
& VimOle
)
643 SCODE sc
= GetScode(VimOle
.GetResult());
644 if (sc
== CO_E_CLASSSTRING
)
647 sprintf(Buf
, "There is no registered OLE automation server named "
648 "\"Vim.Application\".\n"
649 "Use the OLE-enabled version of Vim with VisVim and "
650 "make sure to register Vim by running \"vim -register\".");
651 MessageBox(NULL
, Buf
, "OLE Error", MB_OK
);
657 // Change directory to the directory the file 'FileName' is in or it's parent
658 // directory according to the setting of the global 'g_ChangeDir':
659 // 'FileName' is expected to contain an absolute DOS path including the drive
665 static void VimChangeDir(COleAutomationControl
& VimOle
, DISPID DispatchId
, BSTR
& FileName
)
669 // Get the path name of the file ("dir/")
670 CString StrFileName
= FileName
;
671 char Drive
[_MAX_DRIVE
];
673 char DirUnix
[_MAX_DIR
* 2];
676 _splitpath(StrFileName
, Drive
, Dir
, NULL
, NULL
);
678 // Convert to Unix path name format, escape spaces.
680 for (s
= Dir
; *s
; ++s
)
692 // Construct the cd command; append /.. if cd to parent
693 // directory and not in root directory
694 OLECHAR Buf
[MAX_OLE_STR
];
695 char VimCmd
[MAX_OLE_STR
];
697 sprintf(VimCmd
, ":cd %s%s%s\n", Drive
, DirUnix
,
698 g_ChangeDir
== CD_SOURCE_PARENT
&& DirUnix
[1] ? ".." : "");
699 VimOle
.Method(DispatchId
, "s", TO_OLE_STR_BUF(VimCmd
, Buf
));
703 // Print out a debug message
705 static void DebugMsg(char* Msg
, char* Arg
)
708 sprintf(Buf
, Msg
, Arg
);