xref: /vim-8.2.3635/src/GvimExt/gvimext.cpp (revision dabfde04)
1 /* vi:set ts=8 sts=4 sw=4:
2  *
3  * VIM - Vi IMproved	gvimext by Tianmiao Hu
4  *
5  * Do ":help uganda"  in Vim to read copying and usage conditions.
6  * Do ":help credits" in Vim to see a list of people who contributed.
7  */
8 
9 /*
10  * gvimext is a DLL which is used for the "Edit with Vim" context menu
11  * extension.  It implements a MS defined interface with the Shell.
12  *
13  * If you have any questions or any suggestions concerning gvimext, please
14  * contact Tianmiao Hu: [email protected].
15  */
16 
17 #include "gvimext.h"
18 
19 static char *searchpath(char *name);
20 
21 // Always get an error while putting the following stuff to the
22 // gvimext.h file as class protected variables, give up and
23 // declare them as global stuff
24 FORMATETC fmte = {CF_HDROP,
25 		  (DVTARGETDEVICE FAR *)NULL,
26 		  DVASPECT_CONTENT,
27 		  -1,
28 		  TYMED_HGLOBAL
29 		 };
30 STGMEDIUM medium;
31 HRESULT hres = 0;
32 UINT cbFiles = 0;
33 
34 /* The buffers size used to be MAX_PATH (260 bytes), but that's not always
35  * enough */
36 #define BUFSIZE 1100
37 
38 //
39 // Get the name of the Gvim executable to use, with the path.
40 // When "runtime" is non-zero, we were called to find the runtime directory.
41 // Returns the path in name[BUFSIZE].  It's empty when it fails.
42 //
43     static void
44 getGvimName(char *name, int runtime)
45 {
46     HKEY	keyhandle;
47     DWORD	hlen;
48 
49     // Get the location of gvim from the registry.
50     name[0] = 0;
51     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
52 				       KEY_READ, &keyhandle) == ERROR_SUCCESS)
53     {
54 	hlen = BUFSIZE;
55 	if (RegQueryValueEx(keyhandle, "path", 0, NULL, (BYTE *)name, &hlen)
56 							     != ERROR_SUCCESS)
57 	    name[0] = 0;
58 	else
59 	    name[hlen] = 0;
60 	RegCloseKey(keyhandle);
61     }
62 
63     // Registry didn't work, use the search path.
64     if (name[0] == 0)
65 	strcpy(name, searchpath((char *)"gvim.exe"));
66 
67     if (!runtime)
68     {
69 	// Only when looking for the executable, not the runtime dir, we can
70 	// search for the batch file or a name without a path.
71 	if (name[0] == 0)
72 	    strcpy(name, searchpath((char *)"gvim.bat"));
73 	if (name[0] == 0)
74 	    strcpy(name, "gvim");	// finds gvim.bat or gvim.exe
75     }
76 }
77 
78     static void
79 getGvimInvocation(char *name, int runtime)
80 {
81     getGvimName(name, runtime);
82     // avoid that Vim tries to expand wildcards in the file names
83     strcat(name, " --literal");
84 }
85 
86     static void
87 getGvimInvocationW(wchar_t *nameW)
88 {
89     char *name;
90 
91     name = (char *)malloc(BUFSIZE);
92     getGvimInvocation(name, 0);
93     mbstowcs(nameW, name, BUFSIZE);
94     free(name);
95 }
96 
97 //
98 // Get the Vim runtime directory into buf[BUFSIZE].
99 // The result is empty when it failed.
100 // When it works, the path ends in a slash or backslash.
101 //
102     static void
103 getRuntimeDir(char *buf)
104 {
105     int		idx;
106 
107     getGvimName(buf, 1);
108     if (buf[0] != 0)
109     {
110 	// When no path found, use the search path to expand it.
111 	if (strchr(buf, '/') == NULL && strchr(buf, '\\') == NULL)
112 	    strcpy(buf, searchpath(buf));
113 
114 	// remove "gvim.exe" from the end
115 	for (idx = (int)strlen(buf) - 1; idx >= 0; idx--)
116 	    if (buf[idx] == '\\' || buf[idx] == '/')
117 	    {
118 		buf[idx + 1] = 0;
119 		break;
120 	    }
121     }
122 }
123 
124 HBITMAP IconToBitmap(HICON hIcon, HBRUSH hBackground, int width, int height)
125 {
126 	HDC hDC = GetDC(NULL);
127 	HDC hMemDC = CreateCompatibleDC(hDC);
128 	HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, width, height);
129 	HBITMAP hResultBmp = NULL;
130 	HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
131 
132 	DrawIconEx(hMemDC, 0, 0, hIcon, width, height, 0, hBackground, DI_NORMAL);
133 
134 	hResultBmp = hMemBmp;
135 	hMemBmp = NULL;
136 
137 	SelectObject(hMemDC, hOrgBMP);
138 	DeleteDC(hMemDC);
139 	ReleaseDC(NULL, hDC);
140 	DestroyIcon(hIcon);
141 	return hResultBmp;
142 }
143 
144 //
145 // GETTEXT: translated messages and menu entries
146 //
147 #ifndef FEAT_GETTEXT
148 # define _(x)  x
149 #else
150 # define _(x)  (*dyn_libintl_gettext)(x)
151 # define VIMPACKAGE "vim"
152 # ifndef GETTEXT_DLL
153 #  define GETTEXT_DLL "libintl.dll"
154 #  define GETTEXT_DLL_ALT "libintl-8.dll"
155 # endif
156 
157 // Dummy functions
158 static char *null_libintl_gettext(const char *);
159 static char *null_libintl_textdomain(const char *);
160 static char *null_libintl_bindtextdomain(const char *, const char *);
161 static int dyn_libintl_init(char *dir);
162 static void dyn_libintl_end(void);
163 
164 static wchar_t *oldenv = NULL;
165 static HINSTANCE hLibintlDLL = 0;
166 static char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext;
167 static char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain;
168 static char *(*dyn_libintl_bindtextdomain)(const char *, const char *)
169 						= null_libintl_bindtextdomain;
170 
171 //
172 // Attempt to load libintl.dll.  If it doesn't work, use dummy functions.
173 // "dir" is the directory where the libintl.dll might be.
174 // Return 1 for success, 0 for failure.
175 //
176     static int
177 dyn_libintl_init(char *dir)
178 {
179     int		i;
180     static struct
181     {
182 	char	    *name;
183 	FARPROC	    *ptr;
184     } libintl_entry[] =
185     {
186 	{(char *)"gettext",		(FARPROC*)&dyn_libintl_gettext},
187 	{(char *)"textdomain",		(FARPROC*)&dyn_libintl_textdomain},
188 	{(char *)"bindtextdomain",	(FARPROC*)&dyn_libintl_bindtextdomain},
189 	{NULL, NULL}
190     };
191     DWORD	len, len2;
192     LPWSTR	buf = NULL;
193     LPWSTR	buf2 = NULL;
194 
195     // No need to initialize twice.
196     if (hLibintlDLL)
197 	return 1;
198 
199     // Load gettext library from $VIMRUNTIME\GvimExt{64,32} directory.
200     // Add the directory to $PATH temporarily.
201     len = GetEnvironmentVariableW(L"PATH", NULL, 0);
202     len2 = MAX_PATH + 1 + len;
203     buf = (LPWSTR)malloc(len * sizeof(WCHAR));
204     buf2 = (LPWSTR)malloc(len2 * sizeof(WCHAR));
205     if (buf != NULL && buf2 != NULL)
206     {
207 	GetEnvironmentVariableW(L"PATH", buf, len);
208 #ifdef _WIN64
209 	_snwprintf(buf2, len2, L"%S\\GvimExt64;%s", dir, buf);
210 #else
211 	_snwprintf(buf2, len2, L"%S\\GvimExt32;%s", dir, buf);
212 #endif
213 	SetEnvironmentVariableW(L"PATH", buf2);
214 	hLibintlDLL = LoadLibrary(GETTEXT_DLL);
215 #ifdef GETTEXT_DLL_ALT
216 	if (!hLibintlDLL)
217 	    hLibintlDLL = LoadLibrary(GETTEXT_DLL_ALT);
218 #endif
219 	SetEnvironmentVariableW(L"PATH", buf);
220     }
221     free(buf);
222     free(buf2);
223     if (!hLibintlDLL)
224 	return 0;
225 
226     // Get the addresses of the functions we need.
227     for (i = 0; libintl_entry[i].name != NULL
228 					 && libintl_entry[i].ptr != NULL; ++i)
229     {
230 	if ((*libintl_entry[i].ptr = GetProcAddress(hLibintlDLL,
231 					      libintl_entry[i].name)) == NULL)
232 	{
233 	    dyn_libintl_end();
234 	    return 0;
235 	}
236     }
237     return 1;
238 }
239 
240     static void
241 dyn_libintl_end(void)
242 {
243     if (hLibintlDLL)
244 	FreeLibrary(hLibintlDLL);
245     hLibintlDLL			= NULL;
246     dyn_libintl_gettext		= null_libintl_gettext;
247     dyn_libintl_textdomain	= null_libintl_textdomain;
248     dyn_libintl_bindtextdomain	= null_libintl_bindtextdomain;
249 }
250 
251     static char *
252 null_libintl_gettext(const char *msgid)
253 {
254     return (char *)msgid;
255 }
256 
257     static char *
258 null_libintl_bindtextdomain(const char * /* domainname */, const char * /* dirname */)
259 {
260     return NULL;
261 }
262 
263     static char *
264 null_libintl_textdomain(const char*  /* domainname */)
265 {
266     return NULL;
267 }
268 
269 //
270 // Setup for translating strings.
271 //
272     static void
273 dyn_gettext_load(void)
274 {
275     char    szBuff[BUFSIZE];
276     char    szLang[BUFSIZE];
277     DWORD   len;
278     HKEY    keyhandle;
279     int	    gotlang = 0;
280 
281     strcpy(szLang, "LANG=");
282 
283     // First try getting the language from the registry, this can be
284     // used to overrule the system language.
285     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
286 				       KEY_READ, &keyhandle) == ERROR_SUCCESS)
287     {
288 	len = BUFSIZE;
289 	if (RegQueryValueEx(keyhandle, "lang", 0, NULL, (BYTE*)szBuff, &len)
290 							     == ERROR_SUCCESS)
291 	{
292 	    szBuff[len] = 0;
293 	    strcat(szLang, szBuff);
294 	    gotlang = 1;
295 	}
296 	RegCloseKey(keyhandle);
297     }
298 
299     if (!gotlang && getenv("LANG") == NULL)
300     {
301 	// Get the language from the system.
302 	// Could use LOCALE_SISO639LANGNAME, but it's not in Win95.
303 	// LOCALE_SABBREVLANGNAME gives us three letters, like "enu", we use
304 	// only the first two.
305 	len = GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SABBREVLANGNAME,
306 						    (LPTSTR)szBuff, BUFSIZE);
307 	if (len >= 2 && _strnicmp(szBuff, "en", 2) != 0)
308 	{
309 	    // There are a few exceptions (probably more)
310 	    if (_strnicmp(szBuff, "cht", 3) == 0
311 					  || _strnicmp(szBuff, "zht", 3) == 0)
312 		strcpy(szBuff, "zh_TW");
313 	    else if (_strnicmp(szBuff, "chs", 3) == 0
314 					  || _strnicmp(szBuff, "zhc", 3) == 0)
315 		strcpy(szBuff, "zh_CN");
316 	    else if (_strnicmp(szBuff, "jp", 2) == 0)
317 		strcpy(szBuff, "ja");
318 	    else
319 		szBuff[2] = 0;	// truncate to two-letter code
320 	    strcat(szLang, szBuff);
321 	    gotlang = 1;
322 	}
323     }
324     if (gotlang)
325 	putenv(szLang);
326 
327     // Try to locate the runtime files.  The path is used to find libintl.dll
328     // and the vim.mo files.
329     getRuntimeDir(szBuff);
330     if (szBuff[0] != 0)
331     {
332 	len = (DWORD)strlen(szBuff);
333 	if (dyn_libintl_init(szBuff))
334 	{
335 	    strcpy(szBuff + len, "lang");
336 
337 	    (*dyn_libintl_bindtextdomain)(VIMPACKAGE, szBuff);
338 	    (*dyn_libintl_textdomain)(VIMPACKAGE);
339 	}
340     }
341 }
342 
343     static void
344 dyn_gettext_free(void)
345 {
346     dyn_libintl_end();
347 }
348 #endif // FEAT_GETTEXT
349 
350 //
351 // Global variables
352 //
353 UINT      g_cRefThisDll = 0;    // Reference count of this DLL.
354 HINSTANCE g_hmodThisDll = NULL;	// Handle to this DLL itself.
355 
356 
357 //---------------------------------------------------------------------------
358 // DllMain
359 //---------------------------------------------------------------------------
360 extern "C" int APIENTRY
361 DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID  /* lpReserved */)
362 {
363     switch (dwReason)
364     {
365     case DLL_PROCESS_ATTACH:
366 	// Extension DLL one-time initialization
367 	g_hmodThisDll = hInstance;
368 	break;
369 
370     case DLL_PROCESS_DETACH:
371 	break;
372     }
373 
374     return 1;   // ok
375 }
376 
377     static void
378 inc_cRefThisDLL()
379 {
380 #ifdef FEAT_GETTEXT
381     if (g_cRefThisDll == 0) {
382 	dyn_gettext_load();
383 	oldenv = GetEnvironmentStringsW();
384     }
385 #endif
386     InterlockedIncrement((LPLONG)&g_cRefThisDll);
387 }
388 
389     static void
390 dec_cRefThisDLL()
391 {
392 #ifdef FEAT_GETTEXT
393     if (InterlockedDecrement((LPLONG)&g_cRefThisDll) == 0) {
394 	dyn_gettext_free();
395 	if (oldenv != NULL) {
396 	    FreeEnvironmentStringsW(oldenv);
397 	    oldenv = NULL;
398 	}
399     }
400 #else
401     InterlockedDecrement((LPLONG)&g_cRefThisDll);
402 #endif
403 }
404 
405 //---------------------------------------------------------------------------
406 // DllCanUnloadNow
407 //---------------------------------------------------------------------------
408 
409 STDAPI DllCanUnloadNow(void)
410 {
411     return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
412 }
413 
414 STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
415 {
416     *ppvOut = NULL;
417 
418     if (IsEqualIID(rclsid, CLSID_ShellExtension))
419     {
420 	CShellExtClassFactory *pcf = new CShellExtClassFactory;
421 
422 	return pcf->QueryInterface(riid, ppvOut);
423     }
424 
425     return CLASS_E_CLASSNOTAVAILABLE;
426 }
427 
428 CShellExtClassFactory::CShellExtClassFactory()
429 {
430     m_cRef = 0L;
431 
432     inc_cRefThisDLL();
433 }
434 
435 CShellExtClassFactory::~CShellExtClassFactory()
436 {
437     dec_cRefThisDLL();
438 }
439 
440 STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid,
441 						   LPVOID FAR *ppv)
442 {
443     *ppv = NULL;
444 
445     // any interface on this object is the object pointer
446 
447     if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
448     {
449 	*ppv = (LPCLASSFACTORY)this;
450 
451 	AddRef();
452 
453 	return NOERROR;
454     }
455 
456     return E_NOINTERFACE;
457 }
458 
459 STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef()
460 {
461     return InterlockedIncrement((LPLONG)&m_cRef);
462 }
463 
464 STDMETHODIMP_(ULONG) CShellExtClassFactory::Release()
465 {
466     if (InterlockedDecrement((LPLONG)&m_cRef))
467 	return m_cRef;
468 
469     delete this;
470 
471     return 0L;
472 }
473 
474 STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
475 						      REFIID riid,
476 						      LPVOID *ppvObj)
477 {
478     *ppvObj = NULL;
479 
480     // Shell extensions typically don't support aggregation (inheritance)
481 
482     if (pUnkOuter)
483 	return CLASS_E_NOAGGREGATION;
484 
485     // Create the main shell extension object.  The shell will then call
486     // QueryInterface with IID_IShellExtInit--this is how shell extensions are
487     // initialized.
488 
489     LPCSHELLEXT pShellExt = new CShellExt();  // create the CShellExt object
490 
491     if (NULL == pShellExt)
492 	return E_OUTOFMEMORY;
493 
494     return pShellExt->QueryInterface(riid, ppvObj);
495 }
496 
497 
498 STDMETHODIMP CShellExtClassFactory::LockServer(BOOL  /* fLock */)
499 {
500     return NOERROR;
501 }
502 
503 // *********************** CShellExt *************************
504 CShellExt::CShellExt()
505 {
506     m_cRef = 0L;
507     m_pDataObj = NULL;
508 
509     inc_cRefThisDLL();
510 
511     LoadMenuIcon();
512 }
513 
514 CShellExt::~CShellExt()
515 {
516     if (m_pDataObj)
517 	m_pDataObj->Release();
518 
519     dec_cRefThisDLL();
520 
521     if (m_hVimIconBitmap)
522 	DeleteObject(m_hVimIconBitmap);
523 }
524 
525 STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv)
526 {
527     *ppv = NULL;
528 
529     if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
530     {
531 	*ppv = (LPSHELLEXTINIT)this;
532     }
533     else if (IsEqualIID(riid, IID_IContextMenu))
534     {
535 	*ppv = (LPCONTEXTMENU)this;
536     }
537 
538     if (*ppv)
539     {
540 	AddRef();
541 
542 	return NOERROR;
543     }
544 
545     return E_NOINTERFACE;
546 }
547 
548 STDMETHODIMP_(ULONG) CShellExt::AddRef()
549 {
550     return InterlockedIncrement((LPLONG)&m_cRef);
551 }
552 
553 STDMETHODIMP_(ULONG) CShellExt::Release()
554 {
555 
556     if (InterlockedDecrement((LPLONG)&m_cRef))
557 	return m_cRef;
558 
559     delete this;
560 
561     return 0L;
562 }
563 
564 
565 //
566 //  FUNCTION: CShellExt::Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY)
567 //
568 //  PURPOSE: Called by the shell when initializing a context menu or property
569 //	     sheet extension.
570 //
571 //  PARAMETERS:
572 //    pIDFolder - Specifies the parent folder
573 //    pDataObj  - Specifies the set of items selected in that folder.
574 //    hRegKey   - Specifies the type of the focused item in the selection.
575 //
576 //  RETURN VALUE:
577 //
578 //    NOERROR in all cases.
579 //
580 //  COMMENTS:   Note that at the time this function is called, we don't know
581 //		(or care) what type of shell extension is being initialized.
582 //		It could be a context menu or a property sheet.
583 //
584 
585 STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST  /* pIDFolder */,
586 				   LPDATAOBJECT pDataObj,
587 				   HKEY  /* hRegKey */)
588 {
589     // Initialize can be called more than once
590     if (m_pDataObj)
591 	m_pDataObj->Release();
592 
593     // duplicate the object pointer and registry handle
594 
595     if (pDataObj)
596     {
597 	m_pDataObj = pDataObj;
598 	pDataObj->AddRef();
599     }
600 
601     return NOERROR;
602 }
603 
604 
605 //
606 //  FUNCTION: CShellExt::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
607 //
608 //  PURPOSE: Called by the shell just before the context menu is displayed.
609 //	     This is where you add your specific menu items.
610 //
611 //  PARAMETERS:
612 //    hMenu      - Handle to the context menu
613 //    indexMenu  - Index of where to begin inserting menu items
614 //    idCmdFirst - Lowest value for new menu ID's
615 //    idCmtLast  - Highest value for new menu ID's
616 //    uFlags     - Specifies the context of the menu event
617 //
618 //  RETURN VALUE:
619 //
620 //
621 //  COMMENTS:
622 //
623 
624 STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
625 					 UINT indexMenu,
626 					 UINT idCmdFirst,
627 					 UINT  /* idCmdLast */,
628 					 UINT  /* uFlags */)
629 {
630     UINT idCmd = idCmdFirst;
631 
632     hres = m_pDataObj->GetData(&fmte, &medium);
633     if (medium.hGlobal)
634 	cbFiles = DragQueryFile((HDROP)medium.hGlobal, (UINT)-1, 0, 0);
635 
636     // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
637 
638     // Initialize m_cntOfHWnd to 0
639     m_cntOfHWnd = 0;
640 
641     HKEY keyhandle;
642     bool showExisting = true;
643     bool showIcons = true;
644 
645     // Check whether "Edit with existing Vim" entries are disabled.
646     if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
647 				       KEY_READ, &keyhandle) == ERROR_SUCCESS)
648     {
649 	if (RegQueryValueEx(keyhandle, "DisableEditWithExisting", 0, NULL,
650 						 NULL, NULL) == ERROR_SUCCESS)
651 	    showExisting = false;
652 	if (RegQueryValueEx(keyhandle, "DisableContextMenuIcons", 0, NULL,
653 						 NULL, NULL) == ERROR_SUCCESS)
654 	    showIcons = false;
655 	RegCloseKey(keyhandle);
656     }
657 
658     // Retrieve all the vim instances, unless disabled.
659     if (showExisting)
660 	EnumWindows(EnumWindowsProc, (LPARAM)this);
661 
662     MENUITEMINFO mii = { sizeof(MENUITEMINFO) };
663     mii.fMask = MIIM_STRING | MIIM_ID;
664     if (showIcons)
665     {
666 	mii.fMask |= MIIM_BITMAP;
667 	mii.hbmpItem = m_hVimIconBitmap;
668     }
669 
670     if (cbFiles > 1)
671     {
672 	mii.wID = idCmd++;
673 	mii.dwTypeData = _("Edit with &multiple Vims");
674 	mii.cch = lstrlen(mii.dwTypeData);
675 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
676 
677 	mii.wID = idCmd++;
678 	mii.dwTypeData = _("Edit with single &Vim");
679 	mii.cch = lstrlen(mii.dwTypeData);
680 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
681 
682 	if (cbFiles <= 4)
683 	{
684 	    // Can edit up to 4 files in diff mode
685 	    mii.wID = idCmd++;
686 	    mii.dwTypeData = _("Diff with Vim");
687 	    mii.cch = lstrlen(mii.dwTypeData);
688 	    InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
689 	    m_edit_existing_off = 3;
690 	}
691 	else
692 	    m_edit_existing_off = 2;
693 
694     }
695     else
696     {
697 	mii.wID = idCmd++;
698 	mii.dwTypeData = _("Edit with &Vim");
699 	mii.cch = lstrlen(mii.dwTypeData);
700 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
701 	m_edit_existing_off = 1;
702     }
703 
704     HMENU hSubMenu = NULL;
705     if (m_cntOfHWnd > 1)
706     {
707 	hSubMenu = CreatePopupMenu();
708 	mii.fMask |= MIIM_SUBMENU;
709 	mii.wID = idCmd;
710 	mii.dwTypeData = _("Edit with existing Vim");
711 	mii.cch = lstrlen(mii.dwTypeData);
712 	mii.hSubMenu = hSubMenu;
713 	InsertMenuItem(hMenu, indexMenu++, TRUE, &mii);
714 	mii.fMask = mii.fMask & ~MIIM_SUBMENU;
715 	mii.hSubMenu = NULL;
716     }
717     // Now display all the vim instances
718     for (int i = 0; i < m_cntOfHWnd; i++)
719     {
720 	char title[BUFSIZE];
721 	char temp[BUFSIZE];
722 	int index;
723 	HMENU hmenu;
724 
725 	// Obtain window title, continue if can not
726 	if (GetWindowText(m_hWnd[i], title, BUFSIZE - 1) == 0)
727 	    continue;
728 	// Truncate the title before the path, keep the file name
729 	char *pos = strchr(title, '(');
730 	if (pos != NULL)
731 	{
732 	    if (pos > title && pos[-1] == ' ')
733 		--pos;
734 	    *pos = 0;
735 	}
736 	// Now concatenate
737 	if (m_cntOfHWnd > 1)
738 	    temp[0] = '\0';
739 	else
740 	{
741 	    strncpy(temp, _("Edit with existing Vim - "), BUFSIZE - 1);
742 	    temp[BUFSIZE - 1] = '\0';
743 	}
744 	strncat(temp, title, BUFSIZE - 1 - strlen(temp));
745 	temp[BUFSIZE - 1] = '\0';
746 
747 	mii.wID = idCmd++;
748 	mii.dwTypeData = temp;
749 	mii.cch = lstrlen(mii.dwTypeData);
750 	if (m_cntOfHWnd > 1)
751 	{
752 	    hmenu = hSubMenu;
753 	    index = i;
754 	}
755 	else
756 	{
757 	    hmenu = hMenu;
758 	    index = indexMenu++;
759 	}
760 	InsertMenuItem(hmenu, index, TRUE, &mii);
761     }
762     // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
763 
764     // Must return number of menu items we added.
765     return ResultFromShort(idCmd-idCmdFirst);
766 }
767 
768 //
769 //  FUNCTION: CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO)
770 //
771 //  PURPOSE: Called by the shell after the user has selected on of the
772 //	     menu items that was added in QueryContextMenu().
773 //
774 //  PARAMETERS:
775 //    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
776 //
777 //  RETURN VALUE:
778 //
779 //
780 //  COMMENTS:
781 //
782 
783 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
784 {
785     HRESULT hr = E_INVALIDARG;
786 
787     // If HIWORD(lpcmi->lpVerb) then we have been called programmatically
788     // and lpVerb is a command that should be invoked.  Otherwise, the shell
789     // has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has
790     // selected.  Actually, it's (menu ID - idCmdFirst) from QueryContextMenu().
791     if (!HIWORD(lpcmi->lpVerb))
792     {
793 	UINT idCmd = LOWORD(lpcmi->lpVerb);
794 
795 	if (idCmd >= m_edit_existing_off)
796 	{
797 	    // Existing with vim instance
798 	    hr = PushToWindow(lpcmi->hwnd,
799 		    lpcmi->lpDirectory,
800 		    lpcmi->lpVerb,
801 		    lpcmi->lpParameters,
802 		    lpcmi->nShow,
803 		    idCmd - m_edit_existing_off);
804 	}
805 	else
806 	{
807 	    switch (idCmd)
808 	    {
809 		case 0:
810 		    hr = InvokeGvim(lpcmi->hwnd,
811 			    lpcmi->lpDirectory,
812 			    lpcmi->lpVerb,
813 			    lpcmi->lpParameters,
814 			    lpcmi->nShow);
815 		    break;
816 		case 1:
817 		    hr = InvokeSingleGvim(lpcmi->hwnd,
818 			    lpcmi->lpDirectory,
819 			    lpcmi->lpVerb,
820 			    lpcmi->lpParameters,
821 			    lpcmi->nShow,
822 			    0);
823 		    break;
824 		case 2:
825 		    hr = InvokeSingleGvim(lpcmi->hwnd,
826 			    lpcmi->lpDirectory,
827 			    lpcmi->lpVerb,
828 			    lpcmi->lpParameters,
829 			    lpcmi->nShow,
830 			    1);
831 		    break;
832 	    }
833 	}
834     }
835     return hr;
836 }
837 
838 STDMETHODIMP CShellExt::PushToWindow(HWND  /* hParent */,
839 				   LPCSTR  /* pszWorkingDir */,
840 				   LPCSTR  /* pszCmd */,
841 				   LPCSTR  /* pszParam */,
842 				   int  /* iShowCmd */,
843 				   int idHWnd)
844 {
845     HWND hWnd = m_hWnd[idHWnd];
846 
847     // Show and bring vim instance to foreground
848     if (IsIconic(hWnd) != 0)
849 	ShowWindow(hWnd, SW_RESTORE);
850     else
851 	ShowWindow(hWnd, SW_SHOW);
852     SetForegroundWindow(hWnd);
853 
854     // Post the selected files to the vim instance
855     PostMessage(hWnd, WM_DROPFILES, (WPARAM)medium.hGlobal, 0);
856 
857     return NOERROR;
858 }
859 
860 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR  /* idCmd */,
861 					 UINT uFlags,
862 					 UINT FAR * /* reserved */,
863 					 LPSTR pszName,
864 					 UINT cchMax)
865 {
866     if (uFlags == GCS_HELPTEXT && cchMax > 35)
867 	lstrcpy(pszName, _("Edits the selected file(s) with Vim"));
868 
869     return NOERROR;
870 }
871 
872 BOOL CALLBACK CShellExt::EnumWindowsProc(HWND hWnd, LPARAM lParam)
873 {
874     char temp[BUFSIZE];
875 
876     // First do a bunch of check
877     // No invisible window
878     if (!IsWindowVisible(hWnd)) return TRUE;
879     // No child window ???
880     // if (GetParent(hWnd)) return TRUE;
881     // Class name should be Vim, if failed to get class name, return
882     if (GetClassName(hWnd, temp, sizeof(temp)) == 0)
883 	return TRUE;
884     // Compare class name to that of vim, if not, return
885     if (_strnicmp(temp, "vim", sizeof("vim")) != 0)
886 	return TRUE;
887     // First check if the number of vim instance exceeds MAX_HWND
888     CShellExt *cs = (CShellExt*) lParam;
889     if (cs->m_cntOfHWnd >= MAX_HWND) return TRUE;
890     // Now we get the vim window, put it into some kind of array
891     cs->m_hWnd[cs->m_cntOfHWnd] = hWnd;
892     cs->m_cntOfHWnd ++;
893 
894     return TRUE; // continue enumeration (otherwise this would be false)
895 }
896 
897 BOOL CShellExt::LoadMenuIcon()
898 {
899 	char vimExeFile[BUFSIZE];
900 	getGvimName(vimExeFile, 1);
901 	if (vimExeFile[0] == '\0')
902 		return FALSE;
903 	HICON hVimIcon;
904 	if (ExtractIconEx(vimExeFile, 0, NULL, &hVimIcon, 1) == 0)
905 		return FALSE;
906 	m_hVimIconBitmap = IconToBitmap(hVimIcon,
907 		GetSysColorBrush(COLOR_MENU),
908 		GetSystemMetrics(SM_CXSMICON),
909 		GetSystemMetrics(SM_CYSMICON));
910 	return TRUE;
911 }
912 
913     static char *
914 searchpath(char *name)
915 {
916     static char widename[2 * BUFSIZE];
917     static char location[2 * BUFSIZE + 2];
918 
919     // There appears to be a bug in FindExecutableA() on Windows NT.
920     // Use FindExecutableW() instead...
921     MultiByteToWideChar(CP_ACP, 0, (LPCSTR)name, -1,
922 	    (LPWSTR)widename, BUFSIZE);
923     if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
924 		(LPWSTR)location) > (HINSTANCE)32)
925     {
926 	WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
927 		(LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
928 	return widename;
929     }
930     return (char *)"";
931 }
932 
933 STDMETHODIMP CShellExt::InvokeGvim(HWND hParent,
934 				   LPCSTR  /* pszWorkingDir */,
935 				   LPCSTR  /* pszCmd */,
936 				   LPCSTR  /* pszParam */,
937 				   int  /* iShowCmd */)
938 {
939     wchar_t m_szFileUserClickedOn[BUFSIZE];
940     wchar_t cmdStrW[BUFSIZE];
941     UINT i;
942 
943     for (i = 0; i < cbFiles; i++)
944     {
945 	DragQueryFileW((HDROP)medium.hGlobal,
946 		i,
947 		m_szFileUserClickedOn,
948 		sizeof(m_szFileUserClickedOn));
949 
950 	getGvimInvocationW(cmdStrW);
951 	wcscat(cmdStrW, L" \"");
952 
953 	if ((wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 2) < BUFSIZE)
954 	{
955 	    wcscat(cmdStrW, m_szFileUserClickedOn);
956 	    wcscat(cmdStrW, L"\"");
957 
958 	    STARTUPINFOW si;
959 	    PROCESS_INFORMATION pi;
960 
961 	    ZeroMemory(&si, sizeof(si));
962 	    si.cb = sizeof(si);
963 
964 	    // Start the child process.
965 	    if (!CreateProcessW(NULL,	// No module name (use command line).
966 			cmdStrW,	// Command line.
967 			NULL,		// Process handle not inheritable.
968 			NULL,		// Thread handle not inheritable.
969 			FALSE,		// Set handle inheritance to FALSE.
970 			oldenv == NULL ? 0 : CREATE_UNICODE_ENVIRONMENT,
971 			oldenv,		// Use unmodified environment block.
972 			NULL,		// Use parent's starting directory.
973 			&si,		// Pointer to STARTUPINFO structure.
974 			&pi)		// Pointer to PROCESS_INFORMATION structure.
975 	       )
976 	    {
977 		MessageBox(
978 		    hParent,
979 		    _("Error creating process: Check if gvim is in your path!"),
980 		    _("gvimext.dll error"),
981 		    MB_OK);
982 	    }
983 	    else
984 	    {
985 		CloseHandle( pi.hProcess );
986 		CloseHandle( pi.hThread );
987 	    }
988 	}
989 	else
990 	{
991 	    MessageBox(
992 		hParent,
993 		_("Path length too long!"),
994 		_("gvimext.dll error"),
995 		MB_OK);
996 	}
997     }
998 
999     return NOERROR;
1000 }
1001 
1002 
1003 STDMETHODIMP CShellExt::InvokeSingleGvim(HWND hParent,
1004 				   LPCSTR  /* pszWorkingDir */,
1005 				   LPCSTR  /* pszCmd */,
1006 				   LPCSTR  /* pszParam */,
1007 				   int  /* iShowCmd */,
1008 				   int useDiff)
1009 {
1010     wchar_t	m_szFileUserClickedOn[BUFSIZE];
1011     wchar_t	*cmdStrW;
1012     size_t	cmdlen;
1013     size_t	len;
1014     UINT i;
1015 
1016     cmdlen = BUFSIZE;
1017     cmdStrW  = (wchar_t *) malloc(cmdlen * sizeof(wchar_t));
1018     if (cmdStrW == NULL)
1019 	return E_FAIL;
1020     getGvimInvocationW(cmdStrW);
1021 
1022     if (useDiff)
1023 	wcscat(cmdStrW, L" -d");
1024     for (i = 0; i < cbFiles; i++)
1025     {
1026 	DragQueryFileW((HDROP)medium.hGlobal,
1027 		i,
1028 		m_szFileUserClickedOn,
1029 		sizeof(m_szFileUserClickedOn));
1030 
1031 	len = wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 4;
1032 	if (len > cmdlen)
1033 	{
1034 	    cmdlen = len + BUFSIZE;
1035 	    wchar_t *cmdStrW_new = (wchar_t *)realloc(cmdStrW, cmdlen * sizeof(wchar_t));
1036 	    if (cmdStrW_new == NULL)
1037 	    {
1038 		free(cmdStrW);
1039 		return E_FAIL;
1040 	    }
1041 	    cmdStrW = cmdStrW_new;
1042 	}
1043 	wcscat(cmdStrW, L" \"");
1044 	wcscat(cmdStrW, m_szFileUserClickedOn);
1045 	wcscat(cmdStrW, L"\"");
1046     }
1047 
1048     STARTUPINFOW si;
1049     PROCESS_INFORMATION pi;
1050 
1051     ZeroMemory(&si, sizeof(si));
1052     si.cb = sizeof(si);
1053 
1054     // Start the child process.
1055     if (!CreateProcessW(NULL,	// No module name (use command line).
1056 		cmdStrW,	// Command line.
1057 		NULL,		// Process handle not inheritable.
1058 		NULL,		// Thread handle not inheritable.
1059 		FALSE,		// Set handle inheritance to FALSE.
1060 		oldenv == NULL ? 0 : CREATE_UNICODE_ENVIRONMENT,
1061 		oldenv,		// Use unmodified environment block.
1062 		NULL,		// Use parent's starting directory.
1063 		&si,		// Pointer to STARTUPINFO structure.
1064 		&pi)		// Pointer to PROCESS_INFORMATION structure.
1065        )
1066     {
1067 	MessageBox(
1068 	    hParent,
1069 	    _("Error creating process: Check if gvim is in your path!"),
1070 	    _("gvimext.dll error"),
1071 	    MB_OK);
1072     }
1073     else
1074     {
1075 	CloseHandle(pi.hProcess);
1076 	CloseHandle(pi.hThread);
1077     }
1078     free(cmdStrW);
1079 
1080     return NOERROR;
1081 }
1082