1 #include "stdafx.h" 2 #include <comdef.h> // For _bstr_t 3 #include "VisVim.h" 4 #include "Commands.h" 5 #include "OleAut.h" 6 7 #ifdef _DEBUG 8 #define new DEBUG_NEW 9 #undef THIS_FILE 10 static char THIS_FILE[] = __FILE__; 11 12 #endif 13 14 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 19 20 21 static BOOL g_bEnableVim = TRUE; // Vim enabled 22 static BOOL g_bDevStudioEditor = FALSE; // Open file in Dev Studio editor simultaneously 23 static int g_ChangeDir = CD_NONE; // CD after file open? 24 25 static void VimSetEnableState (BOOL bEnableState); 26 static BOOL VimOpenFile (BSTR& FileName, long LineNr); 27 static DISPID VimGetDispatchId (COleAutomationControl& VimOle, char* Method); 28 static void VimErrDiag (COleAutomationControl& VimOle); 29 static void VimChangeDir (COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName); 30 static void DebugMsg (char* Msg, char* Arg = NULL); 31 32 33 ///////////////////////////////////////////////////////////////////////////// 34 // CCommands 35 36 CCommands::CCommands () 37 { 38 // m_pApplication == NULL; M$ Code generation bug!!! 39 m_pApplication = NULL; 40 m_pApplicationEventsObj = NULL; 41 m_pDebuggerEventsObj = NULL; 42 } 43 44 CCommands::~CCommands () 45 { 46 ASSERT (m_pApplication != NULL); 47 if (m_pApplication) 48 { 49 m_pApplication->Release (); 50 m_pApplication = NULL; 51 } 52 } 53 54 void CCommands::SetApplicationObject (IApplication * pApplication) 55 { 56 // This function assumes pApplication has already been AddRef'd 57 // for us, which CDSAddIn did in it's QueryInterface call 58 // just before it called us. 59 m_pApplication = pApplication; 60 if (! m_pApplication) 61 return; 62 63 // Create Application event handlers 64 XApplicationEventsObj::CreateInstance (&m_pApplicationEventsObj); 65 if (! m_pApplicationEventsObj) 66 { 67 ReportInternalError ("XApplicationEventsObj::CreateInstance"); 68 return; 69 } 70 m_pApplicationEventsObj->AddRef (); 71 m_pApplicationEventsObj->Connect (m_pApplication); 72 m_pApplicationEventsObj->m_pCommands = this; 73 74 #ifdef NEVER 75 // Create Debugger event handler 76 CComPtr < IDispatch > pDebugger; 77 if (SUCCEEDED (m_pApplication->get_Debugger (&pDebugger)) 78 && pDebugger != NULL) 79 { 80 XDebuggerEventsObj::CreateInstance (&m_pDebuggerEventsObj); 81 m_pDebuggerEventsObj->AddRef (); 82 m_pDebuggerEventsObj->Connect (pDebugger); 83 m_pDebuggerEventsObj->m_pCommands = this; 84 } 85 #endif 86 87 // Get settings from registry HKEY_CURRENT_USER\Software\Vim\VisVim 88 HKEY hAppKey = GetAppKey ("Vim"); 89 if (hAppKey) 90 { 91 HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); 92 if (hSectionKey) 93 { 94 g_bEnableVim = GetRegistryInt (hSectionKey, "EnableVim", 95 g_bEnableVim); 96 g_bDevStudioEditor = GetRegistryInt(hSectionKey,"DevStudioEditor", 97 g_bDevStudioEditor); 98 g_ChangeDir = GetRegistryInt (hSectionKey, "ChangeDir", 99 g_ChangeDir); 100 RegCloseKey (hSectionKey); 101 } 102 RegCloseKey (hAppKey); 103 } 104 } 105 106 void CCommands::UnadviseFromEvents () 107 { 108 ASSERT (m_pApplicationEventsObj != NULL); 109 if (m_pApplicationEventsObj) 110 { 111 m_pApplicationEventsObj->Disconnect (m_pApplication); 112 m_pApplicationEventsObj->Release (); 113 m_pApplicationEventsObj = NULL; 114 } 115 116 #ifdef NEVER 117 if (m_pDebuggerEventsObj) 118 { 119 // Since we were able to connect to the Debugger events, we 120 // should be able to access the Debugger object again to 121 // unadvise from its events (thus the VERIFY_OK below--see 122 // stdafx.h). 123 CComPtr < IDispatch > pDebugger; 124 VERIFY_OK (m_pApplication->get_Debugger (&pDebugger)); 125 ASSERT (pDebugger != NULL); 126 m_pDebuggerEventsObj->Disconnect (pDebugger); 127 m_pDebuggerEventsObj->Release (); 128 m_pDebuggerEventsObj = NULL; 129 } 130 #endif 131 } 132 133 134 ///////////////////////////////////////////////////////////////////////////// 135 // Event handlers 136 137 // Application events 138 139 HRESULT CCommands::XApplicationEvents::BeforeBuildStart () 140 { 141 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 142 return S_OK; 143 } 144 145 HRESULT CCommands::XApplicationEvents::BuildFinish (long nNumErrors, long nNumWarnings) 146 { 147 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 148 return S_OK; 149 } 150 151 HRESULT CCommands::XApplicationEvents::BeforeApplicationShutDown () 152 { 153 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 154 return S_OK; 155 } 156 157 // The open document event handle is the place where the real interface work 158 // is done. 159 // Vim gets called from here. 160 // 161 HRESULT CCommands::XApplicationEvents::DocumentOpen (IDispatch * theDocument) 162 { 163 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 164 165 if (! g_bEnableVim) 166 // Vim not enabled or empty command line entered 167 return S_OK; 168 169 // First get the current file name and line number 170 171 // Get the document object 172 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (theDocument); 173 if (! pDoc) 174 return S_OK; 175 176 BSTR FileName; 177 long LineNr = -1; 178 179 // Get the document name 180 if (FAILED (pDoc->get_FullName (&FileName))) 181 return S_OK; 182 183 LPDISPATCH pDispSel; 184 185 // Get a selection object dispatch pointer 186 if (SUCCEEDED (pDoc->get_Selection (&pDispSel))) 187 { 188 // Get the selection object 189 CComQIPtr < ITextSelection, &IID_ITextSelection > pSel (pDispSel); 190 191 if (pSel) 192 // Get the selection line number 193 pSel->get_CurrentLine (&LineNr); 194 195 pDispSel->Release (); 196 } 197 198 // Open the file in Vim and position to the current line 199 if (VimOpenFile (FileName, LineNr)) 200 { 201 if (! g_bDevStudioEditor) 202 { 203 // Close the document in developer studio 204 CComVariant vSaveChanges = dsSaveChangesPrompt; 205 DsSaveStatus Saved; 206 207 pDoc->Close (vSaveChanges, &Saved); 208 } 209 } 210 211 // We're done here 212 SysFreeString (FileName); 213 return S_OK; 214 } 215 216 HRESULT CCommands::XApplicationEvents::BeforeDocumentClose (IDispatch * theDocument) 217 { 218 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 219 return S_OK; 220 } 221 222 HRESULT CCommands::XApplicationEvents::DocumentSave (IDispatch * theDocument) 223 { 224 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 225 return S_OK; 226 } 227 228 HRESULT CCommands::XApplicationEvents::NewDocument (IDispatch * theDocument) 229 { 230 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 231 232 if (! g_bEnableVim) 233 // Vim not enabled or empty command line entered 234 return S_OK; 235 236 // First get the current file name and line number 237 238 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (theDocument); 239 if (! pDoc) 240 return S_OK; 241 242 BSTR FileName; 243 HRESULT hr; 244 245 hr = pDoc->get_FullName (&FileName); 246 if (FAILED (hr)) 247 return S_OK; 248 249 // Open the file in Vim and position to the current line 250 if (VimOpenFile (FileName, 0)) 251 { 252 if (! g_bDevStudioEditor) 253 { 254 // Close the document in developer studio 255 CComVariant vSaveChanges = dsSaveChangesPrompt; 256 DsSaveStatus Saved; 257 258 pDoc->Close (vSaveChanges, &Saved); 259 } 260 } 261 262 SysFreeString (FileName); 263 return S_OK; 264 } 265 266 HRESULT CCommands::XApplicationEvents::WindowActivate (IDispatch * theWindow) 267 { 268 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 269 return S_OK; 270 } 271 272 HRESULT CCommands::XApplicationEvents::WindowDeactivate (IDispatch * theWindow) 273 { 274 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 275 return S_OK; 276 } 277 278 HRESULT CCommands::XApplicationEvents::WorkspaceOpen () 279 { 280 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 281 return S_OK; 282 } 283 284 HRESULT CCommands::XApplicationEvents::WorkspaceClose () 285 { 286 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 287 return S_OK; 288 } 289 290 HRESULT CCommands::XApplicationEvents::NewWorkspace () 291 { 292 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 293 return S_OK; 294 } 295 296 // Debugger event 297 298 HRESULT CCommands::XDebuggerEvents::BreakpointHit (IDispatch * pBreakpoint) 299 { 300 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 301 return S_OK; 302 } 303 304 305 ///////////////////////////////////////////////////////////////////////////// 306 // VisVim dialog 307 308 class CMainDialog : public CDialog 309 { 310 public: 311 CMainDialog (CWnd * pParent = NULL); // Standard constructor 312 313 //{{AFX_DATA(CMainDialog) 314 enum { IDD = IDD_ADDINMAIN }; 315 int m_ChangeDir; 316 BOOL m_bDevStudioEditor; 317 //}}AFX_DATA 318 319 //{{AFX_VIRTUAL(CMainDialog) 320 protected: 321 virtual void DoDataExchange (CDataExchange * pDX); // DDX/DDV support 322 //}}AFX_VIRTUAL 323 324 protected: 325 //{{AFX_MSG(CMainDialog) 326 afx_msg void OnEnable(); 327 afx_msg void OnDisable(); 328 //}}AFX_MSG 329 DECLARE_MESSAGE_MAP () 330 }; 331 332 CMainDialog::CMainDialog (CWnd * pParent /* =NULL */ ) 333 : CDialog (CMainDialog::IDD, pParent) 334 { 335 //{{AFX_DATA_INIT(CMainDialog) 336 m_ChangeDir = -1; 337 m_bDevStudioEditor = FALSE; 338 //}}AFX_DATA_INIT 339 } 340 341 void CMainDialog::DoDataExchange (CDataExchange * pDX) 342 { 343 CDialog::DoDataExchange (pDX); 344 //{{AFX_DATA_MAP(CMainDialog) 345 DDX_Radio(pDX, IDC_CD_SOURCE_PATH, m_ChangeDir); 346 DDX_Check (pDX, IDC_DEVSTUDIO_EDITOR, m_bDevStudioEditor); 347 //}}AFX_DATA_MAP 348 } 349 350 BEGIN_MESSAGE_MAP (CMainDialog, CDialog) 351 //{{AFX_MSG_MAP(CMainDialog) 352 //}}AFX_MSG_MAP 353 END_MESSAGE_MAP () 354 355 356 ///////////////////////////////////////////////////////////////////////////// 357 // CCommands methods 358 359 STDMETHODIMP CCommands::VisVimDialog () 360 { 361 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 362 363 // Use m_pApplication to access the Developer Studio Application 364 // object, 365 // and VERIFY_OK to see error strings in DEBUG builds of your add-in 366 // (see stdafx.h) 367 368 VERIFY_OK (m_pApplication->EnableModeless (VARIANT_FALSE)); 369 370 CMainDialog Dlg; 371 372 Dlg.m_bDevStudioEditor = g_bDevStudioEditor; 373 Dlg.m_ChangeDir = g_ChangeDir; 374 if (Dlg.DoModal () == IDOK) 375 { 376 g_bDevStudioEditor = Dlg.m_bDevStudioEditor; 377 g_ChangeDir = Dlg.m_ChangeDir; 378 379 // Save settings to registry HKEY_CURRENT_USER\Software\Vim\VisVim 380 HKEY hAppKey = GetAppKey ("Vim"); 381 if (hAppKey) 382 { 383 HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); 384 if (hSectionKey) 385 { 386 WriteRegistryInt (hSectionKey, "DevStudioEditor", 387 g_bDevStudioEditor); 388 WriteRegistryInt (hSectionKey, "ChangeDir", g_ChangeDir); 389 RegCloseKey (hSectionKey); 390 } 391 RegCloseKey (hAppKey); 392 } 393 } 394 395 VERIFY_OK (m_pApplication->EnableModeless (VARIANT_TRUE)); 396 return S_OK; 397 } 398 399 STDMETHODIMP CCommands::VisVimEnable () 400 { 401 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 402 VimSetEnableState (true); 403 return S_OK; 404 } 405 406 STDMETHODIMP CCommands::VisVimDisable () 407 { 408 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 409 VimSetEnableState (false); 410 return S_OK; 411 } 412 413 STDMETHODIMP CCommands::VisVimToggle () 414 { 415 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 416 VimSetEnableState (! g_bEnableVim); 417 return S_OK; 418 } 419 420 STDMETHODIMP CCommands::VisVimLoad () 421 { 422 AFX_MANAGE_STATE (AfxGetStaticModuleState ()); 423 424 // Use m_pApplication to access the Developer Studio Application object, 425 // and VERIFY_OK to see error strings in DEBUG builds of your add-in 426 // (see stdafx.h) 427 428 CComBSTR bStr; 429 // Define dispatch pointers for document and selection objects 430 CComPtr < IDispatch > pDispDoc, pDispSel; 431 432 // Get a document object dispatch pointer 433 VERIFY_OK (m_pApplication->get_ActiveDocument (&pDispDoc)); 434 if (! pDispDoc) 435 return S_OK; 436 437 BSTR FileName; 438 long LineNr = -1; 439 440 // Get the document object 441 CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (pDispDoc); 442 443 if (! pDoc) 444 return S_OK; 445 446 // Get the document name 447 if (FAILED (pDoc->get_FullName (&FileName))) 448 return S_OK; 449 450 // Get a selection object dispatch pointer 451 if (SUCCEEDED (pDoc->get_Selection (&pDispSel))) 452 { 453 // Get the selection object 454 CComQIPtr < ITextSelection, &IID_ITextSelection > pSel (pDispSel); 455 456 if (pSel) 457 // Get the selection line number 458 pSel->get_CurrentLine (&LineNr); 459 } 460 461 // Open the file in Vim 462 VimOpenFile (FileName, LineNr); 463 464 SysFreeString (FileName); 465 return S_OK; 466 } 467 468 469 // 470 // Here we do the actual processing and communication with Vim 471 // 472 473 // Set the enable state and save to registry 474 // 475 static void VimSetEnableState (BOOL bEnableState) 476 { 477 g_bEnableVim = bEnableState; 478 HKEY hAppKey = GetAppKey ("Vim"); 479 if (hAppKey) 480 { 481 HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); 482 if (hSectionKey) 483 WriteRegistryInt (hSectionKey, "EnableVim", g_bEnableVim); 484 RegCloseKey (hAppKey); 485 } 486 } 487 488 // Open the file 'FileName' in Vim and goto line 'LineNr' 489 // 'FileName' is expected to contain an absolute DOS path including the drive 490 // letter. 491 // 'LineNr' must contain a valid line number or 0, e. g. for a new file 492 // 493 static BOOL VimOpenFile (BSTR& FileName, long LineNr) 494 { 495 496 // OLE automation object for com. with Vim 497 // When the object goes out of scope, it's destructor destroys the OLE 498 // connection; 499 // This is important to avoid blocking the object 500 // (in this memory corruption would be likely when terminating Vim 501 // while still running DevStudio). 502 // So keep this object local! 503 COleAutomationControl VimOle; 504 505 // :cd D:/Src2/VisVim/ 506 // 507 // Get a dispatch id for the SendKeys method of Vim; 508 // enables connection to Vim if necessary 509 DISPID DispatchId; 510 DispatchId = VimGetDispatchId (VimOle, "SendKeys"); 511 if (! DispatchId) 512 // OLE error, can't obtain dispatch id 513 goto OleError; 514 515 OLECHAR Buf[MAX_OLE_STR]; 516 char FileNameTmp[MAX_OLE_STR]; 517 char VimCmd[MAX_OLE_STR]; 518 char *s, *p; 519 520 // Prepend CTRL-\ CTRL-N to exit insert mode 521 VimCmd[0] = 0x1c; 522 VimCmd[1] = 0x0e; 523 VimCmd[2] = 0; 524 525 #ifdef SINGLE_WINDOW 526 // Update the current file in Vim if it has been modified. 527 // Disabled, because it could write the file when you don't want to. 528 sprintf (VimCmd + 2, ":up\n"); 529 #endif 530 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 531 goto OleError; 532 533 // Change Vim working directory to where the file is if desired 534 if (g_ChangeDir != CD_NONE) 535 VimChangeDir (VimOle, DispatchId, FileName); 536 537 // Make Vim open the file. 538 // In the filename convert all \ to /, put a \ before a space. 539 sprintf(VimCmd, ":drop "); 540 sprintf(FileNameTmp, "%S", (char *)FileName); 541 s = VimCmd + 6; 542 for (p = FileNameTmp; *p != '\0' && s < FileNameTmp + MAX_OLE_STR - 4; 543 ++p) 544 if (*p == '\\') 545 *s++ = '/'; 546 else 547 { 548 if (*p == ' ') 549 *s++ = '\\'; 550 *s++ = *p; 551 } 552 *s++ = '\n'; 553 *s = '\0'; 554 555 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 556 goto OleError; 557 558 if (LineNr > 0) 559 { 560 // Goto line 561 sprintf (VimCmd, ":%d\n", LineNr); 562 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 563 goto OleError; 564 } 565 566 // Make Vim come to the foreground 567 if (! VimOle.Method ("SetForeground")) 568 VimOle.ErrDiag (); 569 570 // We're done 571 return true; 572 573 OleError: 574 // There was an OLE error 575 // Check if it's the "unknown class string" error 576 VimErrDiag (VimOle); 577 return false; 578 } 579 580 // Return the dispatch id for the Vim method 'Method' 581 // Create the Vim OLE object if necessary 582 // Returns a valid dispatch id or null on error 583 // 584 static DISPID VimGetDispatchId (COleAutomationControl& VimOle, char* Method) 585 { 586 // Initialize Vim OLE connection if not already done 587 if (! VimOle.IsCreated ()) 588 { 589 if (! VimOle.CreateObject ("Vim.Application")) 590 return NULL; 591 } 592 593 // Get the dispatch id for the SendKeys method. 594 // By doing this, we are checking if Vim is still there... 595 DISPID DispatchId = VimOle.GetDispatchId ("SendKeys"); 596 if (! DispatchId) 597 { 598 // We can't get a dispatch id. 599 // This means that probably Vim has been terminated. 600 // Don't issue an error message here, instead 601 // destroy the OLE object and try to connect once more 602 // 603 // In fact, this should never happen, because the OLE aut. object 604 // should not be kept long enough to allow the user to terminate Vim 605 // to avoid memory corruption (why the heck is there no system garbage 606 // collection for those damned OLE memory chunks???). 607 VimOle.DeleteObject (); 608 if (! VimOle.CreateObject ("Vim.Application")) 609 // If this create fails, it's time for an error msg 610 return NULL; 611 612 if (! (DispatchId = VimOle.GetDispatchId ("SendKeys"))) 613 // There is something wrong... 614 return NULL; 615 } 616 617 return DispatchId; 618 } 619 620 // Output an error message for an OLE error 621 // Check on the classstring error, which probably means Vim wasn't registered. 622 // 623 static void VimErrDiag (COleAutomationControl& VimOle) 624 { 625 SCODE sc = GetScode (VimOle.GetResult ()); 626 if (sc == CO_E_CLASSSTRING) 627 { 628 char Buf[256]; 629 sprintf (Buf, "There is no registered OLE automation server named " 630 "\"Vim.Application\".\n" 631 "Use the OLE-enabled version of Vim with VisVim and " 632 "make sure to register Vim by running \"vim -register\"."); 633 MessageBox (NULL, Buf, "OLE Error", MB_OK); 634 } 635 else 636 VimOle.ErrDiag (); 637 } 638 639 // Change directory to the directory the file 'FileName' is in or it's parent 640 // directory according to the setting of the global 'g_ChangeDir': 641 // 'FileName' is expected to contain an absolute DOS path including the drive 642 // letter. 643 // CD_NONE 644 // CD_SOURCE_PATH 645 // CD_SOURCE_PARENT 646 // 647 static void VimChangeDir (COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName) 648 { 649 // Do a :cd first 650 651 // Get the path name of the file ("dir/") 652 CString StrFileName = FileName; 653 char Drive[_MAX_DRIVE]; 654 char Dir[_MAX_DIR]; 655 char DirUnix[_MAX_DIR * 2]; 656 char *s, *t; 657 658 _splitpath (StrFileName, Drive, Dir, NULL, NULL); 659 660 // Convert to Unix path name format, escape spaces. 661 t = DirUnix; 662 for (s = Dir; *s; ++s) 663 if (*s == '\\') 664 *t++ = '/'; 665 else 666 { 667 if (*s == ' ') 668 *t++ = '\\'; 669 *t++ = *s; 670 } 671 *t = '\0'; 672 673 674 // Construct the cd command; append /.. if cd to parent 675 // directory and not in root directory 676 OLECHAR Buf[MAX_OLE_STR]; 677 char VimCmd[MAX_OLE_STR]; 678 679 sprintf (VimCmd, ":cd %s%s%s\n", Drive, DirUnix, 680 g_ChangeDir == CD_SOURCE_PARENT && DirUnix[1] ? ".." : ""); 681 VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf)); 682 } 683 684 #ifdef _DEBUG 685 // Print out a debug message 686 // 687 static void DebugMsg (char* Msg, char* Arg) 688 { 689 char Buf[400]; 690 sprintf (Buf, Msg, Arg); 691 AfxMessageBox (Buf); 692 } 693 #endif 694 695