diff options
-rw-r--r-- | runtime/doc/options.txt | 2 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/options.lua | 2 | ||||
-rw-r--r-- | runtime/optwin.vim | 4 | ||||
-rw-r--r-- | src/nvim/buffer.c | 1 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/insexpand.c | 51 | ||||
-rw-r--r-- | src/nvim/option.c | 6 | ||||
-rw-r--r-- | src/nvim/option_vars.h | 13 | ||||
-rw-r--r-- | src/nvim/options.lua | 2 | ||||
-rw-r--r-- | src/nvim/optionstr.c | 19 | ||||
-rw-r--r-- | src/nvim/popupmenu.c | 7 | ||||
-rw-r--r-- | test/old/testdir/test_ins_complete.vim | 68 |
12 files changed, 137 insertions, 40 deletions
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index c33b8ac03c..f02d3c9741 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -1495,7 +1495,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'completeopt'* *'cot'* 'completeopt' 'cot' string (default "menu,preview") - global + global or local to buffer |global-local| A comma-separated list of options for Insert mode completion |ins-completion|. The supported values are: diff --git a/runtime/lua/vim/_meta/options.lua b/runtime/lua/vim/_meta/options.lua index 0b4294ae4b..155c93726b 100644 --- a/runtime/lua/vim/_meta/options.lua +++ b/runtime/lua/vim/_meta/options.lua @@ -1084,6 +1084,8 @@ vim.bo.cfu = vim.bo.completefunc --- @type string vim.o.completeopt = "menu,preview" vim.o.cot = vim.o.completeopt +vim.bo.completeopt = vim.o.completeopt +vim.bo.cot = vim.bo.completeopt vim.go.completeopt = vim.o.completeopt vim.go.cot = vim.go.completeopt diff --git a/runtime/optwin.vim b/runtime/optwin.vim index 5b5b33e4ad..3b874f4cda 100644 --- a/runtime/optwin.vim +++ b/runtime/optwin.vim @@ -1,7 +1,7 @@ " These commands create the option window. " " Maintainer: The Vim Project <https://github.com/vim/vim> -" Last Change: 2023 Aug 31 +" Last Change: 2024 Jun 05 " Former Maintainer: Bram Moolenaar <Bram@vim.org> " If there already is an option window, jump to that one. @@ -723,7 +723,7 @@ if has("insert_expand") call append("$", "\t" .. s:local_to_buffer) call <SID>OptionL("cpt") call <SID>AddOption("completeopt", gettext("whether to use a popup menu for Insert mode completion")) - call <SID>OptionG("cot", &cot) + call <SID>OptionL("cot") call <SID>AddOption("pumheight", gettext("maximum height of the popup menu")) call <SID>OptionG("ph", &ph) call <SID>AddOption("pumwidth", gettext("minimum width of the popup menu")) diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index c0fbc36787..57245ce3f5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -2077,6 +2077,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff) clear_string_option(&buf->b_p_lop); clear_string_option(&buf->b_p_cinsd); clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cot); clear_string_option(&buf->b_p_cpt); clear_string_option(&buf->b_p_cfu); callback_free(&buf->b_cfu_cb); diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 512247047c..221a86a907 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -533,6 +533,8 @@ struct file_buffer { char *b_p_cinsd; ///< 'cinscopedecls' char *b_p_com; ///< 'comments' char *b_p_cms; ///< 'commentstring' + char *b_p_cot; ///< 'completeopt' local value + unsigned b_cot_flags; ///< flags for 'completeopt' char *b_p_cpt; ///< 'complete' #ifdef BACKSLASH_IN_FILENAME char *b_p_csl; ///< 'completeslash' diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c index 9f39c7d581..574c9a9e3a 100644 --- a/src/nvim/insexpand.c +++ b/src/nvim/insexpand.c @@ -226,14 +226,6 @@ static char *compl_leader = NULL; static bool compl_get_longest = false; ///< put longest common string in compl_leader -static bool compl_no_insert = false; ///< false: select & insert - ///< true: noinsert -static bool compl_no_select = false; ///< false: select & insert - ///< true: noselect -static bool compl_longest = false; ///< false: insert full match - ///< true: insert longest prefix -static bool compl_fuzzy_match = false; ///< true: fuzzy match enabled - /// Selected one of the matches. When false the match was edited or using the /// longest common string. static bool compl_used_match; @@ -1051,26 +1043,10 @@ bool ins_compl_long_shown_match(void) && (colnr_T)strlen(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col; } -/// Set variables that store noselect and noinsert behavior from the -/// 'completeopt' value. -void completeopt_was_set(void) +/// Get the local or global value of 'completeopt' flags. +unsigned get_cot_flags(void) { - compl_no_insert = false; - compl_no_select = false; - compl_longest = false; - compl_fuzzy_match = false; - if (strstr(p_cot, "noselect") != NULL) { - compl_no_select = true; - } - if (strstr(p_cot, "noinsert") != NULL) { - compl_no_insert = true; - } - if (strstr(p_cot, "longest") != NULL) { - compl_longest = true; - } - if (strstr(p_cot, "fuzzy") != NULL) { - compl_fuzzy_match = true; - } + return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags; } /// "compl_match_array" points the currently displayed list of entries in the @@ -1094,7 +1070,7 @@ bool pum_wanted(void) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { // "completeopt" must contain "menu" or "menuone" - return vim_strchr(p_cot, 'm') != NULL; + return (get_cot_flags() & COT_ANY_MENU) != 0; } /// Check that there are two or more matches to be shown in the popup menu. @@ -1113,7 +1089,7 @@ static bool pum_enough_matches(void) comp = comp->cp_next; } while (!is_first_match(comp)); - if (strstr(p_cot, "menuone") != NULL) { + if (get_cot_flags() & COT_MENUONE) { return i >= 1; } return i >= 2; @@ -1193,6 +1169,9 @@ static int ins_compl_build_pum(void) const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0; int max_fuzzy_score = 0; + unsigned cur_cot_flags = get_cot_flags(); + bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; do { // When 'completeopt' contains "fuzzy" and leader is not NULL or empty, @@ -2229,7 +2208,7 @@ bool ins_compl_prep(int c) // Set "compl_get_longest" when finding the first matches. if (ctrl_x_mode_not_defined_yet() || (ctrl_x_mode_normal() && !compl_started)) { - compl_get_longest = compl_longest; + compl_get_longest = (get_cot_flags() & COT_LONGEST) != 0; compl_used_match = true; } @@ -2649,6 +2628,10 @@ static void restore_orig_extmarks(void) static void set_completion(colnr_T startcol, list_T *list) { int flags = CP_ORIGINAL_TEXT; + unsigned cur_cot_flags = get_cot_flags(); + bool compl_longest = (cur_cot_flags & COT_LONGEST) != 0; + bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; + bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; // If already doing completions stop it. if (ctrl_x_mode_not_default()) { @@ -3692,6 +3675,9 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a { bool found_end = false; compl_T *found_compl = NULL; + unsigned cur_cot_flags = get_cot_flags(); + bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0; + bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; while (--todo >= 0) { if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) { @@ -3794,6 +3780,9 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match int todo = count; const bool started = compl_started; buf_T *const orig_curbuf = curbuf; + unsigned cur_cot_flags = get_cot_flags(); + bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0; + bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0; // When user complete function return -1 for findstart which is next // time of 'always', compl_shown_match become NULL. @@ -3932,7 +3921,7 @@ void ins_compl_check_keys(int frequency, bool in_compl_func) } } } - if (compl_pending != 0 && !got_int && !compl_no_insert) { + if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)) { int todo = compl_pending > 0 ? compl_pending : -compl_pending; compl_pending = 0; diff --git a/src/nvim/option.c b/src/nvim/option.c index 32fabd4141..41b9aaf9a7 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -4570,6 +4570,8 @@ void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win) return &(buf->b_p_def); case PV_INC: return &(buf->b_p_inc); + case PV_COT: + return &(buf->b_p_cot); case PV_DICT: return &(buf->b_p_dict); case PV_TSR: @@ -4653,6 +4655,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win) return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var; case PV_INC: return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var; + case PV_COT: + return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var; case PV_DICT: return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var; case PV_TSR: @@ -5332,6 +5336,8 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_inc = empty_string_option; buf->b_p_inex = xstrdup(p_inex); COPY_OPT_SCTX(buf, BV_INEX); + buf->b_p_cot = empty_string_option; + buf->b_cot_flags = 0; buf->b_p_dict = empty_string_option; buf->b_p_tsr = empty_string_option; buf->b_p_tsrfu = empty_string_option; diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h index c98c84d34e..bd0fe699d9 100644 --- a/src/nvim/option_vars.h +++ b/src/nvim/option_vars.h @@ -430,6 +430,19 @@ EXTERN char *p_cpt; ///< 'complete' EXTERN OptInt p_columns; ///< 'columns' EXTERN int p_confirm; ///< 'confirm' EXTERN char *p_cot; ///< 'completeopt' +EXTERN unsigned cot_flags; ///< flags from 'completeopt' +// Keep in sync with p_cot_values in optionstr.c +#define COT_MENU 0x001 +#define COT_MENUONE 0x002 +#define COT_ANY_MENU 0x003 // combination of menu flags +#define COT_LONGEST 0x004 // false: insert full match, + // true: insert longest prefix +#define COT_PREVIEW 0x008 +#define COT_POPUP 0x010 +#define COT_ANY_PREVIEW 0x018 // combination of preview flags +#define COT_NOINSERT 0x020 // false: select & insert, true: noinsert +#define COT_NOSELECT 0x040 // false: select & insert, true: noselect +#define COT_FUZZY 0x080 // true: fuzzy match enabled #ifdef BACKSLASH_IN_FILENAME EXTERN char *p_csl; ///< 'completeslash' #endif diff --git a/src/nvim/options.lua b/src/nvim/options.lua index aa43c4cf3d..f323926015 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -1466,7 +1466,7 @@ return { expand_cb = 'expand_set_completeopt', full_name = 'completeopt', list = 'onecomma', - scope = { 'global' }, + scope = { 'global', 'buffer' }, short_desc = N_('options for Insert mode completion'), type = 'string', varname = 'p_cot', diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c index 050cb1fe98..4cd830a2fc 100644 --- a/src/nvim/optionstr.c +++ b/src/nvim/optionstr.c @@ -158,6 +158,7 @@ void didset_string_options(void) opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true); opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true); opt_strings_flags(p_bo, p_bo_values, &bo_flags, true); + opt_strings_flags(p_cot, p_cot_values, &cot_flags, true); opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true); opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); @@ -219,6 +220,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_ft); check_string_option(&buf->b_p_cinw); check_string_option(&buf->b_p_cinsd); + check_string_option(&buf->b_p_cot); check_string_option(&buf->b_p_cpt); check_string_option(&buf->b_p_cfu); check_string_option(&buf->b_p_ofu); @@ -993,10 +995,23 @@ int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches) /// The 'completeopt' option is changed. const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED) { - if (check_opt_strings(p_cot, p_cot_values, true) != OK) { + buf_T *buf = (buf_T *)args->os_buf; + char *cot = p_cot; + unsigned *flags = &cot_flags; + + if (args->os_flags & OPT_LOCAL) { + cot = buf->b_p_cot; + flags = &buf->b_cot_flags; + } + + if (check_opt_strings(cot, p_cot_values, true) != OK) { return e_invarg; } - completeopt_was_set(); + + if (opt_strings_flags(cot, p_cot_values, flags, true) != OK) { + return e_invarg; + } + return NULL; } diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c index bf2fe0f72c..324254a188 100644 --- a/src/nvim/popupmenu.c +++ b/src/nvim/popupmenu.c @@ -700,7 +700,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma } // delete the empty last line ml_delete_buf(buf, buf->b_ml.ml_line_count, false); - if (strstr(p_cot, "popup") != NULL) { + if (get_cot_flags() & COT_POPUP) { extmark_splice(buf, 1, 0, 1, 0, 0, buf->b_ml.ml_line_count, 0, inserted_bytes, kExtmarkNoUndo); } } @@ -795,7 +795,8 @@ static bool pum_set_selected(int n, int repeat) int prev_selected = pum_selected; pum_selected = n; - bool use_float = strstr(p_cot, "popup") != NULL; + unsigned cur_cot_flags = get_cot_flags(); + bool use_float = (cur_cot_flags & COT_POPUP) != 0; // when new leader add and info window is shown and no selected we still // need use the first index item to update the info float window position. bool force_select = use_float && pum_selected < 0 && win_float_find_preview(); @@ -861,7 +862,7 @@ static bool pum_set_selected(int n, int repeat) if ((pum_array[pum_selected].pum_info != NULL) && (Rows > 10) && (repeat <= 1) - && (vim_strchr(p_cot, 'p') != NULL)) { + && (cur_cot_flags & COT_ANY_PREVIEW)) { win_T *curwin_save = curwin; tabpage_T *curtab_save = curtab; diff --git a/test/old/testdir/test_ins_complete.vim b/test/old/testdir/test_ins_complete.vim index 573cd9729c..fd77da67f8 100644 --- a/test/old/testdir/test_ins_complete.vim +++ b/test/old/testdir/test_ins_complete.vim @@ -884,6 +884,74 @@ func Test_complete_with_longest() bwipe! endfunc +" Test for buffer-local value of 'completeopt' +func Test_completeopt_buffer_local() + set completeopt=menu + new + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal bufhidden=hide + enew + call setline(1, ['foofoo', 'foobar', 'foobaz', '']) + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + setlocal completeopt= + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=longest + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=hide + buffer # + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-Y>", 'tnix') + call assert_equal('foofoo', getline('.')) + + setlocal completeopt+=fuzzy,noinsert + call assert_equal('menu,fuzzy,noinsert', &l:completeopt) + call assert_equal('menu,fuzzy,noinsert', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>bz\<C-Y>", 'tnix') + call assert_equal('foobaz', getline('.')) + + buffer # + call assert_equal('menu,longest', &l:completeopt) + call assert_equal('menu,longest', &completeopt) + call assert_equal('menu', &g:completeopt) + call feedkeys("Gccf\<C-X>\<C-N>\<C-X>\<C-Z>", 'tnix') + call assert_equal('foo', getline('.')) + + setlocal bufhidden=wipe + buffer! # + bwipe! + call assert_equal('', &l:completeopt) + call assert_equal('menu', &completeopt) + call assert_equal('menu', &g:completeopt) + + set completeopt& +endfunc " Test for completing words following a completed word in a line func Test_complete_wrapscan() |