text: #include "stdafx.h"
  #include <comdef.h>	// 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 BOOL g_bNewTabs = FALSE;
  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 its 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_bNewTabs = GetRegistryInt(hSectionKey, "NewTabs",
  						    g_bNewTabs);
  			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;
  	BOOL	m_bNewTabs;
  	//}}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;
  	m_bNewTabs = 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);
  	DDX_Check(pDX, IDC_NEW_TABS, m_bNewTabs);
  	//}}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_bNewTabs = g_bNewTabs;
  	Dlg.m_ChangeDir = g_ChangeDir;
  	if (Dlg.DoModal() == IDOK)
  	{
  		g_bDevStudioEditor = Dlg.m_bDevStudioEditor;
  		g_bNewTabs = Dlg.m_bNewTabs;
  		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, "NewTabs",
  						  g_bNewTabs);
  				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, its 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.
  	if (g_bNewTabs)
  	{
  		sprintf(VimCmd, ":tab drop ");
  		s = VimCmd + 10;
  	}
  	else
  	{
  		sprintf(VimCmd, ":drop ");
  		s = VimCmd + 6;
  	}
  	sprintf(FileNameTmp, "%S", (char *)FileName);
  	for (p = FileNameTmp; *p != '\0' && s < VimCmd + 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, ":%ld\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 its 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
  