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