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 its 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 desctructor destroys the OLE connection; 498 // This is imortant to avoid blocking the object 499 // (in this memory corruption would be likely when terminating Vim 500 // while still running DevStudio). 501 // So keep this object local! 502 COleAutomationControl VimOle; 503 504 // :cd D:/Src2/VisVim/ 505 // 506 // Get a dispatch id for the SendKeys method of Vim; 507 // enables connection to Vim if necessary 508 DISPID DispatchId; 509 DispatchId = VimGetDispatchId (VimOle, "SendKeys"); 510 if (! DispatchId) 511 // OLE error, can't obtain dispatch id 512 goto OleError; 513 514 OLECHAR Buf[MAX_OLE_STR]; 515 char FileNameTmp[MAX_OLE_STR]; 516 char VimCmd[MAX_OLE_STR]; 517 char *s, *p; 518 519 // Prepend CTRL-\ CTRL-N to exit insert mode 520 VimCmd[0] = 0x1c; 521 VimCmd[1] = 0x0e; 522 VimCmd[2] = 0; 523 524 #ifdef SINGLE_WINDOW 525 // Update the current file in Vim if it has been modified. 526 // Disabled, because it could write the file when you don't want to. 527 sprintf (VimCmd + 2, ":up\n"); 528 #endif 529 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 530 goto OleError; 531 532 // Change Vim working directory to where the file is if desired 533 if (g_ChangeDir != CD_NONE) 534 VimChangeDir (VimOle, DispatchId, FileName); 535 536 // Make Vim open the file. 537 // In the filename convert all \ to /, put a \ before a space. 538 sprintf(VimCmd, ":drop "); 539 sprintf(FileNameTmp, "%S", (char *)FileName); 540 s = VimCmd + 6; 541 for (p = FileNameTmp; *p != '\0' && s < FileNameTmp + MAX_OLE_STR - 4; 542 ++p) 543 if (*p == '\\') 544 *s++ = '/'; 545 else 546 { 547 if (*p == ' ') 548 *s++ = '\\'; 549 *s++ = *p; 550 } 551 *s++ = '\n'; 552 *s = '\0'; 553 554 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 555 goto OleError; 556 557 if (LineNr > 0) 558 { 559 // Goto line 560 sprintf (VimCmd, ":%d\n", LineNr); 561 if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) 562 goto OleError; 563 } 564 565 // Make Vim come to the foreground 566 if (! VimOle.Method ("SetForeground")) 567 VimOle.ErrDiag (); 568 569 // We're done 570 return true; 571 572 OleError: 573 // There was an OLE error 574 // Check if it's the "unknown class string" error 575 VimErrDiag (VimOle); 576 return false; 577 } 578 579 // Return the dispatch id for the Vim method 'Method' 580 // Create the Vim OLE object if necessary 581 // Returns a valid dispatch id or null on error 582 // 583 static DISPID VimGetDispatchId (COleAutomationControl& VimOle, char* Method) 584 { 585 // Initialize Vim OLE connection if not already done 586 if (! VimOle.IsCreated ()) 587 { 588 if (! VimOle.CreateObject ("Vim.Application")) 589 return NULL; 590 } 591 592 // Get the dispatch id for the SendKeys method. 593 // By doing this, we are checking if Vim is still there... 594 DISPID DispatchId = VimOle.GetDispatchId ("SendKeys"); 595 if (! DispatchId) 596 { 597 // We can't get a dispatch id. 598 // This means that probably Vim has been terminated. 599 // Don't issue an error message here, instead 600 // destroy the OLE object and try to connect once more 601 // 602 // In fact, this should never happen, because the OLE aut. object 603 // should not be kept long enough to allow the user to terminate Vim 604 // to avoid memory corruption (why the heck is there no system garbage 605 // collection for those damned OLE memory chunks???). 606 VimOle.DeleteObject (); 607 if (! VimOle.CreateObject ("Vim.Application")) 608 // If this create fails, it's time for an error msg 609 return NULL; 610 611 if (! (DispatchId = VimOle.GetDispatchId ("SendKeys"))) 612 // There is something wrong... 613 return NULL; 614 } 615 616 return DispatchId; 617 } 618 619 // Output an error message for an OLE error 620 // Check on the classstring error, which probably means Vim wasn't registered. 621 // 622 static void VimErrDiag (COleAutomationControl& VimOle) 623 { 624 SCODE sc = GetScode (VimOle.GetResult ()); 625 if (sc == CO_E_CLASSSTRING) 626 { 627 char Buf[256]; 628 sprintf (Buf, "There is no registered OLE automation server named " 629 "\"Vim.Application\".\n" 630 "Use the OLE-enabled version of Vim with VisVim and " 631 "make sure to register Vim by running \"vim -register\"."); 632 MessageBox (NULL, Buf, "OLE Error", MB_OK); 633 } 634 else 635 VimOle.ErrDiag (); 636 } 637 638 // Change directory to the directory the file 'FileName' is in or it's parent 639 // directory according to the setting of the global 'g_ChangeDir': 640 // 'FileName' is expected to contain an absolute DOS path including the drive 641 // letter. 642 // CD_NONE 643 // CD_SOURCE_PATH 644 // CD_SOURCE_PARENT 645 // 646 static void VimChangeDir (COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName) 647 { 648 // Do a :cd first 649 650 // Get the path name of the file ("dir/") 651 CString StrFileName = FileName; 652 char Drive[_MAX_DRIVE]; 653 char Dir[_MAX_DIR]; 654 char DirUnix[_MAX_DIR * 2]; 655 char *s, *t; 656 657 _splitpath (StrFileName, Drive, Dir, NULL, NULL); 658 659 // Convert to Unix path name format, escape spaces. 660 t = DirUnix; 661 for (s = Dir; *s; ++s) 662 if (*s == '\\') 663 *t++ = '/'; 664 else 665 { 666 if (*s == ' ') 667 *t++ = '\\'; 668 *t++ = *s; 669 } 670 *t = '\0'; 671 672 673 // Construct the cd command; append /.. if cd to parent 674 // directory and not in root directory 675 OLECHAR Buf[MAX_OLE_STR]; 676 char VimCmd[MAX_OLE_STR]; 677 678 sprintf (VimCmd, ":cd %s%s%s\n", Drive, DirUnix, 679 g_ChangeDir == CD_SOURCE_PARENT && DirUnix[1] ? ".." : ""); 680 VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf)); 681 } 682 683 #ifdef _DEBUG 684 // Print out a debug message 685 // 686 static void DebugMsg (char* Msg, char* Arg) 687 { 688 char Buf[400]; 689 sprintf (Buf, Msg, Arg); 690 AfxMessageBox (Buf); 691 } 692 #endif 693 694