view src/VisVim/Commands.cpp @ 16150:b23048205589 v8.1.1080

patch 8.1.1080: when a screendump test fails, moving the file is a hassle commit https://github.com/vim/vim/commit/ef7f0e367eeaf6fb31b1caa0e3de1a4b07e86af3 Author: Bram Moolenaar <Bram@vim.org> Date: Sat Mar 30 15:59:51 2019 +0100 patch 8.1.1080: when a screendump test fails, moving the file is a hassle Problem: When a screendump test fails, moving the file is a hassle. Solution: Instead of appending ".failed" to the file name, keep the same file name but put the screendump in the "failed" directory. Then the file name only needs to be typed once when moving a screendump.
author Bram Moolenaar <Bram@vim.org>
date Sat, 30 Mar 2019 16:00:06 +0100
parents 665d10cb3e6b
children 1759d0ec0a6f
line wrap: on
line source

#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 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_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, 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.
	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 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