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 10static 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 21static BOOL g_bEnableVim = TRUE; // Vim enabled 22static BOOL g_bDevStudioEditor = FALSE; // Open file in Dev Studio editor simultaneously 23static BOOL g_bNewTabs = FALSE; 24static int g_ChangeDir = CD_NONE; // CD after file open? 25 26static void VimSetEnableState(BOOL bEnableState); 27static BOOL VimOpenFile(BSTR& FileName, long LineNr); 28static DISPID VimGetDispatchId(COleAutomationControl& VimOle, char* Method); 29static void VimErrDiag(COleAutomationControl& VimOle); 30static void VimChangeDir(COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName); 31static void DebugMsg(char* Msg, char* Arg = NULL); 32 33 34///////////////////////////////////////////////////////////////////////////// 35// CCommands 36 37CCommands::CCommands() 38{ 39 // m_pApplication == NULL; M$ Code generation bug!!! 40 m_pApplication = NULL; 41 m_pApplicationEventsObj = NULL; 42 m_pDebuggerEventsObj = NULL; 43} 44 45CCommands::~CCommands() 46{ 47 ASSERT(m_pApplication != NULL); 48 if (m_pApplication) 49 { 50 m_pApplication->Release(); 51 m_pApplication = NULL; 52 } 53} 54 55void 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 109void 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 142HRESULT CCommands::XApplicationEvents::BeforeBuildStart() 143{ 144 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 145 return S_OK; 146} 147 148HRESULT CCommands::XApplicationEvents::BuildFinish(long nNumErrors, long nNumWarnings) 149{ 150 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 151 return S_OK; 152} 153 154HRESULT 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// 164HRESULT 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 219HRESULT CCommands::XApplicationEvents::BeforeDocumentClose(IDispatch * theDocument) 220{ 221 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 222 return S_OK; 223} 224 225HRESULT CCommands::XApplicationEvents::DocumentSave(IDispatch * theDocument) 226{ 227 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 228 return S_OK; 229} 230 231HRESULT 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 269HRESULT CCommands::XApplicationEvents::WindowActivate(IDispatch * theWindow) 270{ 271 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 272 return S_OK; 273} 274 275HRESULT CCommands::XApplicationEvents::WindowDeactivate(IDispatch * theWindow) 276{ 277 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 278 return S_OK; 279} 280 281HRESULT CCommands::XApplicationEvents::WorkspaceOpen() 282{ 283 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 284 return S_OK; 285} 286 287HRESULT CCommands::XApplicationEvents::WorkspaceClose() 288{ 289 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 290 return S_OK; 291} 292 293HRESULT CCommands::XApplicationEvents::NewWorkspace() 294{ 295 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 296 return S_OK; 297} 298 299// Debugger event 300 301HRESULT CCommands::XDebuggerEvents::BreakpointHit(IDispatch * pBreakpoint) 302{ 303 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 304 return S_OK; 305} 306 307 308///////////////////////////////////////////////////////////////////////////// 309// VisVim dialog 310 311class 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 336CMainDialog::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 346void 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 356BEGIN_MESSAGE_MAP(CMainDialog, CDialog) 357 //{{AFX_MSG_MAP(CMainDialog) 358 //}}AFX_MSG_MAP 359END_MESSAGE_MAP() 360 361 362///////////////////////////////////////////////////////////////////////////// 363// CCommands methods 364 365STDMETHODIMP 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 409STDMETHODIMP CCommands::VisVimEnable() 410{ 411 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 412 VimSetEnableState(true); 413 return S_OK; 414} 415 416STDMETHODIMP CCommands::VisVimDisable() 417{ 418 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 419 VimSetEnableState(false); 420 return S_OK; 421} 422 423STDMETHODIMP CCommands::VisVimToggle() 424{ 425 AFX_MANAGE_STATE(AfxGetStaticModuleState()); 426 VimSetEnableState(! g_bEnableVim); 427 return S_OK; 428} 429 430STDMETHODIMP 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// 485static 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// 503static 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 + 11; 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 < FileNameTmp + MAX_OLE_STR - 4; 561 ++p) 562 if (*p == '\\') 563 *s++ = '/'; 564 else 565 { 566 if (*p == ' ') 567 *s++ = '\\'; 568 *s++ = *p; 569 } 570 *s++ = '\n'; 571 *s = '\0'; 572 573 if (! VimOle.Method(DispatchId, "s", TO_OLE_STR_BUF(VimCmd, Buf))) 574 goto OleError; 575 576 if (LineNr > 0) 577 { 578 // Goto line 579 sprintf(VimCmd, ":%d\n", LineNr); 580 if (! VimOle.Method(DispatchId, "s", TO_OLE_STR_BUF(VimCmd, Buf))) 581 goto OleError; 582 } 583 584 // Make Vim come to the foreground 585 if (! VimOle.Method("SetForeground")) 586 VimOle.ErrDiag(); 587 588 // We're done 589 return true; 590 591 OleError: 592 // There was an OLE error 593 // Check if it's the "unknown class string" error 594 VimErrDiag(VimOle); 595 return false; 596} 597 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 601// 602static DISPID VimGetDispatchId(COleAutomationControl& VimOle, char* Method) 603{ 604 // Initialize Vim OLE connection if not already done 605 if (! VimOle.IsCreated()) 606 { 607 if (! VimOle.CreateObject("Vim.Application")) 608 return NULL; 609 } 610 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) 615 { 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 620 // 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; 629 630 if (! (DispatchId = VimOle.GetDispatchId("SendKeys"))) 631 // There is something wrong... 632 return NULL; 633 } 634 635 return DispatchId; 636} 637 638// Output an error message for an OLE error 639// Check on the classstring error, which probably means Vim wasn't registered. 640// 641static void VimErrDiag(COleAutomationControl& VimOle) 642{ 643 SCODE sc = GetScode(VimOle.GetResult()); 644 if (sc == CO_E_CLASSSTRING) 645 { 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); 652 } 653 else 654 VimOle.ErrDiag(); 655} 656 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 664// 665static void VimChangeDir(COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName) 666{ 667 // Do a :cd first 668 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; 675 676 _splitpath(StrFileName, Drive, Dir, NULL, NULL); 677 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 684 { 685 if (*s == ' ') 686 *t++ = '\\'; 687 *t++ = *s; 688 } 689 *t = '\0'; 690 691 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]; 696 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)); 700} 701 702#ifdef _DEBUG 703// Print out a debug message 704// 705static void DebugMsg(char* Msg, char* Arg) 706{ 707 char Buf[400]; 708 sprintf(Buf, Msg, Arg); 709 AfxMessageBox(Buf); 710} 711#endif 712