From c05335082adb21d99d96374779856444a3a0688f Mon Sep 17 00:00:00 2001 From: Girish Palya Date: Fri, 26 Sep 2025 17:29:38 +0000 Subject: [PATCH] patch 9.1.1797: completion: autocompletion can be improved Problem: completion: autocompletion can be improved Solution: Add support for "longest" and "preinsert" in 'autocomplete'; add preinserted() (Girish Palya) * Add support for "longest" in 'completeopt' when 'autocomplete' is enabled. (Note: the cursor position does not change automatically when 'autocomplete' is enabled.) * Add support for "preinsert" when 'autocomplete' is enabled. Ensure "preinsert" works the same with and without 'autocomplete' * introduce the preinserted() Vim script function, useful for defining custom key mappings. fixes: #18314 closes: #18387 Signed-off-by: Girish Palya Signed-off-by: Christian Brabandt --- runtime/doc/builtin.txt | 10 + runtime/doc/options.txt | 44 ++-- runtime/doc/tags | 1 + runtime/doc/usr_41.txt | 3 +- runtime/doc/version9.txt | 4 +- runtime/syntax/vim.vim | 2 +- src/edit.c | 11 +- src/evalfunc.c | 2 + src/insexpand.c | 161 +++++++------- src/proto/insexpand.pro | 5 +- .../dumps/Test_autocompletedelay_7.dump | 6 +- src/testdir/test_ins_complete.vim | 199 +++++++++++++++++- src/version.c | 2 + 13 files changed, 336 insertions(+), 114 deletions(-) diff --git a/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 90aefbf336..0a73928e97 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -453,6 +453,7 @@ popup_setoptions({id}, {options}) popup_settext({id}, {text}) none set the text of popup window {id} popup_show({id}) Number unhide popup window {id} pow({x}, {y}) Float {x} to the power of {y} +preinserted() Number whether text is inserted after cursor prevnonblank({lnum}) Number line nr of non-blank line <= {lnum} printf({fmt}, {expr1}...) String format text prompt_getprompt({buf}) String get prompt text @@ -8033,6 +8034,15 @@ pow({x}, {y}) *pow()* Return type: |Number| +preinserted() *preinserted()* + Returns non-zero if text has been inserted after the cursor + because "preinsert" is present in 'completeopt', or if + "longest" is present in 'completeopt' while 'autocomplete' + is enabled. Otherwise returns zero. + + Return type: |Number| + + prevnonblank({lnum}) *prevnonblank()* Return the line number of the first line at or above {lnum} that is not blank. Example: > diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 0120275183..ebd7e53bca 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1,4 +1,4 @@ -*options.txt* For Vim version 9.1. Last change: 2025 Sep 22 +*options.txt* For Vim version 9.1. Last change: 2025 Sep 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -2256,11 +2256,22 @@ A jump table for the options with a short description can be found at |Q_op|. to gather more alternatives for your candidate list, see 'completefuzzycollect'. - longest Only insert the longest common text of the matches. If - the menu is displayed you can use CTRL-L to add more - characters. Whether case is ignored depends on the kind - of completion. For buffer text the 'ignorecase' option is - used. + longest + When 'autocomplete' is not active, only the longest + common prefix of the matches is inserted. If the popup + menu is displayed, you can use CTRL-L to add more + characters. Whether case is ignored depends on the type + of completion. For buffer text the 'ignorecase' option + applies. + + When 'autocomplete' is active and no completion item is + selected, the longest common prefix of the matches is + inserted after the cursor. The prefix is taken either + from all displayed items or only from items in the current + buffer. The inserted text is highlighted with + |hl-PreInsert|, and the cursor position does not change + (similar to `"preinsert"`). Press CTRL-Y to accept. + See also |preinserted()|. menu Use a popup menu to show the possible completions. The menu is only shown when there is more than one match and @@ -2302,22 +2313,21 @@ A jump table for the options with a short description can be found at |Q_op|. {only works when compiled with the |+textprop| feature} preinsert - When 'autocomplete' is not active, inserts the part of the - first candidate word beyond the current completion leader, - highlighted with |hl-PreInsert|. The cursor doesn't move. - Requires "fuzzy" unset and "menuone" in 'completeopt'. - - When 'autocomplete' is active, inserts the longest common - prefix of matches (from all shown items or from the - current buffer items). This occurs only when no menu item - is selected. Press CTRL-Y to accept. + Inserts the text of the first completion candidate + beyond the current leader, highlighted with |hl-PreInsert|. + The cursor does not move. + Requires "fuzzy" to be unset, and either "menuone" in + 'completeopt' or 'autocomplete' enabled. When + 'autocomplete' is enabled, this does not work if + 'ignorecase' is set without 'infercase'. + See also |preinserted()|. preview Show extra information about the currently selected completion in the preview window. Only works in combination with "menu" or "menuone". - Only "fuzzy", "popup", "popuphidden", "preinsert" and "preview" have - an effect when 'autocomplete' is enabled. + Only "fuzzy", "longest", "popup", "popuphidden", "preinsert" and + "preview" have an effect when 'autocomplete' is enabled. This option does not apply to |cmdline-completion|. See 'wildoptions' for that. diff --git a/runtime/doc/tags b/runtime/doc/tags index 9dc1f3dc5e..c59f848d56 100644 --- a/runtime/doc/tags +++ b/runtime/doc/tags @@ -9774,6 +9774,7 @@ postscript-print-util print.txt /*postscript-print-util* postscript-printing print.txt /*postscript-printing* pow() builtin.txt /*pow()* ppwiz.vim syntax.txt /*ppwiz.vim* +preinserted() builtin.txt /*preinserted()* press-enter message.txt /*press-enter* press-return message.txt /*press-return* prevcount-variable eval.txt /*prevcount-variable* diff --git a/runtime/doc/usr_41.txt b/runtime/doc/usr_41.txt index 813211e7c8..6ee10f433f 100644 --- a/runtime/doc/usr_41.txt +++ b/runtime/doc/usr_41.txt @@ -1,4 +1,4 @@ -*usr_41.txt* For Vim version 9.1. Last change: 2025 Aug 23 +*usr_41.txt* For Vim version 9.1. Last change: 2025 Sep 26 VIM USER MANUAL - by Bram Moolenaar @@ -1135,6 +1135,7 @@ Insert mode completion: *completion-functions* complete_info() get current completion information complete_match() get insert completion start match col and trigger text + preinserted() check if text is inserted after cursor pumvisible() check if the popup menu is displayed pum_getpos() position and size of popup menu if visible diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 1227641a26..5447b28c6a 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -1,4 +1,4 @@ -*version9.txt* For Vim version 9.1. Last change: 2025 Sep 22 +*version9.txt* For Vim version 9.1. Last change: 2025 Sep 26 VIM REFERENCE MANUAL by Bram Moolenaar @@ -41803,6 +41803,8 @@ Functions: ~ |matchstrlist()| all the matches of a pattern in a List of strings |ngettext()| lookup single/plural message translation |popup_setbuf()| switch to a different buffer in a popup +|preinserted()| whether preinserted text has been inserted during + completion (see 'completeopt') |str2blob()| convert a List of strings into a blob |test_null_tuple()| return a null tuple |tuple2list()| turn a Tuple of items into a List diff --git a/runtime/syntax/vim.vim b/runtime/syntax/vim.vim index 1d244b66c9..c618afe533 100644 --- a/runtime/syntax/vim.vim +++ b/runtime/syntax/vim.vim @@ -156,7 +156,7 @@ syn keyword vimFuncName contained abs acos add and append appendbufline argc arg syn keyword vimFuncName contained char2nr charclass charcol charidx chdir cindent clearmatches cmdcomplete_info col complete complete_add complete_check complete_info complete_match confirm copy cos cosh count cscope_connection cursor debugbreak deepcopy delete deletebufline did_filetype diff diff_filler diff_hlID digraph_get digraph_getlist digraph_set digraph_setlist echoraw empty environ err_teapot escape eval eventhandler executable execute exepath exists exists_compiled exp expand expandcmd extend extendnew feedkeys filecopy filereadable filewritable filter finddir findfile flatten flattennew float2nr floor fmod fnameescape fnamemodify foldclosed foldclosedend foldlevel foldtext foldtextresult foreach foreground fullcommand funcref function garbagecollect syn keyword vimFuncName contained get getbufinfo getbufline getbufoneline getbufvar getcellpixels getcellwidths getchangelist getchar getcharmod getcharpos getcharsearch getcharstr getcmdcomplpat getcmdcompltype getcmdline getcmdpos getcmdprompt getcmdscreenpos getcmdtype getcmdwintype getcompletion getcompletiontype getcurpos getcursorcharpos getcwd getenv getfontname getfperm getfsize getftime getftype getimstatus getjumplist getline getloclist getmarklist getmatches getmousepos getmouseshape getpid getpos getqflist getreg getreginfo getregion getregionpos getregtype getscriptinfo getstacktrace gettabinfo gettabvar gettabwinvar gettagstack gettext getwininfo getwinpos getwinposx getwinposy getwinvar glob glob2regpat globpath has has_key haslocaldir hasmapto syn keyword vimFuncName contained histadd histdel histget histnr hlID hlexists hlget hlset hostname iconv id indent index indexof input inputdialog inputlist inputrestore inputsave inputsecret insert instanceof interrupt invert isabsolutepath isdirectory isinf islocked isnan items job_getchannel job_info job_setoptions job_start job_status job_stop join js_decode js_encode json_decode json_encode keys keytrans len libcall libcallnr line line2byte lispindent list2blob list2str list2tuple listener_add listener_flush listener_remove localtime log log10 luaeval map maparg mapcheck maplist mapnew mapset match matchadd matchaddpos matcharg matchbufline matchdelete matchend matchfuzzy matchfuzzypos matchlist matchstr matchstrlist matchstrpos max menu_info min mkdir mode -syn keyword vimFuncName contained mzeval nextnonblank ngettext nr2char or pathshorten perleval popup_atcursor popup_beval popup_clear popup_close popup_create popup_dialog popup_filter_menu popup_filter_yesno popup_findecho popup_findinfo popup_findpreview popup_getoptions popup_getpos popup_hide popup_list popup_locate popup_menu popup_move popup_notification popup_setbuf popup_setoptions popup_settext popup_show pow prevnonblank printf prompt_getprompt prompt_setcallback prompt_setinterrupt prompt_setprompt prop_add prop_add_list prop_clear prop_find prop_list prop_remove prop_type_add prop_type_change prop_type_delete prop_type_get prop_type_list pum_getpos pumvisible py3eval pyeval pyxeval rand range readblob readdir readdirex readfile reduce reg_executing +syn keyword vimFuncName contained mzeval nextnonblank ngettext nr2char or pathshorten perleval popup_atcursor popup_beval popup_clear popup_close popup_create popup_dialog popup_filter_menu popup_filter_yesno popup_findecho popup_findinfo popup_findpreview popup_getoptions popup_getpos popup_hide popup_list popup_locate popup_menu popup_move popup_notification popup_setbuf popup_setoptions popup_settext popup_show pow preinserted prevnonblank printf prompt_getprompt prompt_setcallback prompt_setinterrupt prompt_setprompt prop_add prop_add_list prop_clear prop_find prop_list prop_remove prop_type_add prop_type_change prop_type_delete prop_type_get prop_type_list pum_getpos pumvisible py3eval pyeval pyxeval rand range readblob readdir readdirex readfile reduce reg_executing syn keyword vimFuncName contained reg_recording reltime reltimefloat reltimestr remote_expr remote_foreground remote_peek remote_read remote_send remote_startserver remove rename repeat resolve reverse round rubyeval screenattr screenchar screenchars screencol screenpos screenrow screenstring search searchcount searchdecl searchpair searchpairpos searchpos server2client serverlist setbufline setbufvar setcellwidths setcharpos setcharsearch setcmdline setcmdpos setcursorcharpos setenv setfperm setline setloclist setmatches setpos setqflist setreg settabvar settabwinvar settagstack setwinvar sha256 shellescape shiftwidth sign_define sign_getdefined sign_getplaced sign_jump sign_place sign_placelist sign_undefine sign_unplace sign_unplacelist simplify sin sinh slice syn keyword vimFuncName contained sort sound_clear sound_playevent sound_playfile sound_stop soundfold spellbadword spellsuggest split sqrt srand state str2blob str2float str2list str2nr strcharlen strcharpart strchars strdisplaywidth strftime strgetchar stridx string strlen strpart strptime strridx strtrans strutf16len strwidth submatch substitute swapfilelist swapinfo swapname synID synIDattr synIDtrans synconcealed synstack system systemlist tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname term_dumpdiff term_dumpload term_dumpwrite term_getaltscreen term_getansicolors term_getattr term_getcursor term_getjob term_getline term_getscrolled term_getsize term_getstatus term_gettitle term_gettty term_list term_scrape term_sendkeys term_setansicolors syn keyword vimFuncName contained term_setapi term_setkill term_setrestore term_setsize term_start term_wait terminalprops test_alloc_fail test_autochdir test_feedinput test_garbagecollect_now test_garbagecollect_soon test_getvalue test_gui_event test_ignore_error test_mswin_event test_null_blob test_null_channel test_null_dict test_null_function test_null_job test_null_list test_null_partial test_null_string test_null_tuple test_option_not_set test_override test_refcount test_setmouse test_settime test_srand_seed test_unknown test_void timer_info timer_pause timer_start timer_stop timer_stopall tolower toupper tr trim trunc tuple2list type typename undofile undotree uniq uri_decode uri_encode utf16idx values virtcol virtcol2col visualmode wildmenumode wildtrigger diff --git a/src/edit.c b/src/edit.c index 400d047b8c..d564e2c9e6 100644 --- a/src/edit.c +++ b/src/edit.c @@ -693,11 +693,12 @@ edit( && stop_arrow() == OK) { ins_compl_delete(); - if (ins_compl_has_preinsert() - && ins_compl_autocomplete_enabled()) - (void)ins_compl_insert(FALSE, TRUE); - else - (void)ins_compl_insert(FALSE, FALSE); + ins_compl_insert(FALSE, !ins_compl_has_preinsert()); + if (ins_compl_preinsert_longest()) + { + ins_compl_init_get_longest(); + continue; + } } // Delete preinserted text when typing special chars else if (IS_WHITE_NL_OR_NUL(c) && ins_compl_preinsert_effect()) diff --git a/src/evalfunc.c b/src/evalfunc.c index c775240c3b..b5c5bb9087 100644 --- a/src/evalfunc.c +++ b/src/evalfunc.c @@ -2655,6 +2655,8 @@ static const funcentry_T global_functions[] = ret_number, PROP_FUNC(f_popup_show)}, {"pow", 2, 2, FEARG_1, arg2_float_or_nr, ret_float, f_pow}, + {"preinserted", 0, 0, 0, NULL, + ret_number_bool, f_preinserted}, {"prevnonblank", 1, 1, FEARG_1, arg1_lnum, ret_number, f_prevnonblank}, {"printf", 1, 19, FEARG_2, arg119_printf, diff --git a/src/insexpand.c b/src/insexpand.c index db6efdcf92..bf28bb2c13 100644 --- a/src/insexpand.c +++ b/src/insexpand.c @@ -210,7 +210,7 @@ static int compl_autocomplete = FALSE; // whether autocompletion is active static int compl_timeout_ms = COMPL_INITIAL_TIMEOUT_MS; static int compl_time_slice_expired = FALSE; // time budget exceeded for current source static int compl_from_nonkeyword = FALSE; // completion started from non-keyword -static int compl_autocomplete_preinsert = FALSE; // apply preinsert highlight +static int compl_hi_on_autocompl_longest = FALSE; // apply "PreInsert" highlight // Halve the current completion timeout, simulating exponential decay. #define COMPL_MIN_TIMEOUT_MS 5 @@ -853,6 +853,18 @@ is_nearest_active(void) && !(flags & COT_FUZZY); } +/* + * Returns TRUE if autocomplete is active and the pre-insert effect targets the + * longest prefix. + */ + int +ins_compl_preinsert_longest(void) +{ + return compl_autocomplete + && (get_cot_flags() & (COT_LONGEST | COT_PREINSERT | COT_FUZZY)) + == COT_LONGEST; +} + /* * Add a match to the list of matches. The arguments are: * str - text of the match to add @@ -1025,7 +1037,8 @@ ins_compl_add( compl_curr_match = match; // Find the longest common string if still doing that. - if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode()) + if (compl_get_longest && (flags & CP_ORIGINAL_TEXT) == 0 && !cfc_has_mode() + && !ins_compl_preinsert_longest()) ins_compl_longest_match(match); return OK; @@ -1084,17 +1097,14 @@ ins_compl_leader_len(void) ins_compl_col_range_attr(linenr_T lnum, int col) { int start_col; + int has_preinsert = ins_compl_has_preinsert() + || ins_compl_preinsert_longest(); int attr; - int has_preinsert = ins_compl_has_preinsert(); if ((get_cot_flags() & COT_FUZZY) - || (!has_preinsert - && (attr = syn_name2attr((char_u *)"ComplMatchIns")) == 0) - || (!compl_autocomplete && has_preinsert - && (attr = syn_name2attr((char_u *)"PreInsert")) == 0) - || (compl_autocomplete - && (!compl_autocomplete_preinsert - || (attr = syn_name2attr((char_u *)"PreInsert")) == 0))) + || (!compl_hi_on_autocompl_longest && ins_compl_preinsert_longest()) + || (attr = syn_name2attr(has_preinsert + ? (char_u *)"PreInsert" : (char_u *)"ComplMatchIns")) == 0) return -1; start_col = compl_col + (int)ins_compl_leader_len(); @@ -1613,7 +1623,7 @@ ins_compl_build_pum(void) int cur = -1; unsigned int cur_cot_flags = get_cot_flags(); int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0 - || compl_autocomplete; + || (compl_autocomplete && !ins_compl_has_preinsert()); int fuzzy_filter = (cur_cot_flags & COT_FUZZY) != 0; compl_T *match_head = NULL; compl_T *match_tail = NULL; @@ -2352,6 +2362,8 @@ ins_compl_len(void) ins_compl_has_preinsert(void) { int cur_cot_flags = get_cot_flags(); + if (compl_autocomplete && p_ic && !p_inf) + return FALSE; return !compl_autocomplete ? (cur_cot_flags & (COT_PREINSERT | COT_FUZZY | COT_MENUONE)) == (COT_PREINSERT | COT_MENUONE) @@ -2365,21 +2377,12 @@ ins_compl_has_preinsert(void) int ins_compl_preinsert_effect(void) { - if (!ins_compl_has_preinsert()) + if (!ins_compl_has_preinsert() && !ins_compl_preinsert_longest()) return FALSE; return curwin->w_cursor.col < compl_ins_end_col; } -/* - * Returns TRUE if autocompletion is active. - */ - int -ins_compl_autocomplete_enabled(void) -{ - return compl_autocomplete; -} - /* * Delete one character before the cursor and show the subset of the matches * that match the word that is now before the cursor. @@ -2425,7 +2428,7 @@ ins_compl_bs(void) } // Clear selection if a menu item is currently selected in autocompletion - if (compl_autocomplete && compl_first_match) + if (compl_autocomplete && compl_first_match && !ins_compl_has_preinsert()) compl_shown_match = compl_first_match; ins_compl_new_leader(); @@ -2538,21 +2541,14 @@ ins_compl_new_leader(void) if (!compl_interrupted) show_pum(save_w_wrow, save_w_leftcol); - compl_autocomplete_preinsert = FALSE; // Don't let Enter select the original text when there is no popup menu. if (compl_match_array == NULL) compl_enter_selects = FALSE; else if (ins_compl_has_preinsert() && compl_leader.length > 0) - { - if (compl_started && compl_autocomplete - && !ins_compl_preinsert_effect()) - { - if (ins_compl_insert(TRUE, TRUE) == OK) - compl_autocomplete_preinsert = TRUE; - } - else - (void)ins_compl_insert(TRUE, FALSE); - } + ins_compl_insert(TRUE, FALSE); + else if (compl_started && ins_compl_preinsert_longest() + && compl_leader.length > 0 && !ins_compl_preinsert_effect()) + ins_compl_insert(TRUE, TRUE); } /* @@ -2602,14 +2598,14 @@ ins_compl_addleader(int c) ins_compl_restart(); // When 'always' is set, don't reset compl_leader. While completing, - // cursor doesn't point original position, changing compl_leader would - // break redo. + // cursor doesn't point to the original position, changing compl_leader + // would break redo. if (!compl_opt_refresh_always) { VIM_CLEAR_STRING(compl_leader); compl_leader.length = (size_t)(curwin->w_cursor.col - compl_col); compl_leader.string = vim_strnsave(ml_get_curline() + compl_col, - compl_leader.length); + compl_leader.length); if (compl_leader.string == NULL) { compl_leader.length = 0; @@ -5104,7 +5100,7 @@ get_next_default_completion(ins_compl_next_state_T *st, pos_T *start_pos) if (!in_fuzzy_collect) ptr = ins_compl_get_next_word_or_line(st->ins_buf, st->cur_match_pos, &len, &cont_s_ipos); - if (ptr == NULL || (!compl_autocomplete && ins_compl_has_preinsert() + if (ptr == NULL || (ins_compl_has_preinsert() && STRCMP(ptr, compl_pattern.string) == 0)) continue; @@ -5666,7 +5662,7 @@ ins_compl_get_exp(pos_T *ini) } may_trigger_modechanged(); - if (is_nearest_active()) + if (is_nearest_active() && !ins_compl_has_preinsert()) sort_compl_match_list(cp_compare_nearest); return match_count; @@ -5907,6 +5903,22 @@ find_common_prefix(size_t *prefix_len, int curbuf_only) if (len > (int)ins_compl_leader_len()) { + // Avoid inserting text that duplicates the text already present + // after the cursor. + if (len == (int)STRLEN(first)) + { + char_u *line = ml_get_curline(); + char_u *p = line + curwin->w_cursor.col; + if (p && !IS_WHITE_OR_NUL(*p)) + { + char_u *end = find_word_end(p); + int text_len = end - p; + if (text_len > 0 + && text_len < (len - (int)ins_compl_leader_len()) + && STRNCMP(first + len - text_len, p, text_len) == 0) + len -= text_len; + } + } *prefix_len = (size_t)len; return first; } @@ -5917,11 +5929,11 @@ find_common_prefix(size_t *prefix_len, int curbuf_only) * Insert the new text being completed. * "move_cursor" is used when 'completeopt' includes "preinsert" and when TRUE * cursor needs to move back from the inserted text to the compl_leader. - * When "preinsert_prefix" is TRUE the longest common prefix is inserted - * instead of shown match. + * When "insert_prefix" is TRUE the longest common prefix is inserted instead + * of shown match. */ - int -ins_compl_insert(int move_cursor, int preinsert_prefix) + void +ins_compl_insert(int move_cursor, int insert_prefix) { int compl_len = get_compl_len(); int preinsert = ins_compl_has_preinsert(); @@ -5930,14 +5942,17 @@ ins_compl_insert(int move_cursor, int preinsert_prefix) size_t leader_len = ins_compl_leader_len(); char_u *has_multiple = vim_strchr(cp_str, '\n'); - if (preinsert_prefix) + if (insert_prefix) { cp_str = find_common_prefix(&cp_str_len, FALSE); if (cp_str == NULL) { cp_str = find_common_prefix(&cp_str_len, TRUE); if (cp_str == NULL) - return FAIL; + { + cp_str = compl_shown_match->cp_str.string; + cp_str_len = compl_shown_match->cp_str.length; + } } } // Since completion sources may provide matches with varying start @@ -5970,13 +5985,13 @@ ins_compl_insert(int move_cursor, int preinsert_prefix) else { ins_compl_insert_bytes(cp_str + compl_len, - preinsert_prefix ? (int)cp_str_len - compl_len : -1); - if (preinsert && move_cursor) + insert_prefix ? (int)cp_str_len - compl_len : -1); + if ((preinsert || insert_prefix) && move_cursor) curwin->w_cursor.col -= (colnr_T)(cp_str_len - leader_len); } } if (match_at_original_text(compl_shown_match) - || (preinsert && !compl_autocomplete)) + || (preinsert && !insert_prefix)) compl_used_match = FALSE; else compl_used_match = TRUE; @@ -5987,7 +6002,7 @@ ins_compl_insert(int move_cursor, int preinsert_prefix) set_vim_var_dict(VV_COMPLETED_ITEM, dict); } #endif - return OK; + compl_hi_on_autocompl_longest = insert_prefix && move_cursor; } /* @@ -6071,7 +6086,7 @@ find_next_completion_match( compl_T *found_compl = NULL; unsigned int cur_cot_flags = get_cot_flags(); int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0 - || compl_autocomplete; + || (compl_autocomplete && !ins_compl_has_preinsert()); int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; string_T *leader; @@ -6201,7 +6216,7 @@ ins_compl_next( buf_T *orig_curbuf = curbuf; unsigned int cur_cot_flags = get_cot_flags(); int compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0 - || compl_autocomplete; + || (compl_autocomplete && !ins_compl_has_preinsert()); int compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; int compl_preinsert = ins_compl_has_preinsert(); @@ -6245,38 +6260,22 @@ ins_compl_next( return -1; } - compl_autocomplete_preinsert = FALSE; - // Insert the text of the new completion, or the compl_leader. - if (compl_no_insert && !started) + if (!started && ins_compl_preinsert_longest()) + ins_compl_insert(TRUE, TRUE); + else if (compl_no_insert && !started && !compl_preinsert) { - int insert_orig = !compl_preinsert; - if (compl_preinsert && compl_autocomplete) - { - if (ins_compl_insert(TRUE, TRUE) == OK) - compl_autocomplete_preinsert = TRUE; - else - insert_orig = TRUE; - } - if (insert_orig) - ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1); + ins_compl_insert_bytes(compl_orig_text.string + get_compl_len(), -1); compl_used_match = FALSE; } else if (insert_match) { if (!compl_get_longest || compl_used_match) { - int none_selected = match_at_original_text(compl_shown_match); - if (compl_preinsert && compl_autocomplete - && none_selected) - { - if (ins_compl_insert(none_selected, TRUE) == OK) - compl_autocomplete_preinsert = none_selected; - else - (void)ins_compl_insert(FALSE, FALSE); - } - else - (void)ins_compl_insert(!compl_autocomplete, FALSE); + int preinsert_longest = ins_compl_preinsert_longest() + && match_at_original_text(compl_shown_match); // none selected + ins_compl_insert(compl_preinsert || preinsert_longest, + preinsert_longest); } else ins_compl_insert_bytes(compl_leader.string + get_compl_len(), -1); @@ -6417,8 +6416,8 @@ ins_compl_check_keys(int frequency, int in_compl_func) check_elapsed_time(); } - if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT) - && !compl_autocomplete) + if (compl_pending && !got_int && !(cot_flags & COT_NOINSERT) + && (!compl_autocomplete || ins_compl_has_preinsert())) { // Insert the first match immediately and advance compl_shown_match, // before finding other matches. @@ -7750,3 +7749,13 @@ cpt_compl_refresh(void) compl_matches = ins_compl_make_cyclic(); #endif } + +/* + * "preinserted()" function + */ + void +f_preinserted(typval_T *argvars UNUSED, typval_T *rettv UNUSED) +{ + if (ins_compl_preinsert_effect()) + rettv->vval.v_number = 1; +} diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro index fcf64378c0..c10d2046b5 100644 --- a/src/proto/insexpand.pro +++ b/src/proto/insexpand.pro @@ -48,7 +48,6 @@ colnr_T ins_compl_col(void); int ins_compl_len(void); int ins_compl_has_preinsert(void); int ins_compl_preinsert_effect(void); -int ins_compl_autocomplete_enabled(void); int ins_compl_bs(void); void ins_compl_addleader(int c); void ins_compl_addfrommatch(void); @@ -70,10 +69,12 @@ void f_complete_check(typval_T *argvars, typval_T *rettv); void f_complete_match(typval_T *argvars, typval_T *rettv); void f_complete_info(typval_T *argvars, typval_T *rettv); void ins_compl_delete(void); -int ins_compl_insert(int move_cursor, int preinsert_prefix); +void ins_compl_insert(int move_cursor, int insert_longest); void ins_compl_check_keys(int frequency, int in_compl_func); int ins_complete(int c, int enable_pum); void ins_compl_enable_autocomplete(void); void free_insexpand_stuff(void); int ins_compl_has_autocomplete(void); +int ins_compl_preinsert_longest(void); +void f_preinserted(typval_T *argvars UNUSED, typval_T *rettv UNUSED); /* vim: set ft=c : */ diff --git a/src/testdir/dumps/Test_autocompletedelay_7.dump b/src/testdir/dumps/Test_autocompletedelay_7.dump index 6c43fa46e9..08f4059c68 100644 --- a/src/testdir/dumps/Test_autocompletedelay_7.dump +++ b/src/testdir/dumps/Test_autocompletedelay_7.dump @@ -1,10 +1,10 @@ |f+0&#ffffff0|o@1| @71 |f|o@1|b|a|r| @68 |f|o@1|b|a|r|b|a|z| @65 -|f>o+0#00e0003&@1| +0#0000000&@71 -|f+0#0000001#e0e0e08|o@1| @11| +0#4040ff13#ffffff0@59 +|f|o@1> @71 +|f+0#0000001#ffd7ff255|o@1| @11| +0#4040ff13#ffffff0@59 |f+0#0000001#ffd7ff255|o@1|b|a|r| @8| +0#4040ff13#ffffff0@59 |f+0#0000001#ffd7ff255|o@1|b|a|r|b|a|z| @5| +0#4040ff13#ffffff0@59 |~| @73 |~| @73 -|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |m+0#00e0003&|a|t|c|h| |1| |o|f| |3| +0#0000000&@33 +|-+2#0000000&@1| |K|e|y|w|o|r|d| |c|o|m|p|l|e|t|i|o|n| |(|^|N|^|P|)| |B+0#e000002&|a|c|k| |a|t| |o|r|i|g|i|n|a|l| +0#0000000&@29 diff --git a/src/testdir/test_ins_complete.vim b/src/testdir/test_ins_complete.vim index 6f6232f963..ba449f2dbc 100644 --- a/src/testdir/test_ins_complete.vim +++ b/src/testdir/test_ins_complete.vim @@ -119,10 +119,9 @@ endfunc func Test_omni_dash() func Omni(findstart, base) if a:findstart - return 5 + return 5 else - echom a:base - return ['-help', '-v'] + return ['-help', '-v'] endif endfunc set omnifunc=Omni @@ -4120,6 +4119,145 @@ func Test_completeopt_preinsert() delfunc Omni_test endfunc +func Test_autocomplete_completeopt_preinsert() + func Omni_test(findstart, base) + if a:findstart + return col(".") - 1 + endif + return [#{word: "fobar"}, #{word: "foobar"}] + endfunc + set omnifunc=Omni_test complete+=o + set completeopt=preinsert autocomplete + " set completeopt=preinsert,menuone autocomplete + func GetLine() + let g:line = getline('.') + let g:col = col('.') + endfunc + + call test_override("char_avail", 1) + new + inoremap =GetLine() + call feedkeys("Sfo\\", 'tx') + call assert_equal("fobar", g:line) + call assert_equal(3, g:col) + + call feedkeys("Sfoo\\", 'tx') + call assert_equal("foobar", g:line) + + call feedkeys("Sfoo\\\", 'tx') + call assert_equal("", getline('.')) + + " delete a character + call feedkeys("Sfoo\b\\", 'tx') + call assert_equal("fobar", g:line) + call assert_equal(4, g:col) + + set complete& + %d + call setline(1, ['fobar', 'foobar']) + + call feedkeys("Gofoo\\\\", 'tx') + call assert_equal("fobar", g:line) + call assert_equal(2, g:col) + + call feedkeys("Shello wo\\\f\\", 'tx') + call assert_equal("hello fobar wo", g:line) + call assert_equal(9, g:col) + + call feedkeys("Shello wo\\\f\\\", 'tx') + call assert_equal("hello wo", g:line) + call assert_equal(8, g:col) + + call feedkeys("Shello wo\\\foo\\", 'tx') + call assert_equal("hello foobar wo", g:line) + call assert_equal(11, g:col) + + call feedkeys("Shello wo\\\foo\b\\", 'tx') + call assert_equal("hello fobar wo", g:line) + call assert_equal(11, g:col) + + " confirm + call feedkeys("Sf\", 'tx') + call assert_equal("fobar", getline('.')) + call assert_equal(5, col('.')) + + " cancel + call feedkeys("Sfo\", 'tx') + call assert_equal("fo", getline('.')) + call assert_equal(2, col('.')) + + " delete preinsert part + call feedkeys("Sfo ", 'tx') + call assert_equal("fo ", getline('.')) + call assert_equal(3, col('.')) + + " can not work with fuzzy + set cot+=fuzzy + call feedkeys("Sf", 'tx') + call assert_equal("f", getline('.')) + set cot-=fuzzy + + " does not work with 'ignorecase' unless 'infercase' is also enabled + %d + call setline(1, ['FIX', 'fobar', 'foobar']) + set ignorecase + call feedkeys("Gof\\", 'tx') + call assert_equal("f", g:line) " should not produce 'FIX' + set infercase + call feedkeys("Gof\\", 'tx') + call assert_equal("fix", g:line) + set ignorecase& infercase& + + %delete _ + let &l:undolevels = &l:undolevels + normal! ifoo + let &l:undolevels = &l:undolevels + normal! obar + let &l:undolevels = &l:undolevels + normal! obaz + let &l:undolevels = &l:undolevels + + func CheckUndo() + let g:errmsg = '' + call assert_equal(['foo', 'bar', 'baz'], getline(1, '$')) + undo + call assert_equal(['foo', 'bar'], getline(1, '$')) + undo + call assert_equal(['foo'], getline(1, '$')) + undo + call assert_equal([''], getline(1, '$')) + later 3 + call assert_equal(['foo', 'bar', 'baz'], getline(1, '$')) + call assert_equal('', v:errmsg) + endfunc + + " Check that switching buffer with "preinsert" doesn't corrupt undo. + new + setlocal bufhidden=wipe + inoremap enew! + call feedkeys("if\\", 'tx') + bwipe! + call CheckUndo() + + " Check that closing window with "preinsert" doesn't corrupt undo. + new + setlocal bufhidden=wipe + inoremap close! + call feedkeys("if\\", 'tx') + call CheckUndo() + + %delete _ + delfunc CheckUndo + + bw! + set cot& + set omnifunc& + set autocomplete& + call test_override("char_avail", 0) + delfunc Omni_test + delfunc GetLine +endfunc + " Check that mark positions are correct after triggering multiline completion. func Test_complete_multiline_marks() func Omni_test(findstart, base) @@ -5583,7 +5721,7 @@ func Test_autocompletedelay() call VerifyScreenDump(buf, 'Test_autocompletedelay_6', {}) " During delay wait, user can open menu using CTRL_N completion - call term_sendkeys(buf, "\:set completeopt=menuone,preinsert\") + call term_sendkeys(buf, "\:set completeopt=menuone,longest\") call term_sendkeys(buf, "Sf\") call VerifyScreenDump(buf, 'Test_autocompletedelay_7', {}) @@ -5607,16 +5745,18 @@ func Test_autocompletedelay() call StopVimInTerminal(buf) endfunc -func Test_autocomplete_completeopt_preinsert() +" Preinsert longest prefix when autocomplete +func Test_autocomplete_longest() func GetLine() let g:line = getline('.') let g:col = col('.') + let g:preinserted = preinserted() endfunc call test_override("char_avail", 1) new inoremap =GetLine() - set completeopt=preinsert autocomplete + set completeopt=longest autocomplete call setline(1, ["foobar", "foozbar"]) call feedkeys("Go\", 'tx') @@ -5648,10 +5788,15 @@ func Test_autocomplete_completeopt_preinsert() call DoTest("f\", 'foobar', 7) call DoTest("fo\\", 'foobar', 7) + " to accept preinserted text + inoremap =pumvisible() call DoTest("zar\0f", 'foozar', 2) call DoTest("zar\0f\", 'foozar', 4) + call DoTest("zar\0f\\", 'foo1zar', 5) call DoTest("zar\0f\\", 'foozar', 3) + call DoTest("zar\0f\\\", 'fo1zar', 4) + " Select items in menu call DoTest("zar\0f\", 'foozbarzar', 8) call DoTest("zar\0f\\", 'foobarzar', 7) call DoTest("zar\0f\\\", 'foozar', 2) @@ -5665,6 +5810,34 @@ func Test_autocomplete_completeopt_preinsert() call DoTest("f", 'f', 2) set cot-=fuzzy + " preinserted() + call DoTest("f", 'foo', 2) + call assert_equal(1, g:preinserted) + call assert_equal(0, preinserted()) + call DoTest("f\", '', 1) + call assert_equal(0, g:preinserted) + call DoTest("f ", 'f ', 3) + call assert_equal(0, g:preinserted) + call DoTest("foob", 'foobar', 5) + call assert_equal(1, g:preinserted) + call DoTest("foob\\x", 'fox', 4) + call assert_equal(0, g:preinserted) + + " Complete non-keyword + func Omni(findstart, base) + if a:findstart + return 5 + else + return ['xyz:'] + endif + endfunc + set omnifunc=Omni + set cpt+=o + call DoTest("xyz:", "xyz:xyz:", 5) + call DoTest("xyz:\\", "xyz:", 3) + set omnifunc& cpt& + delfunc Omni + " leader should match prefix of inserted word %delete set smartcase ignorecase @@ -5675,6 +5848,16 @@ func Test_autocomplete_completeopt_preinsert() call assert_equal('FOO', g:line) set smartcase& ignorecase& + " avoid repeating text that is already present after the cursor + %delete + call setline(1, ["foobarbaz", ""]) + call feedkeys($"G", 'tx') + call DoTest("baz\0f", "foobarbaz", 2) + call feedkeys($"Sxfoozar\\", 'tx') + call DoTest("baz\0f", "foobarbaz", 2) + call feedkeys($"Sfoozar\\", 'tx') + call DoTest("baz\0f", "foobaz", 2) + " Verify that redo (dot) works %delete call setline(1, ["foobar", "foozbar", "foobaz", "changed", "change"]) @@ -5711,7 +5894,7 @@ func Test_autocomplete_completeopt_preinsert() call assert_equal('', v:errmsg) endfunc - " Check that switching buffer with "preinsert" doesn't corrupt undo. + " Check that switching buffer with "longest" doesn't corrupt undo. new setlocal bufhidden=wipe inoremap enew! @@ -5719,7 +5902,7 @@ func Test_autocomplete_completeopt_preinsert() bwipe! call CheckUndo() - " Check that closing window with "preinsert" doesn't corrupt undo. + " Check that closing window with "longest" doesn't corrupt undo. new setlocal bufhidden=wipe inoremap close! diff --git a/src/version.c b/src/version.c index 4d820a9cb4..f5d1e62b9d 100644 --- a/src/version.c +++ b/src/version.c @@ -729,6 +729,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 1797, /**/ 1796, /**/ -- 2.47.3