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