diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/search.c | 87 | ||||
| -rw-r--r-- | src/nvim/testdir/test_filetype.vim | 1 | ||||
| -rw-r--r-- | src/nvim/testdir/test_matchfuzzy.vim | 12 | ||||
| -rw-r--r-- | src/nvim/tui/input.c | 35 | ||||
| -rw-r--r-- | src/nvim/tui/input.h | 10 | ||||
| -rw-r--r-- | src/nvim/tui/tui.c | 56 | ||||
| -rw-r--r-- | src/nvim/tui/tui.h | 2 |
7 files changed, 154 insertions, 49 deletions
diff --git a/src/nvim/search.c b/src/nvim/search.c index 4fc5ac93aa..48df289831 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -5093,26 +5093,30 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2) /// for each item or use 'item_cb' Funcref function to get the string. /// If 'retmatchpos' is true, then return a list of positions where 'str' /// matches for each item. -static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bool matchseq, +static void fuzzy_match_in_list(list_T *const l, char_u *const str, const bool matchseq, const char_u *const key, Callback *const item_cb, - const bool retmatchpos, list_T *const fmatchlist) + const bool retmatchpos, list_T *const fmatchlist, + const long max_matches) FUNC_ATTR_NONNULL_ARG(2, 5, 7) { - const long len = tv_list_len(items); + long len = tv_list_len(l); if (len == 0) { return; } + if (max_matches > 0 && len > max_matches) { + len = max_matches; + } - fuzzyItem_T *const ptrs = xcalloc(len, sizeof(fuzzyItem_T)); - long i = 0; - bool found_match = false; + fuzzyItem_T *const items = xcalloc(len, sizeof(fuzzyItem_T)); + long match_count = 0; uint32_t matches[MAX_FUZZY_MATCHES]; // For all the string items in items, get the fuzzy matching score - TV_LIST_ITER(items, li, { - ptrs[i].idx = i; - ptrs[i].item = li; - ptrs[i].score = SCORE_NONE; + TV_LIST_ITER(l, li, { + if (max_matches > 0 && match_count >= max_matches) { + break; + } + char_u *itemstr = NULL; typval_T rettv; rettv.v_type = VAR_UNKNOWN; @@ -5143,31 +5147,33 @@ static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bo int score; if (itemstr != NULL && fuzzy_match(itemstr, str, matchseq, &score, matches, - sizeof(matches) / sizeof(matches[0]))) { + MAX_FUZZY_MATCHES)) { + items[match_count].idx = match_count; + items[match_count].item = li; + items[match_count].score = score; + // Copy the list of matching positions in itemstr to a list, if // 'retmatchpos' is set. if (retmatchpos) { - ptrs[i].lmatchpos = tv_list_alloc(kListLenMayKnow); + items[match_count].lmatchpos = tv_list_alloc(kListLenMayKnow); int j = 0; const char_u *p = str; while (*p != NUL) { if (!ascii_iswhite(utf_ptr2char(p))) { - tv_list_append_number(ptrs[i].lmatchpos, matches[j]); + tv_list_append_number(items[match_count].lmatchpos, matches[j]); j++; } MB_PTR_ADV(p); } } - ptrs[i].score = score; - found_match = true; + match_count++; } - i++; tv_clear(&rettv); }); - if (found_match) { + if (match_count > 0) { // Sort the list by the descending order of the match score - qsort(ptrs, len, sizeof(fuzzyItem_T), fuzzy_match_item_compare); + qsort(items, match_count, sizeof(fuzzyItem_T), fuzzy_match_item_compare); // For matchfuzzy(), return a list of matched strings. // ['str1', 'str2', 'str3'] @@ -5176,48 +5182,49 @@ static void fuzzy_match_in_list(list_T *const items, char_u *const str, const bo // is a list of lists where each list item is a list of matched // character positions. The third item is a list of matching scores. // [['str1', 'str2', 'str3'], [[1, 3], [1, 3], [1, 3]]] - list_T *l; + list_T *retlist; if (retmatchpos) { const listitem_T *const li = tv_list_find(fmatchlist, 0); assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); - l = TV_LIST_ITEM_TV(li)->vval.v_list; + retlist = TV_LIST_ITEM_TV(li)->vval.v_list; } else { - l = fmatchlist; + retlist = fmatchlist; } // Copy the matching strings with a valid score to the return list - for (i = 0; i < len; i++) { - if (ptrs[i].score == SCORE_NONE) { + for (long i = 0; i < match_count; i++) { + if (items[i].score == SCORE_NONE) { break; } - tv_list_append_tv(l, TV_LIST_ITEM_TV(ptrs[i].item)); + tv_list_append_tv(retlist, TV_LIST_ITEM_TV(items[i].item)); } // next copy the list of matching positions if (retmatchpos) { const listitem_T *li = tv_list_find(fmatchlist, -2); assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); - l = TV_LIST_ITEM_TV(li)->vval.v_list; - for (i = 0; i < len; i++) { - if (ptrs[i].score == SCORE_NONE) { + retlist = TV_LIST_ITEM_TV(li)->vval.v_list; + + for (long i = 0; i < match_count; i++) { + if (items[i].score == SCORE_NONE) { break; } - tv_list_append_list(l, ptrs[i].lmatchpos); + tv_list_append_list(retlist, items[i].lmatchpos); } // copy the matching scores li = tv_list_find(fmatchlist, -1); assert(li != NULL && TV_LIST_ITEM_TV(li)->vval.v_list != NULL); - l = TV_LIST_ITEM_TV(li)->vval.v_list; - for (i = 0; i < len; i++) { - if (ptrs[i].score == SCORE_NONE) { + retlist = TV_LIST_ITEM_TV(li)->vval.v_list; + for (long i = 0; i < match_count; i++) { + if (items[i].score == SCORE_NONE) { break; } - tv_list_append_number(l, ptrs[i].score); + tv_list_append_number(retlist, items[i].score); } } } - xfree(ptrs); + xfree(items); } /// Do fuzzy matching. Returns the list of matched strings in 'rettv'. @@ -5239,6 +5246,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, Callback cb = CALLBACK_NONE; const char_u *key = NULL; bool matchseq = false; + long max_matches = 0; if (argvars[2].v_type != VAR_UNKNOWN) { if (argvars[2].v_type != VAR_DICT || argvars[2].vval.v_dict == NULL) { emsg(_(e_dictreq)); @@ -5248,8 +5256,8 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, // To search a dict, either a callback function or a key can be // specified. dict_T *const d = argvars[2].vval.v_dict; - const dictitem_T *const di = tv_dict_find(d, "key", -1); - if (di != NULL) { + const dictitem_T *di; + if ((di = tv_dict_find(d, "key", -1)) != NULL) { if (di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string == NULL || *di->di_tv.vval.v_string == NUL) { semsg(_(e_invarg2), tv_get_string(&di->di_tv)); @@ -5259,7 +5267,14 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } else if (!tv_dict_get_callback(d, "text_cb", -1, &cb)) { semsg(_(e_invargval), "text_cb"); return; + } else if ((di = tv_dict_find(d, "limit", -1)) != NULL) { + if (di->di_tv.v_type != VAR_NUMBER) { + semsg(_(e_invarg2), tv_get_string(&di->di_tv)); + return; + } + max_matches = (long)tv_get_number_chk(&di->di_tv, NULL); } + if (tv_dict_find(d, "matchseq", -1) != NULL) { matchseq = true; } @@ -5278,7 +5293,7 @@ static void do_fuzzymatch(const typval_T *const argvars, typval_T *const rettv, } fuzzy_match_in_list(argvars[0].vval.v_list, (char_u *)tv_get_string(&argvars[1]), matchseq, key, - &cb, retmatchpos, rettv->vval.v_list); + &cb, retmatchpos, rettv->vval.v_list, max_matches); callback_free(&cb); } diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 7a52d0a044..6872eb3bb7 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -258,6 +258,7 @@ let s:filename_checks = { \ 'rnoweb': ['file.rnw', 'file.snw'], \ 'rrst': ['file.rrst', 'file.srst'], \ 'template': ['file.tmpl'], + \ 'html': ['file.html', 'file.htm', 'file.cshtml'], \ 'htmlm4': ['file.html.m4'], \ 'httest': ['file.htt', 'file.htb'], \ 'ibasic': ['file.iba', 'file.ibi'], diff --git a/src/nvim/testdir/test_matchfuzzy.vim b/src/nvim/testdir/test_matchfuzzy.vim index abcc9b40c1..d53f8b0f4d 100644 --- a/src/nvim/testdir/test_matchfuzzy.vim +++ b/src/nvim/testdir/test_matchfuzzy.vim @@ -245,4 +245,16 @@ func Test_matchfuzzypos_mbyte() call assert_equal([['xффйд'], [[2, 3, 4]], [168]], matchfuzzypos(['xффйд'], 'фйд')) endfunc +" Test for matchfuzzy() with limit +func Test_matchfuzzy_limit() + let x = ['1', '2', '3', '2'] + call assert_equal(['2', '2'], x->matchfuzzy('2')) + call assert_equal(['2', '2'], x->matchfuzzy('2', #{})) + call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 0})) + call assert_equal(['2'], x->matchfuzzy('2', #{limit: 1})) + call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 2})) + call assert_equal(['2', '2'], x->matchfuzzy('2', #{limit: 3})) + call assert_fails("call matchfuzzy(x, '2', #{limit: '2'})", 'E475:') +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 691b2ea9da..399ad325d1 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -13,6 +13,7 @@ #include "nvim/option.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/tui/tui.h" #include "nvim/tui/input.h" #include "nvim/vim.h" #ifdef WIN32 @@ -41,6 +42,7 @@ void tinput_init(TermInput *input, Loop *loop) input->paste = 0; input->in_fd = STDIN_FILENO; input->waiting_for_bg_response = 0; + input->extkeys_type = kExtkeysNone; // The main thread is waiting for the UI thread to call CONTINUE, so it can // safely access global variables. input->ttimeout = (bool)p_ttimeout; @@ -344,6 +346,39 @@ static void tk_getkeys(TermInput *input, bool force) forward_modified_utf8(input, &key); } else if (key.type == TERMKEY_TYPE_MOUSE) { forward_mouse_event(input, &key); + } else if (key.type == TERMKEY_TYPE_UNKNOWN_CSI) { + // There is no specified limit on the number of parameters a CSI sequence can contain, so just + // allocate enough space for a large upper bound + long args[16]; + size_t nargs = 16; + unsigned long cmd; + if (termkey_interpret_csi(input->tk, &key, args, &nargs, &cmd) == TERMKEY_RES_KEY) { + uint8_t intermediate = (cmd >> 16) & 0xFF; + uint8_t initial = (cmd >> 8) & 0xFF; + uint8_t command = cmd & 0xFF; + + // Currently unused + (void)intermediate; + + if (input->waiting_for_csiu_response > 0) { + if (initial == '?' && command == 'u') { + // The first (and only) argument contains the current progressive + // enhancement flags. Only enable CSI u mode if the first bit + // (disambiguate escape codes) is not already set + if (nargs > 0 && (args[0] & 0x1) == 0) { + input->extkeys_type = kExtkeysCSIu; + } else { + input->extkeys_type = kExtkeysNone; + } + } else if (initial == '?' && command == 'c') { + // Received Primary Device Attributes response + input->waiting_for_csiu_response = 0; + tui_enable_extkeys(input->tui_data); + } else { + input->waiting_for_csiu_response--; + } + } + } } } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 2a8ea32a88..84daf40744 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -6,6 +6,13 @@ #include "nvim/event/stream.h" #include "nvim/event/time.h" +#include "nvim/tui/tui.h" + +typedef enum { + kExtkeysNone, + kExtkeysCSIu, + kExtkeysXterm, +} ExtkeysType; typedef struct term_input { int in_fd; @@ -14,6 +21,8 @@ typedef struct term_input { bool waiting; bool ttimeout; int8_t waiting_for_bg_response; + int8_t waiting_for_csiu_response; + ExtkeysType extkeys_type; long ttimeoutlen; TermKey *tk; #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 @@ -25,6 +34,7 @@ typedef struct term_input { RBuffer *key_buffer; uv_mutex_t key_buffer_mutex; uv_cond_t key_buffer_cond; + TUIData *tui_data; } TermInput; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4b5ad4cff8..61c6dc5ca3 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -71,7 +71,7 @@ typedef struct { int top, bot, left, right; } Rect; -typedef struct { +struct TUIData { UIBridgeData *bridge; Loop *loop; unibi_var_t params[9]; @@ -132,9 +132,10 @@ typedef struct { int set_underline_style; int set_underline_color; int enable_extended_keys, disable_extended_keys; + int get_extkeys; } unibi_ext; char *space_buf; -} TUIData; +}; static bool volatile got_winch = false; static bool did_user_set_dimensions = false; @@ -179,6 +180,32 @@ UI *tui_start(void) return ui_bridge_attach(ui, tui_main, tui_scheduler); } +void tui_enable_extkeys(TUIData *data) +{ + TermInput input = data->input; + unibi_term *ut = data->ut; + UI *ui = data->bridge->ui; + + switch (input.extkeys_type) { + case kExtkeysCSIu: + data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>1u"); + data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[<1u"); + break; + case kExtkeysXterm: + data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", + "\x1b[>4;2m"); + data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", + "\x1b[>4;0m"); + break; + default: + break; + } + + unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); +} + static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, char *buf, size_t len) { const char *str = unibi_get_str(data->ut, unibi_index); @@ -228,8 +255,10 @@ static void terminfo_start(UI *ui) data->unibi_ext.set_underline_color = -1; data->unibi_ext.enable_extended_keys = -1; data->unibi_ext.disable_extended_keys = -1; + data->unibi_ext.get_extkeys = -1; data->out_fd = STDOUT_FILENO; data->out_isatty = os_isatty(data->out_fd); + data->input.tui_data = data; const char *term = os_getenv("TERM"); #ifdef WIN32 @@ -311,8 +340,9 @@ static void terminfo_start(UI *ui) // Enable bracketed paste unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); - // Enable extended keys (also known as 'modifyOtherKeys' or CSI u) - unibi_out_ext(ui, data->unibi_ext.enable_extended_keys); + // Query the terminal to see if it supports CSI u + data->input.waiting_for_csiu_response = 5; + unibi_out_ext(ui, data->unibi_ext.get_extkeys); int ret; uv_loop_init(&data->write_loop); @@ -1810,6 +1840,12 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, const char *col data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg", "\x1b]11;?\x07"); + // Query the terminal to see if it supports CSI u key encoding by writing CSI + // ? u followed by a request for the primary device attributes (CSI c) + // See https://sw.kovidgoyal.net/kitty/keyboard-protocol/#detection-of-support-for-this-protocol + data->unibi_ext.get_extkeys = (int)unibi_add_ext_str(ut, "ext.get_extkeys", + "\x1b[?u\x1b[c"); + // Terminals with 256-colour SGR support despite what terminfo says. if (unibi_get_num(ut, unibi_max_colors) < 256) { // See http://fedoraproject.org/wiki/Features/256_Color_Terminals @@ -2074,15 +2110,9 @@ static void augment_terminfo(TUIData *data, const char *term, long vte_version, "\x1b[58:2::%p1%d:%p2%d:%p3%dm"); } - data->unibi_ext.enable_extended_keys = unibi_find_ext_str(ut, "Eneks"); - data->unibi_ext.disable_extended_keys = unibi_find_ext_str(ut, "Dseks"); - if (data->unibi_ext.enable_extended_keys == -1) { - if (!kitty && (vte_version == 0 || vte_version >= 5400)) { - data->unibi_ext.enable_extended_keys = (int)unibi_add_ext_str(ut, "ext.enable_extended_keys", - "\x1b[>4;2m"); - data->unibi_ext.disable_extended_keys = (int)unibi_add_ext_str(ut, "ext.disable_extended_keys", - "\x1b[>4m"); - } + if (!kitty && (vte_version == 0 || vte_version >= 5400)) { + // Fallback to Xterm's modifyOtherKeys if terminal does not support CSI u + data->input.extkeys_type = kExtkeysXterm; } } diff --git a/src/nvim/tui/tui.h b/src/nvim/tui/tui.h index 996496ee60..88ea73e99c 100644 --- a/src/nvim/tui/tui.h +++ b/src/nvim/tui/tui.h @@ -4,6 +4,8 @@ #include "nvim/cursor_shape.h" #include "nvim/ui.h" +typedef struct TUIData TUIData; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.h.generated.h" #endif |
