xref: /vim-8.2.3635/src/GvimExt/gvimext.cpp (revision a93fa7ee)
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("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("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 	{"gettext",		(FARPROC*)&dyn_libintl_gettext},
156 	{"textdomain",		(FARPROC*)&dyn_libintl_textdomain},
157 	{"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     // Retieve 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 	strncat(temp, title, BUFSIZE - 1);
639 	InsertMenu(hMenu,
640 		indexMenu++,
641 		MF_STRING|MF_BYPOSITION,
642 		idCmd++,
643 		temp);
644     }
645     // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
646 
647     // Must return number of menu items we added.
648     return ResultFromShort(idCmd-idCmdFirst);
649 }
650 
651 //
652 //  FUNCTION: CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO)
653 //
654 //  PURPOSE: Called by the shell after the user has selected on of the
655 //	     menu items that was added in QueryContextMenu().
656 //
657 //  PARAMETERS:
658 //    lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
659 //
660 //  RETURN VALUE:
661 //
662 //
663 //  COMMENTS:
664 //
665 
666 STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
667 {
668     HRESULT hr = E_INVALIDARG;
669 
670     // If HIWORD(lpcmi->lpVerb) then we have been called programmatically
671     // and lpVerb is a command that should be invoked.  Otherwise, the shell
672     // has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has
673     // selected.  Actually, it's (menu ID - idCmdFirst) from QueryContextMenu().
674     if (!HIWORD(lpcmi->lpVerb))
675     {
676 	UINT idCmd = LOWORD(lpcmi->lpVerb);
677 
678 	if (idCmd >= m_edit_existing_off)
679 	{
680 	    // Existing with vim instance
681 	    hr = PushToWindow(lpcmi->hwnd,
682 		    lpcmi->lpDirectory,
683 		    lpcmi->lpVerb,
684 		    lpcmi->lpParameters,
685 		    lpcmi->nShow,
686 		    idCmd - m_edit_existing_off);
687 	}
688 	else
689 	{
690 	    switch (idCmd)
691 	    {
692 		case 0:
693 		    hr = InvokeGvim(lpcmi->hwnd,
694 			    lpcmi->lpDirectory,
695 			    lpcmi->lpVerb,
696 			    lpcmi->lpParameters,
697 			    lpcmi->nShow);
698 		    break;
699 		case 1:
700 		    hr = InvokeSingleGvim(lpcmi->hwnd,
701 			    lpcmi->lpDirectory,
702 			    lpcmi->lpVerb,
703 			    lpcmi->lpParameters,
704 			    lpcmi->nShow,
705 			    0);
706 		    break;
707 		case 2:
708 		    hr = InvokeSingleGvim(lpcmi->hwnd,
709 			    lpcmi->lpDirectory,
710 			    lpcmi->lpVerb,
711 			    lpcmi->lpParameters,
712 			    lpcmi->nShow,
713 			    1);
714 		    break;
715 	    }
716 	}
717     }
718     return hr;
719 }
720 
721 STDMETHODIMP CShellExt::PushToWindow(HWND hParent,
722 				   LPCSTR pszWorkingDir,
723 				   LPCSTR pszCmd,
724 				   LPCSTR pszParam,
725 				   int iShowCmd,
726 				   int idHWnd)
727 {
728     HWND hWnd = m_hWnd[idHWnd];
729 
730     // Show and bring vim instance to foreground
731     if (IsIconic(hWnd) != 0)
732 	ShowWindow(hWnd, SW_RESTORE);
733     else
734 	ShowWindow(hWnd, SW_SHOW);
735     SetForegroundWindow(hWnd);
736 
737     // Post the selected files to the vim instance
738     PostMessage(hWnd, WM_DROPFILES, (WPARAM)medium.hGlobal, 0);
739 
740     return NOERROR;
741 }
742 
743 STDMETHODIMP CShellExt::GetCommandString(UINT_PTR idCmd,
744 					 UINT uFlags,
745 					 UINT FAR *reserved,
746 					 LPSTR pszName,
747 					 UINT cchMax)
748 {
749     if (uFlags == GCS_HELPTEXT && cchMax > 35)
750 	lstrcpy(pszName, _("Edits the selected file(s) with Vim"));
751 
752     return NOERROR;
753 }
754 
755 BOOL CALLBACK CShellExt::EnumWindowsProc(HWND hWnd, LPARAM lParam)
756 {
757     char temp[BUFSIZE];
758 
759     // First do a bunch of check
760     // No invisible window
761     if (!IsWindowVisible(hWnd)) return TRUE;
762     // No child window ???
763     // if (GetParent(hWnd)) return TRUE;
764     // Class name should be Vim, if failed to get class name, return
765     if (GetClassName(hWnd, temp, sizeof(temp)) == 0)
766 	return TRUE;
767     // Compare class name to that of vim, if not, return
768     if (_strnicmp(temp, "vim", sizeof("vim")) != 0)
769 	return TRUE;
770     // First check if the number of vim instance exceeds MAX_HWND
771     CShellExt *cs = (CShellExt*) lParam;
772     if (cs->m_cntOfHWnd >= MAX_HWND) return TRUE;
773     // Now we get the vim window, put it into some kind of array
774     cs->m_hWnd[cs->m_cntOfHWnd] = hWnd;
775     cs->m_cntOfHWnd ++;
776 
777     return TRUE; // continue enumeration (otherwise this would be false)
778 }
779 
780 #ifdef WIN32
781 // This symbol is not defined in older versions of the SDK or Visual C++.
782 
783 #ifndef VER_PLATFORM_WIN32_WINDOWS
784 # define VER_PLATFORM_WIN32_WINDOWS 1
785 #endif
786 
787 static DWORD g_PlatformId;
788 
789 //
790 // Set g_PlatformId to VER_PLATFORM_WIN32_NT (NT) or
791 // VER_PLATFORM_WIN32_WINDOWS (Win95).
792 //
793     static void
794 PlatformId(void)
795 {
796     static int done = FALSE;
797 
798     if (!done)
799     {
800 	OSVERSIONINFO ovi;
801 
802 	ovi.dwOSVersionInfoSize = sizeof(ovi);
803 	GetVersionEx(&ovi);
804 
805 	g_PlatformId = ovi.dwPlatformId;
806 	done = TRUE;
807     }
808 }
809 
810 # ifndef __BORLANDC__
811     static char *
812 searchpath(char *name)
813 {
814     static char widename[2 * BUFSIZE];
815     static char location[2 * BUFSIZE + 2];
816 
817     // There appears to be a bug in FindExecutableA() on Windows NT.
818     // Use FindExecutableW() instead...
819     PlatformId();
820     if (g_PlatformId == VER_PLATFORM_WIN32_NT)
821     {
822 	MultiByteToWideChar(CP_ACP, 0, (LPCTSTR)name, -1,
823 		(LPWSTR)widename, BUFSIZE);
824 	if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
825 		    (LPWSTR)location) > (HINSTANCE)32)
826 	{
827 	    WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
828 		    (LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
829 	    return widename;
830 	}
831     }
832     else
833     {
834 	if (FindExecutableA((LPCTSTR)name, (LPCTSTR)"",
835 		    (LPTSTR)location) > (HINSTANCE)32)
836 	    return location;
837     }
838     return "";
839 }
840 # endif
841 #endif
842 
843 STDMETHODIMP CShellExt::InvokeGvim(HWND hParent,
844 				   LPCSTR pszWorkingDir,
845 				   LPCSTR pszCmd,
846 				   LPCSTR pszParam,
847 				   int iShowCmd)
848 {
849     char m_szFileUserClickedOn[BUFSIZE];
850     char cmdStr[BUFSIZE];
851     UINT i;
852 
853     for (i = 0; i < cbFiles; i++)
854     {
855 	DragQueryFile((HDROP)medium.hGlobal,
856 		i,
857 		m_szFileUserClickedOn,
858 		sizeof(m_szFileUserClickedOn));
859 
860 	getGvimName(cmdStr, 0);
861 	strcat(cmdStr, " \"");
862 
863 	if ((strlen(cmdStr) + strlen(m_szFileUserClickedOn) + 2) < BUFSIZE)
864 	{
865 	    strcat(cmdStr, m_szFileUserClickedOn);
866 	    strcat(cmdStr, "\"");
867 
868 	    STARTUPINFO si;
869 	    PROCESS_INFORMATION pi;
870 
871 	    ZeroMemory(&si, sizeof(si));
872 	    si.cb = sizeof(si);
873 
874 	    // Start the child process.
875 	    if (!CreateProcess(NULL,	// No module name (use command line).
876 			cmdStr,		// Command line.
877 			NULL,		// Process handle not inheritable.
878 			NULL,		// Thread handle not inheritable.
879 			FALSE,		// Set handle inheritance to FALSE.
880 			0,		// No creation flags.
881 			NULL,		// Use parent's environment block.
882 			NULL,		// Use parent's starting directory.
883 			&si,		// Pointer to STARTUPINFO structure.
884 			&pi)		// Pointer to PROCESS_INFORMATION structure.
885 	       )
886 	    {
887 		MessageBox(
888 		    hParent,
889 		    _("Error creating process: Check if gvim is in your path!"),
890 		    _("gvimext.dll error"),
891 		    MB_OK);
892 	    }
893 	    else
894 	    {
895 		CloseHandle( pi.hProcess );
896 		CloseHandle( pi.hThread );
897 	    }
898 	}
899 	else
900 	{
901 	    MessageBox(
902 		hParent,
903 		_("Path length too long!"),
904 		_("gvimext.dll error"),
905 		MB_OK);
906 	}
907     }
908 
909     return NOERROR;
910 }
911 
912 
913 STDMETHODIMP CShellExt::InvokeSingleGvim(HWND hParent,
914 				   LPCSTR pszWorkingDir,
915 				   LPCSTR pszCmd,
916 				   LPCSTR pszParam,
917 				   int iShowCmd,
918 				   int useDiff)
919 {
920     char	m_szFileUserClickedOn[BUFSIZE];
921     char	*cmdStr;
922     size_t	cmdlen;
923     size_t	len;
924     UINT i;
925 
926     cmdlen = BUFSIZE;
927     cmdStr = (char *)malloc(cmdlen);
928     getGvimName(cmdStr, 0);
929     if (useDiff)
930 	strcat(cmdStr, " -d");
931     for (i = 0; i < cbFiles; i++)
932     {
933 	DragQueryFile((HDROP)medium.hGlobal,
934 		i,
935 		m_szFileUserClickedOn,
936 		sizeof(m_szFileUserClickedOn));
937 
938 	len = strlen(cmdStr) + strlen(m_szFileUserClickedOn) + 4;
939 	if (len > cmdlen)
940 	{
941 	    cmdlen = len + BUFSIZE;
942 	    cmdStr = (char *)realloc(cmdStr, cmdlen);
943 	}
944 	strcat(cmdStr, " \"");
945 	strcat(cmdStr, m_szFileUserClickedOn);
946 	strcat(cmdStr, "\"");
947     }
948 
949     STARTUPINFO si;
950     PROCESS_INFORMATION pi;
951 
952     ZeroMemory(&si, sizeof(si));
953     si.cb = sizeof(si);
954 
955     // Start the child process.
956     if (!CreateProcess(NULL,	// No module name (use command line).
957 		cmdStr,		// Command line.
958 		NULL,		// Process handle not inheritable.
959 		NULL,		// Thread handle not inheritable.
960 		FALSE,		// Set handle inheritance to FALSE.
961 		0,		// No creation flags.
962 		NULL,		// Use parent's environment block.
963 		NULL,		// Use parent's starting directory.
964 		&si,		// Pointer to STARTUPINFO structure.
965 		&pi)		// Pointer to PROCESS_INFORMATION structure.
966        )
967     {
968 	MessageBox(
969 	    hParent,
970 	    _("Error creating process: Check if gvim is in your path!"),
971 	    _("gvimext.dll error"),
972 	    MB_OK);
973     }
974     else
975     {
976 	CloseHandle(pi.hProcess);
977 	CloseHandle(pi.hThread);
978     }
979 
980     free(cmdStr);
981 
982     return NOERROR;
983 }
984