summaryrefslogtreecommitdiffstats
path: root/src/GvimExt/gvimext.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-10 20:09:20 +0000
commit029f72b1a93430b24b88eb3a72c6114d9f149737 (patch)
tree765d5c2041967f9c6fef195fe343d9234a030e90 /src/GvimExt/gvimext.cpp
parentInitial commit. (diff)
downloadvim-029f72b1a93430b24b88eb3a72c6114d9f149737.tar.xz
vim-029f72b1a93430b24b88eb3a72c6114d9f149737.zip
Adding upstream version 2:9.1.0016.upstream/2%9.1.0016
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/GvimExt/gvimext.cpp')
-rw-r--r--src/GvimExt/gvimext.cpp1050
1 files changed, 1050 insertions, 0 deletions
diff --git a/src/GvimExt/gvimext.cpp b/src/GvimExt/gvimext.cpp
new file mode 100644
index 0000000..e58b142
--- /dev/null
+++ b/src/GvimExt/gvimext.cpp
@@ -0,0 +1,1050 @@
+/* vi:set ts=8 sts=4 sw=4:
+ *
+ * VIM - Vi IMproved gvimext by Tianmiao Hu
+ *
+ * Do ":help uganda" in Vim to read copying and usage conditions.
+ * Do ":help credits" in Vim to see a list of people who contributed.
+ */
+
+/*
+ * gvimext is a DLL which is used for the "Edit with Vim" context menu
+ * extension. It implements a MS defined interface with the Shell.
+ *
+ * If you have any questions or any suggestions concerning gvimext, please
+ * contact Tianmiao Hu: tianmiao@acm.org.
+ */
+
+#include "gvimext.h"
+
+static char *searchpath(char *name);
+
+// Always get an error while putting the following stuff to the
+// gvimext.h file as class protected variables, give up and
+// declare them as global stuff
+FORMATETC fmte = {CF_HDROP,
+ (DVTARGETDEVICE FAR *)NULL,
+ DVASPECT_CONTENT,
+ -1,
+ TYMED_HGLOBAL
+ };
+STGMEDIUM medium;
+HRESULT hres = 0;
+UINT cbFiles = 0;
+
+/* The buffers size used to be MAX_PATH (260 bytes), but that's not always
+ * enough */
+#define BUFSIZE 1100
+
+// The "Edit with Vim" shell extension provides these choices when
+// a new instance of Gvim is selected:
+// - use tabpages
+// - enable diff mode
+// - none of the above
+#define EDIT_WITH_VIM_USE_TABPAGES (2)
+#define EDIT_WITH_VIM_IN_DIFF_MODE (1)
+#define EDIT_WITH_VIM_NO_OPTIONS (0)
+
+//
+// Get the name of the Gvim executable to use, with the path.
+// When "runtime" is non-zero, we were called to find the runtime directory.
+// Returns the path in name[BUFSIZE]. It's empty when it fails.
+//
+ static void
+getGvimName(char *name, int runtime)
+{
+ HKEY keyhandle;
+ DWORD hlen;
+
+ // Get the location of gvim from the registry.
+ name[0] = 0;
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
+ KEY_READ, &keyhandle) == ERROR_SUCCESS)
+ {
+ hlen = BUFSIZE;
+ if (RegQueryValueEx(keyhandle, "path", 0, NULL, (BYTE *)name, &hlen)
+ != ERROR_SUCCESS)
+ name[0] = 0;
+ else
+ name[hlen] = 0;
+ RegCloseKey(keyhandle);
+ }
+
+ // Registry didn't work, use the search path.
+ if (name[0] == 0)
+ strcpy(name, searchpath((char *)"gvim.exe"));
+
+ if (!runtime)
+ {
+ // Only when looking for the executable, not the runtime dir, we can
+ // search for the batch file or a name without a path.
+ if (name[0] == 0)
+ strcpy(name, searchpath((char *)"gvim.bat"));
+ if (name[0] == 0)
+ strcpy(name, "gvim"); // finds gvim.bat or gvim.exe
+ }
+}
+
+ static void
+getGvimInvocation(char *name, int runtime)
+{
+ getGvimName(name, runtime);
+ // avoid that Vim tries to expand wildcards in the file names
+ strcat(name, " --literal");
+}
+
+ static void
+getGvimInvocationW(wchar_t *nameW)
+{
+ char *name;
+
+ name = (char *)malloc(BUFSIZE);
+ getGvimInvocation(name, 0);
+ mbstowcs(nameW, name, BUFSIZE);
+ free(name);
+}
+
+//
+// Get the Vim runtime directory into buf[BUFSIZE].
+// The result is empty when it failed.
+// When it works, the path ends in a slash or backslash.
+//
+ static void
+getRuntimeDir(char *buf)
+{
+ int idx;
+
+ getGvimName(buf, 1);
+ if (buf[0] != 0)
+ {
+ // When no path found, use the search path to expand it.
+ if (strchr(buf, '/') == NULL && strchr(buf, '\\') == NULL)
+ strcpy(buf, searchpath(buf));
+
+ // remove "gvim.exe" from the end
+ for (idx = (int)strlen(buf) - 1; idx >= 0; idx--)
+ if (buf[idx] == '\\' || buf[idx] == '/')
+ {
+ buf[idx + 1] = 0;
+ break;
+ }
+ }
+}
+
+ WCHAR *
+utf8_to_utf16(const char *s)
+{
+ int size = MultiByteToWideChar(CP_UTF8, 0, s, -1, NULL, 0);
+ WCHAR *buf = (WCHAR *)malloc(size * sizeof(WCHAR));
+ MultiByteToWideChar(CP_UTF8, 0, s, -1, buf, size);
+ return buf;
+}
+
+ HBITMAP
+IconToBitmap(HICON hIcon, HBRUSH hBackground, int width, int height)
+{
+ HDC hDC = GetDC(NULL);
+ HDC hMemDC = CreateCompatibleDC(hDC);
+ HBITMAP hMemBmp = CreateCompatibleBitmap(hDC, width, height);
+ HBITMAP hResultBmp = NULL;
+ HGDIOBJ hOrgBMP = SelectObject(hMemDC, hMemBmp);
+
+ DrawIconEx(hMemDC, 0, 0, hIcon, width, height, 0, hBackground, DI_NORMAL);
+
+ hResultBmp = hMemBmp;
+ hMemBmp = NULL;
+
+ SelectObject(hMemDC, hOrgBMP);
+ DeleteDC(hMemDC);
+ ReleaseDC(NULL, hDC);
+ DestroyIcon(hIcon);
+ return hResultBmp;
+}
+
+//
+// GETTEXT: translated messages and menu entries
+//
+#ifndef FEAT_GETTEXT
+# define _(x) x
+# define W_impl(x) _wcsdup(L##x)
+# define W(x) W_impl(x)
+# define set_gettext_codeset() NULL
+# define restore_gettext_codeset(x)
+#else
+# define _(x) (*dyn_libintl_gettext)(x)
+# define W(x) utf8_to_utf16(x)
+# define VIMPACKAGE "vim"
+# ifndef GETTEXT_DLL
+# define GETTEXT_DLL "libintl.dll"
+# define GETTEXT_DLL_ALT "libintl-8.dll"
+# endif
+
+// Dummy functions
+static char *null_libintl_gettext(const char *);
+static char *null_libintl_textdomain(const char *);
+static char *null_libintl_bindtextdomain(const char *, const char *);
+static char *null_libintl_bind_textdomain_codeset(const char *, const char *);
+static int dyn_libintl_init(char *dir);
+static void dyn_libintl_end(void);
+
+static HINSTANCE hLibintlDLL = 0;
+static char *(*dyn_libintl_gettext)(const char *) = null_libintl_gettext;
+static char *(*dyn_libintl_textdomain)(const char *) = null_libintl_textdomain;
+static char *(*dyn_libintl_bindtextdomain)(const char *, const char *)
+ = null_libintl_bindtextdomain;
+static char *(*dyn_libintl_bind_textdomain_codeset)(const char *, const char *)
+ = null_libintl_bind_textdomain_codeset;
+
+//
+// Attempt to load libintl.dll. If it doesn't work, use dummy functions.
+// "dir" is the directory where the libintl.dll might be.
+// Return 1 for success, 0 for failure.
+//
+ static int
+dyn_libintl_init(char *dir)
+{
+ int i;
+ static struct
+ {
+ char *name;
+ FARPROC *ptr;
+ } libintl_entry[] =
+ {
+ {(char *)"gettext", (FARPROC*)&dyn_libintl_gettext},
+ {(char *)"textdomain", (FARPROC*)&dyn_libintl_textdomain},
+ {(char *)"bindtextdomain", (FARPROC*)&dyn_libintl_bindtextdomain},
+ {(char *)"bind_textdomain_codeset", (FARPROC*)&dyn_libintl_bind_textdomain_codeset},
+ {NULL, NULL}
+ };
+ DWORD len, len2;
+ LPWSTR buf = NULL;
+ LPWSTR buf2 = NULL;
+
+ // No need to initialize twice.
+ if (hLibintlDLL)
+ return 1;
+
+ // Load gettext library from $VIMRUNTIME\GvimExt{64,32} directory.
+ // Add the directory to $PATH temporarily.
+ len = GetEnvironmentVariableW(L"PATH", NULL, 0);
+ len2 = MAX_PATH + 1 + len;
+ buf = (LPWSTR)malloc(len * sizeof(WCHAR));
+ buf2 = (LPWSTR)malloc(len2 * sizeof(WCHAR));
+ if (buf != NULL && buf2 != NULL)
+ {
+ GetEnvironmentVariableW(L"PATH", buf, len);
+# ifdef _WIN64
+ _snwprintf(buf2, len2, L"%S\\GvimExt64;%s", dir, buf);
+# else
+ _snwprintf(buf2, len2, L"%S\\GvimExt32;%s", dir, buf);
+# endif
+ SetEnvironmentVariableW(L"PATH", buf2);
+ hLibintlDLL = LoadLibrary(GETTEXT_DLL);
+# ifdef GETTEXT_DLL_ALT
+ if (!hLibintlDLL)
+ hLibintlDLL = LoadLibrary(GETTEXT_DLL_ALT);
+# endif
+ SetEnvironmentVariableW(L"PATH", buf);
+ }
+ free(buf);
+ free(buf2);
+ if (!hLibintlDLL)
+ return 0;
+
+ // Get the addresses of the functions we need.
+ for (i = 0; libintl_entry[i].name != NULL
+ && libintl_entry[i].ptr != NULL; ++i)
+ {
+ if ((*libintl_entry[i].ptr = GetProcAddress(hLibintlDLL,
+ libintl_entry[i].name)) == NULL)
+ {
+ dyn_libintl_end();
+ return 0;
+ }
+ }
+ return 1;
+}
+
+ static void
+dyn_libintl_end(void)
+{
+ if (hLibintlDLL)
+ FreeLibrary(hLibintlDLL);
+ hLibintlDLL = NULL;
+ dyn_libintl_gettext = null_libintl_gettext;
+ dyn_libintl_textdomain = null_libintl_textdomain;
+ dyn_libintl_bindtextdomain = null_libintl_bindtextdomain;
+ dyn_libintl_bind_textdomain_codeset = null_libintl_bind_textdomain_codeset;
+}
+
+ static char *
+null_libintl_gettext(const char *msgid)
+{
+ return (char *)msgid;
+}
+
+ static char *
+null_libintl_textdomain(const char * /* domainname */)
+{
+ return NULL;
+}
+
+ static char *
+null_libintl_bindtextdomain(const char * /* domainname */, const char * /* dirname */)
+{
+ return NULL;
+}
+
+ static char *
+null_libintl_bind_textdomain_codeset(const char * /* domainname */, const char * /* codeset */)
+{
+ return NULL;
+}
+
+//
+// Setup for translating strings.
+//
+ static void
+dyn_gettext_load(void)
+{
+ char szBuff[BUFSIZE];
+ DWORD len;
+
+ // Try to locate the runtime files. The path is used to find libintl.dll
+ // and the vim.mo files.
+ getRuntimeDir(szBuff);
+ if (szBuff[0] != 0)
+ {
+ len = (DWORD)strlen(szBuff);
+ if (dyn_libintl_init(szBuff))
+ {
+ strcpy(szBuff + len, "lang");
+
+ (*dyn_libintl_bindtextdomain)(VIMPACKAGE, szBuff);
+ (*dyn_libintl_textdomain)(VIMPACKAGE);
+ }
+ }
+}
+
+ static void
+dyn_gettext_free(void)
+{
+ dyn_libintl_end();
+}
+
+//
+// Use UTF-8 for gettext. Returns previous codeset.
+//
+ static char *
+set_gettext_codeset(void)
+{
+ char *prev = dyn_libintl_bind_textdomain_codeset(VIMPACKAGE, NULL);
+ prev = _strdup((prev != NULL) ? prev : "char");
+ dyn_libintl_bind_textdomain_codeset(VIMPACKAGE, "utf-8");
+
+ return prev;
+}
+
+//
+// Restore previous codeset for gettext.
+//
+ static void
+restore_gettext_codeset(char *prev)
+{
+ dyn_libintl_bind_textdomain_codeset(VIMPACKAGE, prev);
+ free(prev);
+}
+#endif // FEAT_GETTEXT
+
+//
+// Global variables
+//
+UINT g_cRefThisDll = 0; // Reference count of this DLL.
+HINSTANCE g_hmodThisDll = NULL; // Handle to this DLL itself.
+
+
+//---------------------------------------------------------------------------
+// DllMain
+//---------------------------------------------------------------------------
+extern "C" int APIENTRY
+DllMain(HINSTANCE hInstance, DWORD dwReason, LPVOID /* lpReserved */)
+{
+ switch (dwReason)
+ {
+ case DLL_PROCESS_ATTACH:
+ // Extension DLL one-time initialization
+ g_hmodThisDll = hInstance;
+ break;
+
+ case DLL_PROCESS_DETACH:
+ break;
+ }
+
+ return 1; // ok
+}
+
+ static void
+inc_cRefThisDLL()
+{
+#ifdef FEAT_GETTEXT
+ if (g_cRefThisDll == 0)
+ dyn_gettext_load();
+#endif
+ InterlockedIncrement((LPLONG)&g_cRefThisDll);
+}
+
+ static void
+dec_cRefThisDLL()
+{
+#ifdef FEAT_GETTEXT
+ if (InterlockedDecrement((LPLONG)&g_cRefThisDll) == 0)
+ dyn_gettext_free();
+#else
+ InterlockedDecrement((LPLONG)&g_cRefThisDll);
+#endif
+}
+
+//---------------------------------------------------------------------------
+// DllCanUnloadNow
+//---------------------------------------------------------------------------
+
+STDAPI DllCanUnloadNow(void)
+{
+ return (g_cRefThisDll == 0 ? S_OK : S_FALSE);
+}
+
+STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID *ppvOut)
+{
+ *ppvOut = NULL;
+
+ if (IsEqualIID(rclsid, CLSID_ShellExtension))
+ {
+ CShellExtClassFactory *pcf = new CShellExtClassFactory;
+
+ return pcf->QueryInterface(riid, ppvOut);
+ }
+
+ return CLASS_E_CLASSNOTAVAILABLE;
+}
+
+CShellExtClassFactory::CShellExtClassFactory()
+{
+ m_cRef = 0L;
+
+ inc_cRefThisDLL();
+}
+
+CShellExtClassFactory::~CShellExtClassFactory()
+{
+ dec_cRefThisDLL();
+}
+
+STDMETHODIMP CShellExtClassFactory::QueryInterface(REFIID riid,
+ LPVOID FAR *ppv)
+{
+ *ppv = NULL;
+
+ // any interface on this object is the object pointer
+
+ if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IClassFactory))
+ {
+ *ppv = (LPCLASSFACTORY)this;
+
+ AddRef();
+
+ return NOERROR;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) CShellExtClassFactory::AddRef()
+{
+ return InterlockedIncrement((LPLONG)&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) CShellExtClassFactory::Release()
+{
+ if (InterlockedDecrement((LPLONG)&m_cRef))
+ return m_cRef;
+
+ delete this;
+
+ return 0L;
+}
+
+STDMETHODIMP CShellExtClassFactory::CreateInstance(LPUNKNOWN pUnkOuter,
+ REFIID riid,
+ LPVOID *ppvObj)
+{
+ *ppvObj = NULL;
+
+ // Shell extensions typically don't support aggregation (inheritance)
+
+ if (pUnkOuter)
+ return CLASS_E_NOAGGREGATION;
+
+ // Create the main shell extension object. The shell will then call
+ // QueryInterface with IID_IShellExtInit--this is how shell extensions are
+ // initialized.
+
+ LPCSHELLEXT pShellExt = new CShellExt(); // create the CShellExt object
+
+ if (NULL == pShellExt)
+ return E_OUTOFMEMORY;
+
+ return pShellExt->QueryInterface(riid, ppvObj);
+}
+
+
+STDMETHODIMP CShellExtClassFactory::LockServer(BOOL /* fLock */)
+{
+ return NOERROR;
+}
+
+// *********************** CShellExt *************************
+CShellExt::CShellExt()
+{
+ m_cRef = 0L;
+ m_pDataObj = NULL;
+
+ inc_cRefThisDLL();
+
+ LoadMenuIcon();
+}
+
+CShellExt::~CShellExt()
+{
+ if (m_pDataObj)
+ m_pDataObj->Release();
+
+ dec_cRefThisDLL();
+
+ if (m_hVimIconBitmap)
+ DeleteObject(m_hVimIconBitmap);
+}
+
+STDMETHODIMP CShellExt::QueryInterface(REFIID riid, LPVOID FAR *ppv)
+{
+ *ppv = NULL;
+
+ if (IsEqualIID(riid, IID_IShellExtInit) || IsEqualIID(riid, IID_IUnknown))
+ {
+ *ppv = (LPSHELLEXTINIT)this;
+ }
+ else if (IsEqualIID(riid, IID_IContextMenu))
+ {
+ *ppv = (LPCONTEXTMENU)this;
+ }
+
+ if (*ppv)
+ {
+ AddRef();
+
+ return NOERROR;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG) CShellExt::AddRef()
+{
+ return InterlockedIncrement((LPLONG)&m_cRef);
+}
+
+STDMETHODIMP_(ULONG) CShellExt::Release()
+{
+
+ if (InterlockedDecrement((LPLONG)&m_cRef))
+ return m_cRef;
+
+ delete this;
+
+ return 0L;
+}
+
+
+//
+// FUNCTION: CShellExt::Initialize(LPCITEMIDLIST, LPDATAOBJECT, HKEY)
+//
+// PURPOSE: Called by the shell when initializing a context menu or property
+// sheet extension.
+//
+// PARAMETERS:
+// pIDFolder - Specifies the parent folder
+// pDataObj - Specifies the set of items selected in that folder.
+// hRegKey - Specifies the type of the focused item in the selection.
+//
+// RETURN VALUE:
+//
+// NOERROR in all cases.
+//
+// COMMENTS: Note that at the time this function is called, we don't know
+// (or care) what type of shell extension is being initialized.
+// It could be a context menu or a property sheet.
+//
+
+STDMETHODIMP CShellExt::Initialize(LPCITEMIDLIST /* pIDFolder */,
+ LPDATAOBJECT pDataObj,
+ HKEY /* hRegKey */)
+{
+ // Initialize can be called more than once
+ if (m_pDataObj)
+ m_pDataObj->Release();
+
+ // duplicate the object pointer and registry handle
+
+ if (pDataObj)
+ {
+ m_pDataObj = pDataObj;
+ pDataObj->AddRef();
+ }
+
+ return NOERROR;
+}
+
+
+//
+// FUNCTION: CShellExt::QueryContextMenu(HMENU, UINT, UINT, UINT, UINT)
+//
+// PURPOSE: Called by the shell just before the context menu is displayed.
+// This is where you add your specific menu items.
+//
+// PARAMETERS:
+// hMenu - Handle to the context menu
+// indexMenu - Index of where to begin inserting menu items
+// idCmdFirst - Lowest value for new menu ID's
+// idCmtLast - Highest value for new menu ID's
+// uFlags - Specifies the context of the menu event
+//
+// RETURN VALUE:
+//
+//
+// COMMENTS:
+//
+
+STDMETHODIMP CShellExt::QueryContextMenu(HMENU hMenu,
+ UINT indexMenu,
+ UINT idCmdFirst,
+ UINT /* idCmdLast */,
+ UINT /* uFlags */)
+{
+ UINT idCmd = idCmdFirst;
+
+ hres = m_pDataObj->GetData(&fmte, &medium);
+ if (medium.hGlobal)
+ cbFiles = DragQueryFileW((HDROP)medium.hGlobal, (UINT)-1, 0, 0);
+
+ // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
+
+ // Initialize m_cntOfHWnd to 0
+ m_cntOfHWnd = 0;
+
+ HKEY keyhandle;
+ bool showExisting = true;
+ bool showIcons = true;
+
+ // Check whether "Edit with existing Vim" entries are disabled.
+ if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, "Software\\Vim\\Gvim", 0,
+ KEY_READ, &keyhandle) == ERROR_SUCCESS)
+ {
+ if (RegQueryValueEx(keyhandle, "DisableEditWithExisting", 0, NULL,
+ NULL, NULL) == ERROR_SUCCESS)
+ showExisting = false;
+ if (RegQueryValueEx(keyhandle, "DisableContextMenuIcons", 0, NULL,
+ NULL, NULL) == ERROR_SUCCESS)
+ showIcons = false;
+ RegCloseKey(keyhandle);
+ }
+
+ // Use UTF-8 for gettext.
+ char *prev = set_gettext_codeset();
+
+ // Retrieve all the vim instances, unless disabled.
+ if (showExisting)
+ EnumWindows(EnumWindowsProc, (LPARAM)this);
+
+ MENUITEMINFOW mii = { sizeof(MENUITEMINFOW) };
+ mii.fMask = MIIM_STRING | MIIM_ID;
+ if (showIcons)
+ {
+ mii.fMask |= MIIM_BITMAP;
+ mii.hbmpItem = m_hVimIconBitmap;
+ }
+
+ if (cbFiles > 1)
+ {
+ mii.wID = idCmd++;
+ mii.dwTypeData = W(_("Edit with Vim using &tabpages"));
+ mii.cch = wcslen(mii.dwTypeData);
+ InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii);
+ free(mii.dwTypeData);
+
+ mii.wID = idCmd++;
+ mii.dwTypeData = W(_("Edit with single &Vim"));
+ mii.cch = wcslen(mii.dwTypeData);
+ InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii);
+ free(mii.dwTypeData);
+
+ if (cbFiles <= 4)
+ {
+ // Can edit up to 4 files in diff mode
+ mii.wID = idCmd++;
+ mii.dwTypeData = W(_("Diff with Vim"));
+ mii.cch = wcslen(mii.dwTypeData);
+ InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii);
+ free(mii.dwTypeData);
+ m_edit_existing_off = 3;
+ }
+ else
+ m_edit_existing_off = 2;
+
+ }
+ else
+ {
+ mii.wID = idCmd++;
+ mii.dwTypeData = W(_("Edit with &Vim"));
+ mii.cch = wcslen(mii.dwTypeData);
+ InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii);
+ free(mii.dwTypeData);
+ m_edit_existing_off = 1;
+ }
+
+ HMENU hSubMenu = NULL;
+ if (m_cntOfHWnd > 1)
+ {
+ hSubMenu = CreatePopupMenu();
+ mii.fMask |= MIIM_SUBMENU;
+ mii.wID = idCmd;
+ mii.dwTypeData = W(_("Edit with existing Vim"));
+ mii.cch = wcslen(mii.dwTypeData);
+ mii.hSubMenu = hSubMenu;
+ InsertMenuItemW(hMenu, indexMenu++, TRUE, &mii);
+ free(mii.dwTypeData);
+ mii.fMask = mii.fMask & ~MIIM_SUBMENU;
+ mii.hSubMenu = NULL;
+ }
+ // Now display all the vim instances
+ for (int i = 0; i < m_cntOfHWnd; i++)
+ {
+ WCHAR title[BUFSIZE];
+ WCHAR temp[BUFSIZE];
+ int index;
+ HMENU hmenu;
+
+ // Obtain window title, continue if can not
+ if (GetWindowTextW(m_hWnd[i], title, BUFSIZE - 1) == 0)
+ continue;
+ // Truncate the title before the path, keep the file name
+ WCHAR *pos = wcschr(title, L'(');
+ if (pos != NULL)
+ {
+ if (pos > title && pos[-1] == L' ')
+ --pos;
+ *pos = 0;
+ }
+ // Now concatenate
+ if (m_cntOfHWnd > 1)
+ temp[0] = L'\0';
+ else
+ {
+ WCHAR *s = W(_("Edit with existing Vim - "));
+ wcsncpy(temp, s, BUFSIZE - 1);
+ temp[BUFSIZE - 1] = L'\0';
+ free(s);
+ }
+ wcsncat(temp, title, BUFSIZE - 1 - wcslen(temp));
+ temp[BUFSIZE - 1] = L'\0';
+
+ mii.wID = idCmd++;
+ mii.dwTypeData = temp;
+ mii.cch = wcslen(mii.dwTypeData);
+ if (m_cntOfHWnd > 1)
+ {
+ hmenu = hSubMenu;
+ index = i;
+ }
+ else
+ {
+ hmenu = hMenu;
+ index = indexMenu++;
+ }
+ InsertMenuItemW(hmenu, index, TRUE, &mii);
+ }
+ // InsertMenu(hMenu, indexMenu++, MF_SEPARATOR|MF_BYPOSITION, 0, NULL);
+
+ // Restore previous codeset.
+ restore_gettext_codeset(prev);
+
+ // Must return number of menu items we added.
+ return ResultFromShort(idCmd-idCmdFirst);
+}
+
+//
+// FUNCTION: CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO)
+//
+// PURPOSE: Called by the shell after the user has selected on of the
+// menu items that was added in QueryContextMenu().
+//
+// PARAMETERS:
+// lpcmi - Pointer to an CMINVOKECOMMANDINFO structure
+//
+// RETURN VALUE:
+//
+//
+// COMMENTS:
+//
+
+STDMETHODIMP CShellExt::InvokeCommand(LPCMINVOKECOMMANDINFO lpcmi)
+{
+ HRESULT hr = E_INVALIDARG;
+ int gvimExtraOptions;
+
+ // If HIWORD(lpcmi->lpVerb) then we have been called programmatically
+ // and lpVerb is a command that should be invoked. Otherwise, the shell
+ // has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has
+ // selected. Actually, it's (menu ID - idCmdFirst) from QueryContextMenu().
+ if (!HIWORD(lpcmi->lpVerb))
+ {
+ UINT idCmd = LOWORD(lpcmi->lpVerb);
+
+ if (idCmd >= m_edit_existing_off)
+ {
+ // Existing with vim instance
+ hr = PushToWindow(lpcmi->hwnd,
+ lpcmi->lpDirectory,
+ lpcmi->lpVerb,
+ lpcmi->lpParameters,
+ lpcmi->nShow,
+ idCmd - m_edit_existing_off);
+ }
+ else
+ {
+ switch (idCmd)
+ {
+ case 0:
+ gvimExtraOptions = EDIT_WITH_VIM_USE_TABPAGES;
+ break;
+ case 1:
+ gvimExtraOptions = EDIT_WITH_VIM_NO_OPTIONS;
+ break;
+ case 2:
+ gvimExtraOptions = EDIT_WITH_VIM_IN_DIFF_MODE;
+ break;
+ default:
+ // If execution reaches this point we likely have an
+ // inconsistency between the code that setup the menus
+ // and this code that determines what the user
+ // selected. This should be detected and fixed during
+ // development.
+ return E_FAIL;
+ }
+
+ LPCMINVOKECOMMANDINFOEX lpcmiex = (LPCMINVOKECOMMANDINFOEX)lpcmi;
+ LPCWSTR currentDirectory = lpcmi->cbSize == sizeof(CMINVOKECOMMANDINFOEX) ? lpcmiex->lpDirectoryW : NULL;
+
+ hr = InvokeSingleGvim(lpcmi->hwnd,
+ currentDirectory,
+ lpcmi->lpVerb,
+ lpcmi->lpParameters,
+ lpcmi->nShow,
+ gvimExtraOptions);
+ }
+ }
+ return hr;
+}
+
+STDMETHODIMP CShellExt::PushToWindow(HWND /* hParent */,
+ LPCSTR /* pszWorkingDir */,
+ LPCSTR /* pszCmd */,
+ LPCSTR /* pszParam */,
+ int /* iShowCmd */,
+ int idHWnd)
+{
+ HWND hWnd = m_hWnd[idHWnd];
+
+ // Show and bring vim instance to foreground
+ if (IsIconic(hWnd) != 0)
+ ShowWindow(hWnd, SW_RESTORE);
+ else
+ ShowWindow(hWnd, SW_SHOW);
+ SetForegroundWindow(hWnd);
+
+ // Post the selected files to the vim instance
+ PostMessage(hWnd, WM_DROPFILES, (WPARAM)medium.hGlobal, 0);
+
+ return NOERROR;
+}
+
+STDMETHODIMP CShellExt::GetCommandString(UINT_PTR /* idCmd */,
+ UINT uFlags,
+ UINT FAR * /* reserved */,
+ LPSTR pszName,
+ UINT cchMax)
+{
+ // Use UTF-8 for gettext.
+ char *prev = set_gettext_codeset();
+
+ WCHAR *s = W(_("Edits the selected file(s) with Vim"));
+ if (uFlags == GCS_HELPTEXTW && cchMax > wcslen(s))
+ wcscpy((WCHAR *)pszName, s);
+ free(s);
+
+ // Restore previous codeset.
+ restore_gettext_codeset(prev);
+
+ return NOERROR;
+}
+
+BOOL CALLBACK CShellExt::EnumWindowsProc(HWND hWnd, LPARAM lParam)
+{
+ char temp[BUFSIZE];
+
+ // First do a bunch of check
+ // No invisible window
+ if (!IsWindowVisible(hWnd))
+ return TRUE;
+ // No child window ???
+ // if (GetParent(hWnd)) return TRUE;
+ // Class name should be Vim, if failed to get class name, return
+ if (GetClassName(hWnd, temp, sizeof(temp)) == 0)
+ return TRUE;
+ // Compare class name to that of vim, if not, return
+ if (_strnicmp(temp, "vim", sizeof("vim")) != 0)
+ return TRUE;
+ // First check if the number of vim instance exceeds MAX_HWND
+ CShellExt *cs = (CShellExt*) lParam;
+ if (cs->m_cntOfHWnd >= MAX_HWND)
+ return FALSE; // stop enumeration
+ // Now we get the vim window, put it into some kind of array
+ cs->m_hWnd[cs->m_cntOfHWnd] = hWnd;
+ cs->m_cntOfHWnd ++;
+
+ return TRUE; // continue enumeration (otherwise this would be false)
+}
+
+BOOL CShellExt::LoadMenuIcon()
+{
+ char vimExeFile[BUFSIZE];
+ getGvimName(vimExeFile, 1);
+ if (vimExeFile[0] == '\0')
+ return FALSE;
+ HICON hVimIcon;
+ if (ExtractIconEx(vimExeFile, 0, NULL, &hVimIcon, 1) == 0)
+ return FALSE;
+ m_hVimIconBitmap = IconToBitmap(hVimIcon,
+ GetSysColorBrush(COLOR_MENU),
+ GetSystemMetrics(SM_CXSMICON),
+ GetSystemMetrics(SM_CYSMICON));
+ return TRUE;
+}
+
+ static char *
+searchpath(char *name)
+{
+ static char widename[2 * BUFSIZE];
+ static char location[2 * BUFSIZE + 2];
+
+ // There appears to be a bug in FindExecutableA() on Windows NT.
+ // Use FindExecutableW() instead...
+ MultiByteToWideChar(CP_ACP, 0, (LPCSTR)name, -1,
+ (LPWSTR)widename, BUFSIZE);
+ if (FindExecutableW((LPCWSTR)widename, (LPCWSTR)"",
+ (LPWSTR)location) > (HINSTANCE)32)
+ {
+ WideCharToMultiByte(CP_ACP, 0, (LPWSTR)location, -1,
+ (LPSTR)widename, 2 * BUFSIZE, NULL, NULL);
+ return widename;
+ }
+ return (char *)"";
+}
+
+
+STDMETHODIMP CShellExt::InvokeSingleGvim(HWND hParent,
+ LPCWSTR workingDir,
+ LPCSTR /* pszCmd */,
+ LPCSTR /* pszParam */,
+ int /* iShowCmd */,
+ int gvimExtraOptions)
+{
+ wchar_t m_szFileUserClickedOn[BUFSIZE];
+ wchar_t *cmdStrW;
+ size_t cmdlen;
+ size_t len;
+ UINT i;
+
+ cmdlen = BUFSIZE;
+ cmdStrW = (wchar_t *) malloc(cmdlen * sizeof(wchar_t));
+ if (cmdStrW == NULL)
+ return E_FAIL;
+ getGvimInvocationW(cmdStrW);
+
+ if (gvimExtraOptions == EDIT_WITH_VIM_IN_DIFF_MODE)
+ wcscat(cmdStrW, L" -d");
+ else if (gvimExtraOptions == EDIT_WITH_VIM_USE_TABPAGES)
+ wcscat(cmdStrW, L" -p");
+ for (i = 0; i < cbFiles; i++)
+ {
+ DragQueryFileW((HDROP)medium.hGlobal,
+ i,
+ m_szFileUserClickedOn,
+ sizeof(m_szFileUserClickedOn));
+
+ len = wcslen(cmdStrW) + wcslen(m_szFileUserClickedOn) + 4;
+ if (len > cmdlen)
+ {
+ cmdlen = len + BUFSIZE;
+ wchar_t *cmdStrW_new = (wchar_t *)realloc(cmdStrW, cmdlen * sizeof(wchar_t));
+ if (cmdStrW_new == NULL)
+ {
+ free(cmdStrW);
+ return E_FAIL;
+ }
+ cmdStrW = cmdStrW_new;
+ }
+ wcscat(cmdStrW, L" \"");
+ wcscat(cmdStrW, m_szFileUserClickedOn);
+ wcscat(cmdStrW, L"\"");
+ }
+
+ STARTUPINFOW si;
+ PROCESS_INFORMATION pi;
+
+ ZeroMemory(&si, sizeof(si));
+ si.cb = sizeof(si);
+
+ // Start the child process.
+ if (!CreateProcessW(NULL, // No module name (use command line).
+ cmdStrW, // Command line.
+ NULL, // Process handle not inheritable.
+ NULL, // Thread handle not inheritable.
+ FALSE, // Set handle inheritance to FALSE.
+ 0, // No creation flags.
+ NULL, // Use parent's environment block.
+ workingDir, // Use parent's starting directory.
+ &si, // Pointer to STARTUPINFO structure.
+ &pi) // Pointer to PROCESS_INFORMATION structure.
+ )
+ {
+ // Use UTF-8 for gettext.
+ char *prev = set_gettext_codeset();
+
+ WCHAR *msg = W(_("Error creating process: Check if gvim is in your path!"));
+ WCHAR *title = W(_("gvimext.dll error"));
+
+ MessageBoxW(hParent, msg, title, MB_OK);
+
+ free(msg);
+ free(title);
+
+ // Restore previous codeset.
+ restore_gettext_codeset(prev);
+ }
+ else
+ {
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ }
+ free(cmdStrW);
+
+ return NOERROR;
+}