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