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