Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
v3: Use strpbrkW and a helper function, and make it return TRUE when it expands, which will be useful in the future.
Also avoid (extremely rare?) reference leak when E_OUTOFMEMORY is returned.
dlls/shell32/autocomplete.c | 70 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index 11e9317..dfe7638 100644 --- a/dlls/shell32/autocomplete.c +++ b/dlls/shell32/autocomplete.c @@ -2,6 +2,7 @@ * AutoComplete interfaces implementation. * * Copyright 2004 Maxime Bellengé maxime.bellenge@laposte.net + * Copyright 2018 Gabriel Ivăncescu gabrielopcode@gmail.com * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -68,6 +69,7 @@ typedef struct WCHAR *txtbackup; WCHAR *quickComplete; IEnumString *enumstr; + IACList *aclist; AUTOCOMPLETEOPTIONS options; WCHAR no_fwd_char; } IAutoCompleteImpl; @@ -221,6 +223,49 @@ static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key) return 0; }
+static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim) +{ + WCHAR c = last_delim[1]; + last_delim[1] = '\0'; + IACList_Expand(ac->aclist, txt); + last_delim[1] = c; + return TRUE; +} + +static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt) +{ + /* call IACList::Expand only when needed, if the + new txt and old_txt require different expansions */ + WCHAR c, *p, *last_delim, *old_txt = ac->txtbackup; + size_t i = 0; + + /* '/' is allowed as a delim for unix paths */ + static const WCHAR delims[] = { '\', '/', 0 }; + + /* skip the shared prefix */ + while ((c = tolowerW(txt[i])) == tolowerW(old_txt[i])) + { + if (c == '\0') return FALSE; + i++; + } + + /* they differ at this point, check for a delim further in txt */ + for (last_delim = NULL, p = &txt[i]; (p = strpbrkW(p, delims)) != NULL; p++) + last_delim = p; + if (last_delim) return do_aclist_expand(ac, txt, last_delim); + + /* txt has no delim after i, check for a delim further in old_txt */ + if (strpbrkW(&old_txt[i], delims)) + { + /* scan backwards to find the first delim before txt[i] (if any) */ + while (i--) + if (strchrW(delims, txt[i])) + return do_aclist_expand(ac, txt, &txt[i]); + } + + return FALSE; +} + static void autoappend_str(IAutoCompleteImpl *ac, WCHAR *text, UINT len, WCHAR *str, HWND hwnd) { DWORD sel_start; @@ -268,6 +313,17 @@ static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_ if (len + 1 != size) text = heap_realloc(text, (len + 1) * sizeof(WCHAR));
+ /* Reset it here to simplify the logic in aclist_expand for + empty strings, since it tracks changes using txtbackup, + and Reset needs to be called before IACList::Expand */ + IEnumString_Reset(ac->enumstr); + if (ac->aclist) + { + aclist_expand(ac, text); + if (text[len - 1] == '\' || text[len - 1] == '/') + flag = autoappend_flag_no; + } + /* Set txtbackup to point to text itself (which must not be released) */ heap_free(ac->txtbackup); ac->txtbackup = text; @@ -277,7 +333,6 @@ static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_ SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0); SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0); } - IEnumString_Reset(ac->enumstr); for (cpt = 0;;) { LPOLESTR strs = NULL; @@ -607,6 +662,8 @@ static ULONG WINAPI IAutoComplete2_fnRelease( heap_free(This->txtbackup); if (This->enumstr) IEnumString_Release(This->enumstr); + if (This->aclist) + IACList_Release(This->aclist); heap_free(This); } return refCount; @@ -663,6 +720,17 @@ static HRESULT WINAPI IAutoComplete2_fnInit( return E_NOINTERFACE; }
+ /* Prevent txtbackup from ever being NULL to simplify aclist_expand */ + if ((This->txtbackup = heap_alloc_zero(sizeof(WCHAR))) == NULL) + { + IEnumString_Release(This->enumstr); + This->enumstr = NULL; + return E_OUTOFMEMORY; + } + + if (FAILED (IUnknown_QueryInterface (punkACL, &IID_IACList, (LPVOID*)&This->aclist))) + This->aclist = NULL; + This->initialized = TRUE; This->hwndEdit = hwndEdit;
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
I had to remove the #include of initguid.h because it gave a duplicate IID_ShellFolderView definition with shlobj.h, which is needed for IACList.
dlls/shell32/tests/autocomplete.c | 114 +++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 8 deletions(-)
diff --git a/dlls/shell32/tests/autocomplete.c b/dlls/shell32/tests/autocomplete.c index e85e30c..66bd472 100644 --- a/dlls/shell32/tests/autocomplete.c +++ b/dlls/shell32/tests/autocomplete.c @@ -25,8 +25,8 @@ #include "windows.h" #include "shobjidl.h" #include "shlguid.h" -#include "initguid.h" #include "shldisp.h" +#include "shlobj.h"
#include "wine/heap.h" #include "wine/test.h" @@ -269,10 +269,13 @@ static LRESULT CALLBACK HijackerWndProc2(HWND hWnd, UINT msg, WPARAM wParam, LPA struct string_enumerator { IEnumString IEnumString_iface; + IACList IACList_iface; LONG ref; WCHAR **data; int data_len; int cur; + UINT num_expand; + WCHAR last_expand[32]; };
static struct string_enumerator *impl_from_IEnumString(IEnumString *iface) @@ -282,15 +285,19 @@ static struct string_enumerator *impl_from_IEnumString(IEnumString *iface)
static HRESULT WINAPI string_enumerator_QueryInterface(IEnumString *iface, REFIID riid, void **ppv) { + struct string_enumerator *this = impl_from_IEnumString(iface); if (IsEqualGUID(riid, &IID_IEnumString) || IsEqualGUID(riid, &IID_IUnknown)) + *ppv = &this->IEnumString_iface; + else if (IsEqualGUID(riid, &IID_IACList)) + *ppv = &this->IACList_iface; + else { - IUnknown_AddRef(iface); - *ppv = iface; - return S_OK; + *ppv = NULL; + return E_NOINTERFACE; }
- *ppv = NULL; - return E_NOINTERFACE; + IUnknown_AddRef(&this->IEnumString_iface); + return S_OK; }
static ULONG WINAPI string_enumerator_AddRef(IEnumString *iface) @@ -361,7 +368,7 @@ static HRESULT WINAPI string_enumerator_Clone(IEnumString *iface, IEnumString ** return E_NOTIMPL; }
-static IEnumStringVtbl string_enumerator_vtlb = +static IEnumStringVtbl string_enumerator_vtbl = { string_enumerator_QueryInterface, string_enumerator_AddRef, @@ -372,12 +379,54 @@ static IEnumStringVtbl string_enumerator_vtlb = string_enumerator_Clone };
+static struct string_enumerator *impl_from_IACList(IACList *iface) +{ + return CONTAINING_RECORD(iface, struct string_enumerator, IACList_iface); +} + +static HRESULT WINAPI aclist_QueryInterface(IACList *iface, REFIID riid, void **ppv) +{ + return string_enumerator_QueryInterface(&impl_from_IACList(iface)->IEnumString_iface, riid, ppv); +} + +static ULONG WINAPI aclist_AddRef(IACList *iface) +{ + return string_enumerator_AddRef(&impl_from_IACList(iface)->IEnumString_iface); +} + +static ULONG WINAPI aclist_Release(IACList *iface) +{ + return string_enumerator_Release(&impl_from_IACList(iface)->IEnumString_iface); +} + +static HRESULT WINAPI aclist_Expand(IACList *iface, const WCHAR *expand) +{ + struct string_enumerator *this = impl_from_IACList(iface); + + /* see what we get called with and how many times, + don't actually do any expansion of the strings */ + memcpy(this->last_expand, expand, min((lstrlenW(expand) + 1)*sizeof(WCHAR), sizeof(this->last_expand))); + this->last_expand[ARRAY_SIZE(this->last_expand) - 1] = '\0'; + this->num_expand++; + + return S_OK; +} + +static IACListVtbl aclist_vtbl = +{ + aclist_QueryInterface, + aclist_AddRef, + aclist_Release, + aclist_Expand +}; + static HRESULT string_enumerator_create(void **ppv, WCHAR **suggestions, int count) { struct string_enumerator *object;
object = heap_alloc_zero(sizeof(*object)); - object->IEnumString_iface.lpVtbl = &string_enumerator_vtlb; + object->IEnumString_iface.lpVtbl = &string_enumerator_vtbl; + object->IACList_iface.lpVtbl = &aclist_vtbl; object->ref = 1; object->data = suggestions; object->data_len = count; @@ -399,6 +448,53 @@ static void dispatch_messages(void) } }
+static void test_aclist_expand(HWND hwnd_edit, void *enumerator) +{ + struct string_enumerator *obj = (struct string_enumerator*)enumerator; + static WCHAR str1[] = {'t','e','s','t',0}; + static WCHAR str1a[] = {'t','e','s','t','\',0}; + static WCHAR str2[] = {'t','e','s','t','\','f','o','o','\','b','a','r','\','b','a',0}; + static WCHAR str2a[] = {'t','e','s','t','\','f','o','o','\','b','a','r','\',0}; + static WCHAR str2b[] = {'t','e','s','t','\','f','o','o','\','b','a','r','\','b','a','z','_','b','b','q','\',0}; + + ok(obj->num_expand == 0, "Expected 0 expansions, got %u\n", obj->num_expand); + SendMessageW(hwnd_edit, WM_SETTEXT, 0, (LPARAM)str1); + SendMessageW(hwnd_edit, EM_SETSEL, ARRAY_SIZE(str1) - 1, ARRAY_SIZE(str1) - 1); + SendMessageW(hwnd_edit, WM_CHAR, '\', 1); + dispatch_messages(); + ok(obj->num_expand == 1, "Expected 1 expansion, got %u\n", obj->num_expand); + ok(lstrcmpW(obj->last_expand, str1a) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str1a), wine_dbgstr_w(obj->last_expand)); + SendMessageW(hwnd_edit, WM_SETTEXT, 0, (LPARAM)str2); + SendMessageW(hwnd_edit, EM_SETSEL, ARRAY_SIZE(str2) - 1, ARRAY_SIZE(str2) - 1); + SendMessageW(hwnd_edit, WM_CHAR, 'z', 1); + dispatch_messages(); + ok(obj->num_expand == 2, "Expected 2 expansions, got %u\n", obj->num_expand); + ok(lstrcmpW(obj->last_expand, str2a) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str2a), wine_dbgstr_w(obj->last_expand)); + SetFocus(hwnd_edit); + SendMessageW(hwnd_edit, WM_CHAR, '_', 1); + SendMessageW(hwnd_edit, WM_CHAR, 'b', 1); + SetFocus(0); + SetFocus(hwnd_edit); + SendMessageW(hwnd_edit, WM_CHAR, 'b', 1); + SendMessageW(hwnd_edit, WM_CHAR, 'q', 1); + dispatch_messages(); + ok(obj->num_expand == 2, "Expected 2 expansions, got %u\n", obj->num_expand); + SendMessageW(hwnd_edit, WM_CHAR, '\', 1); + dispatch_messages(); + ok(obj->num_expand == 3, "Expected 3 expansions, got %u\n", obj->num_expand); + ok(lstrcmpW(obj->last_expand, str2b) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str2b), wine_dbgstr_w(obj->last_expand)); + SendMessageW(hwnd_edit, EM_SETSEL, ARRAY_SIZE(str1a) - 1, -1); + SendMessageW(hwnd_edit, WM_CHAR, 'x', 1); + SendMessageW(hwnd_edit, WM_CHAR, 'y', 1); + dispatch_messages(); + ok(obj->num_expand == 4, "Expected 4 expansions, got %u\n", obj->num_expand); + ok(lstrcmpW(obj->last_expand, str1a) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str1a), wine_dbgstr_w(obj->last_expand)); + SendMessageW(hwnd_edit, EM_SETSEL, ARRAY_SIZE(str1) - 1, -1); + SendMessageW(hwnd_edit, WM_CHAR, 'x', 1); + dispatch_messages(); + ok(obj->num_expand == 4, "Expected 4 expansions, got %u\n", obj->num_expand); +} + static void test_custom_source(void) { static WCHAR str_alpha[] = {'t','e','s','t','1',0}; @@ -464,6 +560,8 @@ static void test_custom_source(void) ok(lstrcmpW(str_beta, buffer) == 0, "Expected %s, got %s\n", wine_dbgstr_w(str_beta), wine_dbgstr_w(buffer)); /* end of hijacks */
+ test_aclist_expand(hwnd_edit, enumerator); + ShowWindow(hMainWnd, SW_HIDE); DestroyWindow(hwnd_edit); }
On Tue, Oct 16, 2018 at 02:30:46PM +0300, Gabriel Ivăncescu wrote:
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
I had to remove the #include of initguid.h because it gave a duplicate IID_ShellFolderView definition with shlobj.h, which is needed for IACList.
dlls/shell32/tests/autocomplete.c | 114 +++++++++++++++++++++++++++++++++++--- 1 file changed, 106 insertions(+), 8 deletions(-)
diff --git a/dlls/shell32/tests/autocomplete.c b/dlls/shell32/tests/autocomplete.c index e85e30c..66bd472 100644 --- a/dlls/shell32/tests/autocomplete.c +++ b/dlls/shell32/tests/autocomplete.c @@ -25,8 +25,8 @@ #include "windows.h" #include "shobjidl.h" #include "shlguid.h" -#include "initguid.h" #include "shldisp.h" +#include "shlobj.h"
I haven't looked at this patch properly yet, but note that while this does appear to be correct here, initguid.h is not needed, had it been you could have placed shlobj.h before initguid.h to avoid the duplicate definition.
Huw.
Signed-off-by: Huw Davies huw@codeweavers.com