Merge branch 'vim'
[MacVim.git] / src / VisVim / Commands.cpp
bloba346dea702954c1e6212aa82e9730dd1df8b937d
1 #include "stdafx.h"
2 #include <comdef.h> // For _bstr_t
3 #include "VisVim.h"
4 #include "Commands.h"
5 #include "OleAut.h"
7 #ifdef _DEBUG
8 #define new DEBUG_NEW
9 #undef THIS_FILE
10 static char THIS_FILE[] = __FILE__;
12 #endif
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 /////////////////////////////////////////////////////////////////////////////
35 // CCommands
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);
48 if (m_pApplication)
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;
61 if (! m_pApplication)
62 return;
64 // Create Application event handlers
65 XApplicationEventsObj::CreateInstance(&m_pApplicationEventsObj);
66 if (! m_pApplicationEventsObj)
68 ReportInternalError("XApplicationEventsObj::CreateInstance");
69 return;
71 m_pApplicationEventsObj->AddRef();
72 m_pApplicationEventsObj->Connect(m_pApplication);
73 m_pApplicationEventsObj->m_pCommands = this;
75 #ifdef NEVER
76 // Create Debugger event handler
77 CComPtr < IDispatch > pDebugger;
78 if (SUCCEEDED(m_pApplication->get_Debugger(&pDebugger))
79 && pDebugger != NULL)
81 XDebuggerEventsObj::CreateInstance(&m_pDebuggerEventsObj);
82 m_pDebuggerEventsObj->AddRef();
83 m_pDebuggerEventsObj->Connect(pDebugger);
84 m_pDebuggerEventsObj->m_pCommands = this;
86 #endif
88 // Get settings from registry HKEY_CURRENT_USER\Software\Vim\VisVim
89 HKEY hAppKey = GetAppKey("Vim");
90 if (hAppKey)
92 HKEY hSectionKey = GetSectionKey(hAppKey, "VisVim");
93 if (hSectionKey)
95 g_bEnableVim = GetRegistryInt(hSectionKey, "EnableVim",
96 g_bEnableVim);
97 g_bDevStudioEditor = GetRegistryInt(hSectionKey,
98 "DevStudioEditor", g_bDevStudioEditor);
99 g_bNewTabs = GetRegistryInt(hSectionKey, "NewTabs",
100 g_bNewTabs);
101 g_ChangeDir = GetRegistryInt(hSectionKey, "ChangeDir",
102 g_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;
119 #ifdef NEVER
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
125 // stdafx.h).
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;
133 #endif
137 /////////////////////////////////////////////////////////////////////////////
138 // Event handlers
140 // Application events
142 HRESULT CCommands::XApplicationEvents::BeforeBuildStart()
144 AFX_MANAGE_STATE(AfxGetStaticModuleState());
145 return S_OK;
148 HRESULT CCommands::XApplicationEvents::BuildFinish(long nNumErrors, long nNumWarnings)
150 AFX_MANAGE_STATE(AfxGetStaticModuleState());
151 return S_OK;
154 HRESULT CCommands::XApplicationEvents::BeforeApplicationShutDown()
156 AFX_MANAGE_STATE(AfxGetStaticModuleState());
157 return S_OK;
160 // The open document event handle is the place where the real interface work
161 // is done.
162 // Vim gets called from here.
164 HRESULT CCommands::XApplicationEvents::DocumentOpen(IDispatch * theDocument)
166 AFX_MANAGE_STATE(AfxGetStaticModuleState());
168 if (! g_bEnableVim)
169 // Vim not enabled or empty command line entered
170 return S_OK;
172 // First get the current file name and line number
174 // Get the document object
175 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc(theDocument);
176 if (! pDoc)
177 return S_OK;
179 BSTR FileName;
180 long LineNr = -1;
182 // Get the document name
183 if (FAILED(pDoc->get_FullName(&FileName)))
184 return S_OK;
186 LPDISPATCH pDispSel;
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);
194 if (pSel)
195 // Get the selection line number
196 pSel->get_CurrentLine(&LineNr);
198 pDispSel->Release();
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;
208 DsSaveStatus Saved;
210 pDoc->Close(vSaveChanges, &Saved);
214 // We're done here
215 SysFreeString(FileName);
216 return S_OK;
219 HRESULT CCommands::XApplicationEvents::BeforeDocumentClose(IDispatch * theDocument)
221 AFX_MANAGE_STATE(AfxGetStaticModuleState());
222 return S_OK;
225 HRESULT CCommands::XApplicationEvents::DocumentSave(IDispatch * theDocument)
227 AFX_MANAGE_STATE(AfxGetStaticModuleState());
228 return S_OK;
231 HRESULT CCommands::XApplicationEvents::NewDocument(IDispatch * theDocument)
233 AFX_MANAGE_STATE(AfxGetStaticModuleState());
235 if (! g_bEnableVim)
236 // Vim not enabled or empty command line entered
237 return S_OK;
239 // First get the current file name and line number
241 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc(theDocument);
242 if (! pDoc)
243 return S_OK;
245 BSTR FileName;
246 HRESULT hr;
248 hr = pDoc->get_FullName(&FileName);
249 if (FAILED(hr))
250 return S_OK;
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;
259 DsSaveStatus Saved;
261 pDoc->Close(vSaveChanges, &Saved);
265 SysFreeString(FileName);
266 return S_OK;
269 HRESULT CCommands::XApplicationEvents::WindowActivate(IDispatch * theWindow)
271 AFX_MANAGE_STATE(AfxGetStaticModuleState());
272 return S_OK;
275 HRESULT CCommands::XApplicationEvents::WindowDeactivate(IDispatch * theWindow)
277 AFX_MANAGE_STATE(AfxGetStaticModuleState());
278 return S_OK;
281 HRESULT CCommands::XApplicationEvents::WorkspaceOpen()
283 AFX_MANAGE_STATE(AfxGetStaticModuleState());
284 return S_OK;
287 HRESULT CCommands::XApplicationEvents::WorkspaceClose()
289 AFX_MANAGE_STATE(AfxGetStaticModuleState());
290 return S_OK;
293 HRESULT CCommands::XApplicationEvents::NewWorkspace()
295 AFX_MANAGE_STATE(AfxGetStaticModuleState());
296 return S_OK;
299 // Debugger event
301 HRESULT CCommands::XDebuggerEvents::BreakpointHit(IDispatch * pBreakpoint)
303 AFX_MANAGE_STATE(AfxGetStaticModuleState());
304 return S_OK;
308 /////////////////////////////////////////////////////////////////////////////
309 // VisVim dialog
311 class CMainDialog : public CDialog
313 public:
314 CMainDialog(CWnd * pParent = NULL); // Standard constructor
316 //{{AFX_DATA(CMainDialog)
317 enum { IDD = IDD_ADDINMAIN };
318 int m_ChangeDir;
319 BOOL m_bDevStudioEditor;
320 BOOL m_bNewTabs;
321 //}}AFX_DATA
323 //{{AFX_VIRTUAL(CMainDialog)
324 protected:
325 virtual void DoDataExchange(CDataExchange * pDX); // DDX/DDV support
326 //}}AFX_VIRTUAL
328 protected:
329 //{{AFX_MSG(CMainDialog)
330 afx_msg void OnEnable();
331 afx_msg void OnDisable();
332 //}}AFX_MSG
333 DECLARE_MESSAGE_MAP()
336 CMainDialog::CMainDialog(CWnd * pParent /* =NULL */ )
337 : CDialog(CMainDialog::IDD, pParent)
339 //{{AFX_DATA_INIT(CMainDialog)
340 m_ChangeDir = -1;
341 m_bDevStudioEditor = FALSE;
342 m_bNewTabs = FALSE;
343 //}}AFX_DATA_INIT
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);
353 //}}AFX_DATA_MAP
356 BEGIN_MESSAGE_MAP(CMainDialog, CDialog)
357 //{{AFX_MSG_MAP(CMainDialog)
358 //}}AFX_MSG_MAP
359 END_MESSAGE_MAP()
362 /////////////////////////////////////////////////////////////////////////////
363 // CCommands methods
365 STDMETHODIMP CCommands::VisVimDialog()
367 AFX_MANAGE_STATE(AfxGetStaticModuleState());
369 // Use m_pApplication to access the Developer Studio Application
370 // object,
371 // and VERIFY_OK to see error strings in DEBUG builds of your add-in
372 // (see stdafx.h)
374 VERIFY_OK(m_pApplication->EnableModeless(VARIANT_FALSE));
376 CMainDialog Dlg;
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");
389 if (hAppKey)
391 HKEY hSectionKey = GetSectionKey(hAppKey, "VisVim");
392 if (hSectionKey)
394 WriteRegistryInt(hSectionKey, "DevStudioEditor",
395 g_bDevStudioEditor);
396 WriteRegistryInt(hSectionKey, "NewTabs",
397 g_bNewTabs);
398 WriteRegistryInt(hSectionKey, "ChangeDir", g_ChangeDir);
399 RegCloseKey(hSectionKey);
401 RegCloseKey(hAppKey);
405 VERIFY_OK(m_pApplication->EnableModeless(VARIANT_TRUE));
406 return S_OK;
409 STDMETHODIMP CCommands::VisVimEnable()
411 AFX_MANAGE_STATE(AfxGetStaticModuleState());
412 VimSetEnableState(true);
413 return S_OK;
416 STDMETHODIMP CCommands::VisVimDisable()
418 AFX_MANAGE_STATE(AfxGetStaticModuleState());
419 VimSetEnableState(false);
420 return S_OK;
423 STDMETHODIMP CCommands::VisVimToggle()
425 AFX_MANAGE_STATE(AfxGetStaticModuleState());
426 VimSetEnableState(! g_bEnableVim);
427 return S_OK;
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
436 // (see stdafx.h)
438 CComBSTR bStr;
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));
444 if (! pDispDoc)
445 return S_OK;
447 BSTR FileName;
448 long LineNr = -1;
450 // Get the document object
451 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc(pDispDoc);
453 if (! pDoc)
454 return S_OK;
456 // Get the document name
457 if (FAILED(pDoc->get_FullName(&FileName)))
458 return S_OK;
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);
466 if (pSel)
467 // Get the selection line number
468 pSel->get_CurrentLine(&LineNr);
471 // Open the file in Vim
472 VimOpenFile(FileName, LineNr);
474 SysFreeString(FileName);
475 return S_OK;
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");
489 if (hAppKey)
491 HKEY hSectionKey = GetSectionKey(hAppKey, "VisVim");
492 if (hSectionKey)
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
500 // letter.
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
508 // connection;
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
519 DISPID DispatchId;
520 DispatchId = VimGetDispatchId(VimOle, "SendKeys");
521 if (! DispatchId)
522 // OLE error, can't obtain dispatch id
523 goto OleError;
525 OLECHAR Buf[MAX_OLE_STR];
526 char FileNameTmp[MAX_OLE_STR];
527 char VimCmd[MAX_OLE_STR];
528 char *s, *p;
530 // Prepend CTRL-\ CTRL-N to exit insert mode
531 VimCmd[0] = 0x1c;
532 VimCmd[1] = 0x0e;
533 VimCmd[2] = 0;
535 #ifdef SINGLE_WINDOW
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");
539 #endif
540 if (! VimOle.Method(DispatchId, "s", TO_OLE_STR_BUF(VimCmd, Buf)))
541 goto OleError;
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.
549 if (g_bNewTabs)
551 sprintf(VimCmd, ":tab drop ");
552 s = VimCmd + 11;
554 else
556 sprintf(VimCmd, ":drop ");
557 s = VimCmd + 6;
559 sprintf(FileNameTmp, "%S", (char *)FileName);
560 for (p = FileNameTmp; *p != '\0' && s < FileNameTmp + MAX_OLE_STR - 4;
561 ++p)
562 if (*p == '\\')
563 *s++ = '/';
564 else
566 if (*p == ' ')
567 *s++ = '\\';
568 *s++ = *p;
570 *s++ = '\n';
571 *s = '\0';
573 if (! VimOle.Method(DispatchId, "s", TO_OLE_STR_BUF(VimCmd, Buf)))
574 goto OleError;
576 if (LineNr > 0)
578 // Goto line
579 sprintf(VimCmd, ":%d\n", LineNr);
580 if (! VimOle.Method(DispatchId, "s", TO_OLE_STR_BUF(VimCmd, Buf)))
581 goto OleError;
584 // Make Vim come to the foreground
585 if (! VimOle.Method("SetForeground"))
586 VimOle.ErrDiag();
588 // We're done
589 return true;
591 OleError:
592 // There was an OLE error
593 // Check if it's the "unknown class string" error
594 VimErrDiag(VimOle);
595 return false;
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"))
608 return NULL;
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");
614 if (! DispatchId)
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
628 return NULL;
630 if (! (DispatchId = VimOle.GetDispatchId("SendKeys")))
631 // There is something wrong...
632 return NULL;
635 return DispatchId;
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)
646 char Buf[256];
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);
653 else
654 VimOle.ErrDiag();
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
660 // letter.
661 // CD_NONE
662 // CD_SOURCE_PATH
663 // CD_SOURCE_PARENT
665 static void VimChangeDir(COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName)
667 // Do a :cd first
669 // Get the path name of the file ("dir/")
670 CString StrFileName = FileName;
671 char Drive[_MAX_DRIVE];
672 char Dir[_MAX_DIR];
673 char DirUnix[_MAX_DIR * 2];
674 char *s, *t;
676 _splitpath(StrFileName, Drive, Dir, NULL, NULL);
678 // Convert to Unix path name format, escape spaces.
679 t = DirUnix;
680 for (s = Dir; *s; ++s)
681 if (*s == '\\')
682 *t++ = '/';
683 else
685 if (*s == ' ')
686 *t++ = '\\';
687 *t++ = *s;
689 *t = '\0';
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));
702 #ifdef _DEBUG
703 // Print out a debug message
705 static void DebugMsg(char* Msg, char* Arg)
707 char Buf[400];
708 sprintf(Buf, Msg, Arg);
709 AfxMessageBox(Buf);
711 #endif