Windows doesn't reset and re-enumerate it everytime autocompletion happens,
and it also sorts the strings. This matches it more closely and makes it
more useable on large lists as well.
Signed-off-by: Gabriel Ivăncescu <gabrielopcode(a)gmail.com>
---
dlls/shell32/autocomplete.c | 209 +++++++++++++++++++++++++++++++-------------
1 file changed, 148 insertions(+), 61 deletions(-)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c
index b3f86f3..d557a4a 100644
--- a/dlls/shell32/autocomplete.c
+++ b/dlls/shell32/autocomplete.c
@@ -25,7 +25,6 @@
- implement ACO_FILTERPREFIXES style
- implement ACO_RTLREADING style
- implement ResetEnumerator
- - string compares should be case-insensitive, the content of the list should be sorted
*/
#include "config.h"
@@ -62,6 +61,8 @@ typedef struct
LONG ref;
BOOL initialized;
BOOL enabled;
+ UINT enum_strs_num;
+ WCHAR **enum_strs;
HWND hwndEdit;
HWND hwndListBox;
WNDPROC wpOrigEditProc;
@@ -103,10 +104,94 @@ static void set_text_and_selection(IAutoCompleteImpl *ac, HWND hwnd, WCHAR *text
CallWindowProcW(proc, hwnd, EM_SETSEL, start, end);
}
-static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd)
+static int enumerate_strings_cmpfn(const void *a, const void *b)
+{
+ return strcmpiW(*(WCHAR* const*)a, *(WCHAR* const*)b);
+}
+
+/*
+ Enumerate all of the strings and sort them in the internal list.
+
+ We don't free the enumerated strings (except on error) to avoid needless
+ copies, until the next reset (or the object itself is destroyed)
+*/
+static void enumerate_strings(IAutoCompleteImpl *ac)
+{
+ UINT cur = 0, array_size = 1024;
+ LPOLESTR *strs = NULL, *tmp;
+ ULONG read;
+
+ do
+ {
+ if ((tmp = heap_realloc(strs, array_size * sizeof(*strs))) == NULL)
+ goto fail;
+ strs = tmp;
+
+ do
+ {
+ if (FAILED(IEnumString_Next(ac->enumstr, array_size - cur, &strs[cur], &read)))
+ read = 0;
+ } while (read != 0 && (cur += read) < array_size);
+
+ array_size *= 2;
+ } while (read != 0);
+
+ /* Allocate even if there were zero strings enumerated, to mark it non-NULL */
+ if ((tmp = heap_realloc(strs, cur * sizeof(*strs))))
+ {
+ strs = tmp;
+ if (cur > 0)
+ qsort(strs, cur, sizeof(*strs), enumerate_strings_cmpfn);
+
+ ac->enum_strs = strs;
+ ac->enum_strs_num = cur;
+ return;
+ }
+
+fail:
+ while (cur--)
+ CoTaskMemFree(strs[cur]);
+ heap_free(strs);
+}
+
+static UINT find_matching_enum_str(IAutoCompleteImpl *ac, UINT start, WCHAR *text,
+ UINT len, int direction)
+{
+ WCHAR **strs = ac->enum_strs;
+ UINT index = ~0, a = start, b = ac->enum_strs_num;
+ while (a < b)
+ {
+ UINT i = (a + b - 1) / 2;
+ int cmp = strncmpiW(text, strs[i], len);
+ if (cmp == 0)
+ {
+ index = i;
+ cmp = direction;
+ }
+ if (cmp <= 0) b = i;
+ else a = i + 1;
+ }
+ return index;
+}
+
+static void free_enum_strs(IAutoCompleteImpl *ac)
+{
+ WCHAR **strs = ac->enum_strs;
+ if (strs)
+ {
+ UINT i = ac->enum_strs_num;
+ ac->enum_strs = NULL;
+ while (i--)
+ CoTaskMemFree(strs[i]);
+ heap_free(strs);
+ }
+}
+
+static void hide_listbox(IAutoCompleteImpl *ac, HWND hwnd, BOOL reset)
{
ShowWindow(hwnd, SW_HIDE);
SendMessageW(hwnd, LB_RESETCONTENT, 0, 0);
+ if (reset) free_enum_strs(ac);
}
static void show_listbox(IAutoCompleteImpl *ac, UINT cnt)
@@ -167,14 +252,14 @@ static BOOL select_item_with_return_key(IAutoCompleteImpl *ac, HWND hwnd)
{
len = SendMessageW(hwndListBox, LB_GETTEXT, sel, (LPARAM)text);
set_text_and_selection(ac, hwnd, text, 0, len);
- hide_listbox(ac, hwndListBox);
+ hide_listbox(ac, hwndListBox, TRUE);
ac->no_fwd_char = '\r'; /* RETURN char */
heap_free(text);
return TRUE;
}
}
}
- hide_listbox(ac, hwndListBox);
+ hide_listbox(ac, hwndListBox, TRUE);
return FALSE;
}
@@ -240,7 +325,11 @@ static LRESULT change_selection(IAutoCompleteImpl *ac, HWND hwnd, UINT key)
static BOOL do_aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt, WCHAR *last_delim)
{
- WCHAR c = last_delim[1];
+ WCHAR c;
+ free_enum_strs(ac);
+ IEnumString_Reset(ac->enumstr); /* call before expand */
+
+ c = last_delim[1];
last_delim[1] = '\0';
IACList_Expand(ac->aclist, txt);
last_delim[1] = c;
@@ -276,6 +365,9 @@ static BOOL aclist_expand(IAutoCompleteImpl *ac, WCHAR *txt)
while (i--)
if (strchrW(delims, txt[i]))
return do_aclist_expand(ac, txt, &txt[i]);
+
+ /* Windows doesn't expand without a delim, but it does reset */
+ free_enum_strs(ac);
}
return FALSE;
@@ -312,66 +404,58 @@ static BOOL display_matching_strs(IAutoCompleteImpl *ac, WCHAR *text, UINT len,
HWND hwnd, enum autoappend_flag flag)
{
/* Return FALSE if we need to hide the listbox */
- UINT cpt;
+ WCHAR **str = ac->enum_strs;
+ UINT cnt, i, k;
+ if (!str) return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
- if (ac->options & ACO_AUTOSUGGEST)
+ if (len)
{
- SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
- SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
+ i = find_matching_enum_str(ac, 0, text, len, -1);
+ if (i == ~0)
+ return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE;
+
+ if (flag == autoappend_flag_yes)
+ autoappend_str(ac, text, len, str[i], hwnd);
+ if (!(ac->options & ACO_AUTOSUGGEST))
+ return TRUE;
+
+ /* Find the index beyond the last string that matches */
+ k = find_matching_enum_str(ac, i + 1, text, len, 1);
+ k = (k == ~0 ? i : k) + 1;
}
- for (cpt = 0;;)
- {
- HRESULT hr;
- LPOLESTR strs = NULL;
- ULONG fetched;
-
- hr = IEnumString_Next(ac->enumstr, 1, &strs, &fetched);
- if (hr != S_OK)
- break;
-
- if (!strncmpiW(text, strs, len))
- {
- if (cpt == 0 && flag == autoappend_flag_yes)
- {
- autoappend_str(ac, text, len, strs, hwnd);
- if (!(ac->options & ACO_AUTOSUGGEST))
- {
- CoTaskMemFree(strs);
- break;
- }
- }
-
- if (ac->options & ACO_AUTOSUGGEST)
- SendMessageW(ac->hwndListBox, LB_ADDSTRING, 0, (LPARAM)strs);
-
- cpt++;
- }
-
- CoTaskMemFree(strs);
- }
-
- if (ac->options & ACO_AUTOSUGGEST)
+ else
{
- if (cpt)
- {
- show_listbox(ac, cpt);
- SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
- }
- else
+ if (!(ac->options & ACO_AUTOSUGGEST))
+ return TRUE;
+ i = 0;
+ k = ac->enum_strs_num;
+ if (k == 0)
return FALSE;
}
+ cnt = k - i;
+
+ SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0);
+ SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0);
+ SendMessageW(ac->hwndListBox, LB_INITSTORAGE, cnt, 0);
+ do
+ SendMessageW(ac->hwndListBox, LB_INSERTSTRING, -1, (LPARAM)str[i]);
+ while (++i < k);
+
+ show_listbox(ac, cnt);
+ SendMessageW(ac->hwndListBox, WM_SETREDRAW, TRUE, 0);
return TRUE;
}
static void autocomplete_text(IAutoCompleteImpl *ac, HWND hwnd, enum autoappend_flag flag)
{
WCHAR *text;
+ BOOL expanded = FALSE;
UINT size, len = SendMessageW(hwnd, WM_GETTEXTLENGTH, 0, 0);
if (flag != autoappend_flag_displayempty && len == 0)
{
if (ac->options & ACO_AUTOSUGGEST)
- hide_listbox(ac, ac->hwndListBox);
+ hide_listbox(ac, ac->hwndListBox, TRUE);
return;
}
@@ -382,28 +466,31 @@ 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;
+ expanded = aclist_expand(ac, text);
+ }
+ if (expanded || !ac->enum_strs)
+ {
+ if (!expanded) IEnumString_Reset(ac->enumstr);
+ enumerate_strings(ac);
}
- /* Set txtbackup to point to text itself (which must not be released) */
+ /* Set txtbackup to point to text itself (which must not be released),
+ and it must be done here since aclist_expand uses it to track changes */
heap_free(ac->txtbackup);
ac->txtbackup = text;
if (!display_matching_strs(ac, text, len, hwnd, flag))
- hide_listbox(ac, ac->hwndListBox);
+ hide_listbox(ac, ac->hwndListBox, FALSE);
}
static void destroy_autocomplete_object(IAutoCompleteImpl *ac)
{
ac->hwndEdit = NULL;
+ free_enum_strs(ac);
if (ac->hwndListBox)
DestroyWindow(ac->hwndListBox);
IAutoComplete2_Release(&ac->IAutoComplete2_iface);
@@ -421,7 +508,7 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT
/* When pressing ESC, Windows hides the auto-suggest listbox, if visible */
if ((ac->options & ACO_AUTOSUGGEST) && IsWindowVisible(ac->hwndListBox))
{
- hide_listbox(ac, ac->hwndListBox);
+ hide_listbox(ac, ac->hwndListBox, FALSE);
ac->no_fwd_char = 0x1B; /* ESC char */
return 0;
}
@@ -448,7 +535,7 @@ static LRESULT ACEditSubclassProc_KeyDown(IAutoCompleteImpl *ac, HWND hwnd, UINT
}
if (ac->options & ACO_AUTOSUGGEST)
- hide_listbox(ac, ac->hwndListBox);
+ hide_listbox(ac, ac->hwndListBox, TRUE);
heap_free(text);
return 0;
}
@@ -512,12 +599,12 @@ static LRESULT APIENTRY ACEditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
{
case CB_SHOWDROPDOWN:
if (This->options & ACO_AUTOSUGGEST)
- hide_listbox(This, This->hwndListBox);
+ hide_listbox(This, This->hwndListBox, TRUE);
return 0;
case WM_KILLFOCUS:
if ((This->options & ACO_AUTOSUGGEST) && ((HWND)wParam != This->hwndListBox))
{
- hide_listbox(This, This->hwndListBox);
+ hide_listbox(This, This->hwndListBox, FALSE);
}
break;
case WM_KEYDOWN:
@@ -583,7 +670,7 @@ static LRESULT APIENTRY ACLBoxSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
break;
len = SendMessageW(hwnd, LB_GETTEXT, sel, (LPARAM)msg);
set_text_and_selection(This, This->hwndEdit, msg, 0, len);
- hide_listbox(This, hwnd);
+ hide_listbox(This, hwnd, TRUE);
heap_free(msg);
break;
default:
@@ -861,7 +948,7 @@ static HRESULT WINAPI IAutoComplete2_fnSetOptions(
if ((This->options & ACO_AUTOSUGGEST) && This->hwndEdit && !This->hwndListBox)
create_listbox(This);
else if (!(This->options & ACO_AUTOSUGGEST) && This->hwndListBox)
- hide_listbox(This, This->hwndListBox);
+ hide_listbox(This, This->hwndListBox, TRUE);
return hr;
}
--
1.9.1