#include "stdafx.h" #include // For _bstr_t #include "VisVim.h" #include "Commands.h" #include "OleAut.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif // Change directory before opening file? #define CD_SOURCE 0 // Cd to source path #define CD_SOURCE_PARENT 1 // Cd to parent directory of source path #define CD_NONE 2 // No cd static BOOL g_bEnableVim = TRUE; // Vim enabled static BOOL g_bDevStudioEditor = FALSE; // Open file in Dev Studio editor simultaneously static int g_ChangeDir = CD_NONE; // CD after file open? static void VimSetEnableState (BOOL bEnableState); static BOOL VimOpenFile (BSTR& FileName, long LineNr); static DISPID VimGetDispatchId (COleAutomationControl& VimOle, char* Method); static void VimErrDiag (COleAutomationControl& VimOle); static void VimChangeDir (COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName); static void DebugMsg (char* Msg, char* Arg = NULL); ///////////////////////////////////////////////////////////////////////////// // CCommands CCommands::CCommands () { // m_pApplication == NULL; M$ Code generation bug!!! m_pApplication = NULL; m_pApplicationEventsObj = NULL; m_pDebuggerEventsObj = NULL; } CCommands::~CCommands () { ASSERT (m_pApplication != NULL); if (m_pApplication) { m_pApplication->Release (); m_pApplication = NULL; } } void CCommands::SetApplicationObject (IApplication * pApplication) { // This function assumes pApplication has already been AddRef'd // for us, which CDSAddIn did in it's QueryInterface call // just before it called us. m_pApplication = pApplication; if (! m_pApplication) return; // Create Application event handlers XApplicationEventsObj::CreateInstance (&m_pApplicationEventsObj); if (! m_pApplicationEventsObj) { ReportInternalError ("XApplicationEventsObj::CreateInstance"); return; } m_pApplicationEventsObj->AddRef (); m_pApplicationEventsObj->Connect (m_pApplication); m_pApplicationEventsObj->m_pCommands = this; #ifdef NEVER // Create Debugger event handler CComPtr < IDispatch > pDebugger; if (SUCCEEDED (m_pApplication->get_Debugger (&pDebugger)) && pDebugger != NULL) { XDebuggerEventsObj::CreateInstance (&m_pDebuggerEventsObj); m_pDebuggerEventsObj->AddRef (); m_pDebuggerEventsObj->Connect (pDebugger); m_pDebuggerEventsObj->m_pCommands = this; } #endif // Get settings from registry HKEY_CURRENT_USER\Software\Vim\VisVim HKEY hAppKey = GetAppKey ("Vim"); if (hAppKey) { HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); if (hSectionKey) { g_bEnableVim = GetRegistryInt (hSectionKey, "EnableVim", g_bEnableVim); g_bDevStudioEditor = GetRegistryInt(hSectionKey,"DevStudioEditor", g_bDevStudioEditor); g_ChangeDir = GetRegistryInt (hSectionKey, "ChangeDir", g_ChangeDir); RegCloseKey (hSectionKey); } RegCloseKey (hAppKey); } } void CCommands::UnadviseFromEvents () { ASSERT (m_pApplicationEventsObj != NULL); if (m_pApplicationEventsObj) { m_pApplicationEventsObj->Disconnect (m_pApplication); m_pApplicationEventsObj->Release (); m_pApplicationEventsObj = NULL; } #ifdef NEVER if (m_pDebuggerEventsObj) { // Since we were able to connect to the Debugger events, we // should be able to access the Debugger object again to // unadvise from its events (thus the VERIFY_OK below--see // stdafx.h). CComPtr < IDispatch > pDebugger; VERIFY_OK (m_pApplication->get_Debugger (&pDebugger)); ASSERT (pDebugger != NULL); m_pDebuggerEventsObj->Disconnect (pDebugger); m_pDebuggerEventsObj->Release (); m_pDebuggerEventsObj = NULL; } #endif } ///////////////////////////////////////////////////////////////////////////// // Event handlers // Application events HRESULT CCommands::XApplicationEvents::BeforeBuildStart () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::BuildFinish (long nNumErrors, long nNumWarnings) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::BeforeApplicationShutDown () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } // The open document event handle is the place where the real interface work // is done. // Vim gets called from here. // HRESULT CCommands::XApplicationEvents::DocumentOpen (IDispatch * theDocument) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); if (! g_bEnableVim) // Vim not enabled or empty command line entered return S_OK; // First get the current file name and line number // Get the document object CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (theDocument); if (! pDoc) return S_OK; BSTR FileName; long LineNr = -1; // Get the document name if (FAILED (pDoc->get_FullName (&FileName))) return S_OK; LPDISPATCH pDispSel; // Get a selection object dispatch pointer if (SUCCEEDED (pDoc->get_Selection (&pDispSel))) { // Get the selection object CComQIPtr < ITextSelection, &IID_ITextSelection > pSel (pDispSel); if (pSel) // Get the selection line number pSel->get_CurrentLine (&LineNr); pDispSel->Release (); } // Open the file in Vim and position to the current line if (VimOpenFile (FileName, LineNr)) { if (! g_bDevStudioEditor) { // Close the document in developer studio CComVariant vSaveChanges = dsSaveChangesPrompt; DsSaveStatus Saved; pDoc->Close (vSaveChanges, &Saved); } } // We're done here SysFreeString (FileName); return S_OK; } HRESULT CCommands::XApplicationEvents::BeforeDocumentClose (IDispatch * theDocument) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::DocumentSave (IDispatch * theDocument) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::NewDocument (IDispatch * theDocument) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); if (! g_bEnableVim) // Vim not enabled or empty command line entered return S_OK; // First get the current file name and line number CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (theDocument); if (! pDoc) return S_OK; BSTR FileName; HRESULT hr; hr = pDoc->get_FullName (&FileName); if (FAILED (hr)) return S_OK; // Open the file in Vim and position to the current line if (VimOpenFile (FileName, 0)) { if (! g_bDevStudioEditor) { // Close the document in developer studio CComVariant vSaveChanges = dsSaveChangesPrompt; DsSaveStatus Saved; pDoc->Close (vSaveChanges, &Saved); } } SysFreeString (FileName); return S_OK; } HRESULT CCommands::XApplicationEvents::WindowActivate (IDispatch * theWindow) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::WindowDeactivate (IDispatch * theWindow) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::WorkspaceOpen () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::WorkspaceClose () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } HRESULT CCommands::XApplicationEvents::NewWorkspace () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } // Debugger event HRESULT CCommands::XDebuggerEvents::BreakpointHit (IDispatch * pBreakpoint) { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); return S_OK; } ///////////////////////////////////////////////////////////////////////////// // VisVim dialog class CMainDialog : public CDialog { public: CMainDialog (CWnd * pParent = NULL); // Standard constructor //{{AFX_DATA(CMainDialog) enum { IDD = IDD_ADDINMAIN }; int m_ChangeDir; BOOL m_bDevStudioEditor; //}}AFX_DATA //{{AFX_VIRTUAL(CMainDialog) protected: virtual void DoDataExchange (CDataExchange * pDX); // DDX/DDV support //}}AFX_VIRTUAL protected: //{{AFX_MSG(CMainDialog) afx_msg void OnEnable(); afx_msg void OnDisable(); //}}AFX_MSG DECLARE_MESSAGE_MAP () }; CMainDialog::CMainDialog (CWnd * pParent /* =NULL */ ) : CDialog (CMainDialog::IDD, pParent) { //{{AFX_DATA_INIT(CMainDialog) m_ChangeDir = -1; m_bDevStudioEditor = FALSE; //}}AFX_DATA_INIT } void CMainDialog::DoDataExchange (CDataExchange * pDX) { CDialog::DoDataExchange (pDX); //{{AFX_DATA_MAP(CMainDialog) DDX_Radio(pDX, IDC_CD_SOURCE_PATH, m_ChangeDir); DDX_Check (pDX, IDC_DEVSTUDIO_EDITOR, m_bDevStudioEditor); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP (CMainDialog, CDialog) //{{AFX_MSG_MAP(CMainDialog) //}}AFX_MSG_MAP END_MESSAGE_MAP () ///////////////////////////////////////////////////////////////////////////// // CCommands methods STDMETHODIMP CCommands::VisVimDialog () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); // Use m_pApplication to access the Developer Studio Application // object, // and VERIFY_OK to see error strings in DEBUG builds of your add-in // (see stdafx.h) VERIFY_OK (m_pApplication->EnableModeless (VARIANT_FALSE)); CMainDialog Dlg; Dlg.m_bDevStudioEditor = g_bDevStudioEditor; Dlg.m_ChangeDir = g_ChangeDir; if (Dlg.DoModal () == IDOK) { g_bDevStudioEditor = Dlg.m_bDevStudioEditor; g_ChangeDir = Dlg.m_ChangeDir; // Save settings to registry HKEY_CURRENT_USER\Software\Vim\VisVim HKEY hAppKey = GetAppKey ("Vim"); if (hAppKey) { HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); if (hSectionKey) { WriteRegistryInt (hSectionKey, "DevStudioEditor", g_bDevStudioEditor); WriteRegistryInt (hSectionKey, "ChangeDir", g_ChangeDir); RegCloseKey (hSectionKey); } RegCloseKey (hAppKey); } } VERIFY_OK (m_pApplication->EnableModeless (VARIANT_TRUE)); return S_OK; } STDMETHODIMP CCommands::VisVimEnable () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); VimSetEnableState (true); return S_OK; } STDMETHODIMP CCommands::VisVimDisable () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); VimSetEnableState (false); return S_OK; } STDMETHODIMP CCommands::VisVimToggle () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); VimSetEnableState (! g_bEnableVim); return S_OK; } STDMETHODIMP CCommands::VisVimLoad () { AFX_MANAGE_STATE (AfxGetStaticModuleState ()); // Use m_pApplication to access the Developer Studio Application object, // and VERIFY_OK to see error strings in DEBUG builds of your add-in // (see stdafx.h) CComBSTR bStr; // Define dispatch pointers for document and selection objects CComPtr < IDispatch > pDispDoc, pDispSel; // Get a document object dispatch pointer VERIFY_OK (m_pApplication->get_ActiveDocument (&pDispDoc)); if (! pDispDoc) return S_OK; BSTR FileName; long LineNr = -1; // Get the document object CComQIPtr < ITextDocument, &IID_ITextDocument > pDoc (pDispDoc); if (! pDoc) return S_OK; // Get the document name if (FAILED (pDoc->get_FullName (&FileName))) return S_OK; // Get a selection object dispatch pointer if (SUCCEEDED (pDoc->get_Selection (&pDispSel))) { // Get the selection object CComQIPtr < ITextSelection, &IID_ITextSelection > pSel (pDispSel); if (pSel) // Get the selection line number pSel->get_CurrentLine (&LineNr); } // Open the file in Vim VimOpenFile (FileName, LineNr); SysFreeString (FileName); return S_OK; } // // Here we do the actual processing and communication with Vim // // Set the enable state and save to registry // static void VimSetEnableState (BOOL bEnableState) { g_bEnableVim = bEnableState; HKEY hAppKey = GetAppKey ("Vim"); if (hAppKey) { HKEY hSectionKey = GetSectionKey (hAppKey, "VisVim"); if (hSectionKey) WriteRegistryInt (hSectionKey, "EnableVim", g_bEnableVim); RegCloseKey (hAppKey); } } // Open the file 'FileName' in Vim and goto line 'LineNr' // 'FileName' is expected to contain an absolute DOS path including the drive // letter. // 'LineNr' must contain a valid line number or 0, e. g. for a new file // static BOOL VimOpenFile (BSTR& FileName, long LineNr) { // OLE automation object for com. with Vim // When the object goes out of scope, it's destructor destroys the OLE // connection; // This is important to avoid blocking the object // (in this memory corruption would be likely when terminating Vim // while still running DevStudio). // So keep this object local! COleAutomationControl VimOle; // :cd D:/Src2/VisVim/ // // Get a dispatch id for the SendKeys method of Vim; // enables connection to Vim if necessary DISPID DispatchId; DispatchId = VimGetDispatchId (VimOle, "SendKeys"); if (! DispatchId) // OLE error, can't obtain dispatch id goto OleError; OLECHAR Buf[MAX_OLE_STR]; char FileNameTmp[MAX_OLE_STR]; char VimCmd[MAX_OLE_STR]; char *s, *p; // Prepend CTRL-\ CTRL-N to exit insert mode VimCmd[0] = 0x1c; VimCmd[1] = 0x0e; VimCmd[2] = 0; #ifdef SINGLE_WINDOW // Update the current file in Vim if it has been modified. // Disabled, because it could write the file when you don't want to. sprintf (VimCmd + 2, ":up\n"); #endif if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) goto OleError; // Change Vim working directory to where the file is if desired if (g_ChangeDir != CD_NONE) VimChangeDir (VimOle, DispatchId, FileName); // Make Vim open the file. // In the filename convert all \ to /, put a \ before a space. sprintf(VimCmd, ":drop "); sprintf(FileNameTmp, "%S", (char *)FileName); s = VimCmd + 6; for (p = FileNameTmp; *p != '\0' && s < FileNameTmp + MAX_OLE_STR - 4; ++p) if (*p == '\\') *s++ = '/'; else { if (*p == ' ') *s++ = '\\'; *s++ = *p; } *s++ = '\n'; *s = '\0'; if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) goto OleError; if (LineNr > 0) { // Goto line sprintf (VimCmd, ":%d\n", LineNr); if (! VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf))) goto OleError; } // Make Vim come to the foreground if (! VimOle.Method ("SetForeground")) VimOle.ErrDiag (); // We're done return true; OleError: // There was an OLE error // Check if it's the "unknown class string" error VimErrDiag (VimOle); return false; } // Return the dispatch id for the Vim method 'Method' // Create the Vim OLE object if necessary // Returns a valid dispatch id or null on error // static DISPID VimGetDispatchId (COleAutomationControl& VimOle, char* Method) { // Initialize Vim OLE connection if not already done if (! VimOle.IsCreated ()) { if (! VimOle.CreateObject ("Vim.Application")) return NULL; } // Get the dispatch id for the SendKeys method. // By doing this, we are checking if Vim is still there... DISPID DispatchId = VimOle.GetDispatchId ("SendKeys"); if (! DispatchId) { // We can't get a dispatch id. // This means that probably Vim has been terminated. // Don't issue an error message here, instead // destroy the OLE object and try to connect once more // // In fact, this should never happen, because the OLE aut. object // should not be kept long enough to allow the user to terminate Vim // to avoid memory corruption (why the heck is there no system garbage // collection for those damned OLE memory chunks???). VimOle.DeleteObject (); if (! VimOle.CreateObject ("Vim.Application")) // If this create fails, it's time for an error msg return NULL; if (! (DispatchId = VimOle.GetDispatchId ("SendKeys"))) // There is something wrong... return NULL; } return DispatchId; } // Output an error message for an OLE error // Check on the classstring error, which probably means Vim wasn't registered. // static void VimErrDiag (COleAutomationControl& VimOle) { SCODE sc = GetScode (VimOle.GetResult ()); if (sc == CO_E_CLASSSTRING) { char Buf[256]; sprintf (Buf, "There is no registered OLE automation server named " "\"Vim.Application\".\n" "Use the OLE-enabled version of Vim with VisVim and " "make sure to register Vim by running \"vim -register\"."); MessageBox (NULL, Buf, "OLE Error", MB_OK); } else VimOle.ErrDiag (); } // Change directory to the directory the file 'FileName' is in or it's parent // directory according to the setting of the global 'g_ChangeDir': // 'FileName' is expected to contain an absolute DOS path including the drive // letter. // CD_NONE // CD_SOURCE_PATH // CD_SOURCE_PARENT // static void VimChangeDir (COleAutomationControl& VimOle, DISPID DispatchId, BSTR& FileName) { // Do a :cd first // Get the path name of the file ("dir/") CString StrFileName = FileName; char Drive[_MAX_DRIVE]; char Dir[_MAX_DIR]; char DirUnix[_MAX_DIR * 2]; char *s, *t; _splitpath (StrFileName, Drive, Dir, NULL, NULL); // Convert to Unix path name format, escape spaces. t = DirUnix; for (s = Dir; *s; ++s) if (*s == '\\') *t++ = '/'; else { if (*s == ' ') *t++ = '\\'; *t++ = *s; } *t = '\0'; // Construct the cd command; append /.. if cd to parent // directory and not in root directory OLECHAR Buf[MAX_OLE_STR]; char VimCmd[MAX_OLE_STR]; sprintf (VimCmd, ":cd %s%s%s\n", Drive, DirUnix, g_ChangeDir == CD_SOURCE_PARENT && DirUnix[1] ? ".." : ""); VimOle.Method (DispatchId, "s", TO_OLE_STR_BUF (VimCmd, Buf)); } #ifdef _DEBUG // Print out a debug message // static void DebugMsg (char* Msg, char* Arg) { char Buf[400]; sprintf (Buf, Msg, Arg); AfxMessageBox (Buf); } #endif