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