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@gmail.com --- dlls/shell32/autocomplete.c | 206 +++++++++++++++++++++++++++++++------------- 1 file changed, 146 insertions(+), 60 deletions(-)
diff --git a/dlls/shell32/autocomplete.c b/dlls/shell32/autocomplete.c index b3f86f3..69d9cc8 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; }
@@ -241,6 +326,10 @@ 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]; + + free_enum_strs(ac); + IEnumString_Reset(ac->enumstr); /* call before expand */ + 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,57 @@ 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, start, end; + 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); + start = find_matching_enum_str(ac, 0, text, len, -1); + if (start == ~0) + return (ac->options & ACO_AUTOSUGGEST) ? FALSE : TRUE; + + if (flag == autoappend_flag_yes) + autoappend_str(ac, text, len, str[start], hwnd); + if (!(ac->options & ACO_AUTOSUGGEST)) + return TRUE; + + /* Find the index beyond the last string that matches */ + end = find_matching_enum_str(ac, start + 1, text, len, 1); + end = (end == ~0 ? start : end) + 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; + start = 0; + end = ac->enum_strs_num; + if (end == 0) return FALSE; } + cnt = end - start; + + SendMessageW(ac->hwndListBox, WM_SETREDRAW, FALSE, 0); + SendMessageW(ac->hwndListBox, LB_RESETCONTENT, 0, 0); + SendMessageW(ac->hwndListBox, LB_INITSTORAGE, cnt, 0); + for (; start < end; start++) + SendMessageW(ac->hwndListBox, LB_INSERTSTRING, -1, (LPARAM)str[start]); + + 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 +465,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 +507,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 +534,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 +598,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 +669,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 +947,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; }