patch 9.1.0469: Cannot have buffer-local value for 'completeopt'

Commit: 
https://github.com/vim/vim/commit/529b9ad62a0e843ee56ef609aef7e51b7dc8a4c8
Author: zeertzjq <zeert...@outlook.com>
Date:   Wed Jun 5 20:27:06 2024 +0200

    patch 9.1.0469: Cannot have buffer-local value for 'completeopt'
    
    Problem:  Cannot have buffer-local value for 'completeopt'
              (Nick Jensen).
    Solution: Make 'completeopt' global-local (zeertzjq).
    
    Also for some reason test Test_ColonEight_MultiByte seems to be failing
    sporadically now. Let's mark it as flaky.
    
    fixes: #5487
    closes: #14922
    
    Signed-off-by: zeertzjq <zeert...@outlook.com>
    Signed-off-by: Christian Brabandt <c...@256bit.org>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 57fefa223..655fd6c52 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*  For Vim version 9.1.  Last change: 2024 Jun 04
+*options.txt*  For Vim version 9.1.  Last change: 2024 Jun 05
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -2101,7 +2101,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/doc/version9.txt b/runtime/doc/version9.txt
index 07c416ae2..6edb8891b 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*  For Vim version 9.1.  Last change: 2024 Jun 03
+*version9.txt*  For Vim version 9.1.  Last change: 2024 Jun 05
 
 
                  VIM REFERENCE MANUAL    by Bram Moolenaar
@@ -41567,6 +41567,7 @@ Changed~
 - use 'smoothscroll' logic for CTRL-D and CTRL-U for half-pagewise scrolling
 - the default for 'commentstring' contains whitespace padding to have
   automatic comments look nicer |comment-install|
+- 'completeopt' is now a |global-local| option.
 
                                                        *added-9.2*
 Added ~
diff --git a/runtime/optwin.vim b/runtime/optwin.vim
index d3e1605ed..f218ffba2 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 <b...@vim.org>
 
 " If there already is an option window, jump to that one.
@@ -846,7 +846,7 @@ if has("insert_expand")
   call append("$", "   " .. 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")
   if exists("+completepopup")
     call <SID>AddOption("completepopup", gettext("options for the Insert mode 
completion info popup"))
     call <SID>OptionG("cpp", &cpp)
diff --git a/src/buffer.c b/src/buffer.c
index 58e9718e3..cbec9b980 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -2429,6 +2429,7 @@ free_buf_options(
     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);
 #ifdef FEAT_COMPL_FUNC
     clear_string_option(&buf->b_p_cfu);
diff --git a/src/insexpand.c b/src/insexpand.c
index 552c67957..7066be8f3 100644
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -148,14 +148,6 @@ static char_u        *compl_leader = NULL;
 static int       compl_get_longest = FALSE;    // put longest common string
                                                // in compl_leader
 
-static int       compl_no_insert = FALSE;      // FALSE: select & insert
-                                               // TRUE: noinsert
-static int       compl_no_select = FALSE;      // FALSE: select & insert
-                                               // TRUE: noselect
-static int       compl_longest = FALSE;        // FALSE: insert full match
-                                               // TRUE: insert longest prefix
-static int       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 int       compl_used_match;
@@ -1054,24 +1046,12 @@ ins_compl_long_shown_match(void)
 }
 
 /*
- * Set variables that store noselect and noinsert behavior from the
- * 'completeopt' value.
+ * Get the local or global value of 'completeopt' flags.
  */
-    void
-completeopt_was_set(void)
+    unsigned int
+get_cot_flags(void)
 {
-    compl_no_insert = FALSE;
-    compl_no_select = FALSE;
-    compl_longest = FALSE;
-    compl_fuzzy_match = FALSE;
-    if (strstr((char *)p_cot, "noselect") != NULL)
-       compl_no_select = TRUE;
-    if (strstr((char *)p_cot, "noinsert") != NULL)
-       compl_no_insert = TRUE;
-    if (strstr((char *)p_cot, "longest") != NULL)
-       compl_longest = TRUE;
-    if (strstr((char *)p_cot, "fuzzy") != NULL)
-       compl_fuzzy_match = TRUE;
+    return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags;
 }
 
 
@@ -1118,7 +1098,7 @@ ins_compl_del_pum(void)
 pum_wanted(void)
 {
     // 'completeopt' must contain "menu" or "menuone"
-    if (vim_strchr(p_cot, 'm') == NULL)
+    if ((get_cot_flags() & COT_ANY_MENU) == 0)
        return FALSE;
 
     // The display looks bad on a B&W display.
@@ -1152,7 +1132,7 @@ pum_enough_matches(void)
        compl = compl->cp_next;
     } while (!is_first_match(compl));
 
-    if (strstr((char *)p_cot, "menuone") != NULL)
+    if (get_cot_flags() & COT_MENUONE)
        return (i >= 1);
     return (i >= 2);
 }
@@ -1246,6 +1226,9 @@ ins_compl_build_pum(void)
     int                cur = -1;
     int                lead_len = 0;
     int                max_fuzzy_score = 0;
+    int                cur_cot_flags = get_cot_flags();
+    int                compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
+    int                compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
 
     // Need to build the popup menu list.
     compl_match_arraysize = 0;
@@ -2469,9 +2452,8 @@ ins_compl_prep(int c)
     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;
-
     }
 
     if (ctrl_x_mode_not_defined_yet())
@@ -2943,6 +2925,10 @@ set_completion(colnr_T startcol, list_T *list)
     int save_w_wrow = curwin->w_wrow;
     int save_w_leftcol = curwin->w_leftcol;
     int flags = CP_ORIGINAL_TEXT;
+    int cur_cot_flags = get_cot_flags();
+    int compl_longest = (cur_cot_flags & COT_LONGEST) != 0;
+    int compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
+    int compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
 
     // If already doing completions stop it.
     if (ctrl_x_mode_not_default())
@@ -4140,6 +4126,9 @@ find_next_completion_match(
 {
     int            found_end = FALSE;
     compl_T *found_compl = NULL;
+    int            cur_cot_flags = get_cot_flags();
+    int            compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
+    int            compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
 
     while (--todo >= 0)
     {
@@ -4257,6 +4246,9 @@ ins_compl_next(
     int            advance;
     int            started = compl_started;
     buf_T   *orig_curbuf = curbuf;
+    int            cur_cot_flags = get_cot_flags();
+    int            compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
+    int            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.
@@ -4411,7 +4403,7 @@ ins_compl_check_keys(int frequency, int 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;
 
diff --git a/src/option.c b/src/option.c
index 2acc0d475..8521c6ed1 100644
--- a/src/option.c
+++ b/src/option.c
@@ -6224,6 +6224,10 @@ unset_global_local_option(char_u *name, void *from)
            clear_string_option(&buf->b_p_inc);
            break;
 # endif
+       case PV_COT:
+           clear_string_option(&buf->b_p_cot);
+           buf->b_cot_flags = 0;
+           break;
        case PV_DICT:
            clear_string_option(&buf->b_p_dict);
            break;
@@ -6333,6 +6337,7 @@ get_varp_scope(struct vimoption *p, int scope)
            case PV_DEF:  return (char_u *)&(curbuf->b_p_def);
            case PV_INC:  return (char_u *)&(curbuf->b_p_inc);
 #endif
+           case PV_COT:  return (char_u *)&(curbuf->b_p_cot);
            case PV_DICT: return (char_u *)&(curbuf->b_p_dict);
            case PV_TSR:  return (char_u *)&(curbuf->b_p_tsr);
 #ifdef FEAT_COMPL_FUNC
@@ -6413,6 +6418,8 @@ get_varp(struct vimoption *p)
        case PV_INC:    return *curbuf->b_p_inc != NUL
                                    ? (char_u *)&(curbuf->b_p_inc) : p->var;
 #endif
+       case PV_COT:    return *curbuf->b_p_cot != NUL
+                                   ? (char_u *)&(curbuf->b_p_cot) : p->var;
        case PV_DICT:   return *curbuf->b_p_dict != NUL
                                    ? (char_u *)&(curbuf->b_p_dict) : p->var;
        case PV_TSR:    return *curbuf->b_p_tsr != NUL
@@ -7205,6 +7212,8 @@ buf_copy_options(buf_T *buf, int flags)
            COPY_OPT_SCTX(buf, BV_INEX);
 # endif
 #endif
+           buf->b_p_cot = empty_option;
+           buf->b_cot_flags = 0;
            buf->b_p_dict = empty_option;
            buf->b_p_tsr = empty_option;
 #ifdef FEAT_COMPL_FUNC
diff --git a/src/option.h b/src/option.h
index bf889e47d..91d8d95c5 100644
--- a/src/option.h
+++ b/src/option.h
@@ -513,6 +513,20 @@ EXTERN int p_confirm;      // 'confirm'
 #endif
 EXTERN int     p_cp;           // 'compatible'
 EXTERN char_u  *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_POPUPHIDDEN        0x020
+#define COT_ANY_PREVIEW        0x038   // combination of preview flags
+#define COT_NOINSERT   0x040   // FALSE: select & insert, TRUE: noinsert
+#define COT_NOSELECT   0x080   // FALSE: select & insert, TRUE: noselect
+#define COT_FUZZY      0x100   // TRUE: fuzzy match enabled
 #ifdef BACKSLASH_IN_FILENAME
 EXTERN char_u  *p_csl;         // 'completeslash'
 #endif
@@ -1127,6 +1141,7 @@ enum
     , BV_CMS
 #endif
     , BV_COM
+    , BV_COT
     , BV_CPT
     , BV_DICT
     , BV_TSR
diff --git a/src/optiondefs.h b/src/optiondefs.h
index 50472e3f6..7b58f6b33 100644
--- a/src/optiondefs.h
+++ b/src/optiondefs.h
@@ -50,6 +50,7 @@
 # define PV_CMS                OPT_BUF(BV_CMS)
 #endif
 #define PV_COM         OPT_BUF(BV_COM)
+#define PV_COT         OPT_BOTH(OPT_BUF(BV_COT))
 #define PV_CPT         OPT_BUF(BV_CPT)
 #define PV_DICT                OPT_BOTH(OPT_BUF(BV_DICT))
 #define PV_TSR         OPT_BOTH(OPT_BUF(BV_TSR))
@@ -654,7 +655,7 @@ static struct vimoption options[] =
 #endif
                            SCTX_INIT},
     {"completeopt",   "cot",  P_STRING|P_VI_DEF|P_ONECOMMA|P_NODUP,
-                           (char_u *)&p_cot, PV_NONE, did_set_completeopt, 
expand_set_completeopt,
+                           (char_u *)&p_cot, PV_COT, did_set_completeopt, 
expand_set_completeopt,
                            {(char_u *)"menu,preview", (char_u *)0L}
                            SCTX_INIT},
     {"completepopup", "cpp", P_STRING|P_VI_DEF|P_COMMA|P_NODUP|P_COLON,
diff --git a/src/optionstr.c b/src/optionstr.c
index 6b59b6808..d72298194 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -144,6 +144,7 @@ didset_string_options(void)
     (void)opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, TRUE);
     (void)opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, TRUE);
     (void)opt_strings_flags(p_bo, p_bo_values, &bo_flags, TRUE);
+    (void)opt_strings_flags(p_cot, p_cot_values, &cot_flags, TRUE);
 #ifdef FEAT_SESSION
     (void)opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, TRUE);
     (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, TRUE);
@@ -301,6 +302,7 @@ check_buf_options(buf_T *buf)
     check_string_option(&buf->b_p_lop);
     check_string_option(&buf->b_p_ft);
     check_string_option(&buf->b_p_cinw);
+    check_string_option(&buf->b_p_cot);
     check_string_option(&buf->b_p_cpt);
 #ifdef FEAT_COMPL_FUNC
     check_string_option(&buf->b_p_cfu);
@@ -1601,10 +1603,21 @@ expand_set_complete(optexpand_T *args, int *numMatches, 
char_u ***matches)
     char *
 did_set_completeopt(optset_T *args UNUSED)
 {
-    if (check_opt_strings(p_cot, p_cot_values, TRUE) != OK)
+    char_u     *cot = p_cot;
+    unsigned   *flags = &cot_flags;
+
+    if (args->os_flags & OPT_LOCAL)
+    {
+       cot = curbuf->b_p_cot;
+       flags = &curbuf->b_cot_flags;
+    }
+
+    if (check_opt_strings(cot, p_cot_values, TRUE) != OK)
+       return e_invalid_argument;
+
+    if (opt_strings_flags(cot, p_cot_values, flags, TRUE) != OK)
        return e_invalid_argument;
 
-    completeopt_was_set();
     return NULL;
 }
 
diff --git a/src/popupmenu.c b/src/popupmenu.c
index 741980660..01b6ab1e9 100644
--- a/src/popupmenu.c
+++ b/src/popupmenu.c
@@ -760,6 +760,7 @@ pum_set_selected(int n, int repeat UNUSED)
     int            context = pum_height / 2;
 #ifdef FEAT_QUICKFIX
     int            prev_selected = pum_selected;
+    unsigned   cur_cot_flags = get_cot_flags();
 #endif
 #if defined(FEAT_PROP_POPUP) && defined(FEAT_QUICKFIX)
     int            has_info = FALSE;
@@ -831,7 +832,7 @@ pum_set_selected(int n, int repeat UNUSED)
        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;
@@ -842,9 +843,9 @@ pum_set_selected(int n, int repeat UNUSED)
 # endif
 # ifdef FEAT_PROP_POPUP
            has_info = TRUE;
-           if (strstr((char *)p_cot, "popuphidden") != NULL)
+           if (cur_cot_flags & COT_POPUPHIDDEN)
                use_popup = USEPOPUP_HIDDEN;
-           else if (strstr((char *)p_cot, "popup") != NULL)
+           else if (cur_cot_flags & COT_POPUP)
                use_popup = USEPOPUP_NORMAL;
            else
                use_popup = USEPOPUP_NONE;
diff --git a/src/proto/insexpand.pro b/src/proto/insexpand.pro
index 51f5db0f8..3d8c0428e 100644
--- a/src/proto/insexpand.pro
+++ b/src/proto/insexpand.pro
@@ -27,7 +27,7 @@ int ins_compl_accept_char(int c);
 int ins_compl_add_infercase(char_u *str_arg, int len, int icase, char_u 
*fname, int dir, int cont_s_ipos);
 int ins_compl_has_shown_match(void);
 int ins_compl_long_shown_match(void);
-void completeopt_was_set(void);
+unsigned get_cot_flags(void);
 int pum_wanted(void);
 void ins_compl_show_pum(void);
 char_u *find_word_start(char_u *ptr);
diff --git a/src/structs.h b/src/structs.h
index bd6977ef8..f1bbf1735 100644
--- a/src/structs.h
+++ b/src/structs.h
@@ -3222,6 +3222,8 @@ struct file_buffer
 #ifdef FEAT_FOLDING
     char_u     *b_p_cms;       // 'commentstring'
 #endif
+    char_u     *b_p_cot;       // 'completeopt' local value
+    unsigned   b_cot_flags;    // flags for 'completeopt'
     char_u     *b_p_cpt;       // 'complete'
 #ifdef BACKSLASH_IN_FILENAME
     char_u     *b_p_csl;       // 'completeslash'
diff --git a/src/testdir/test_ins_complete.vim 
b/src/testdir/test_ins_complete.vim
index 947b0bb64..ad9f4978e 100644
--- a/src/testdir/test_ins_complete.vim
+++ b/src/testdir/test_ins_complete.vim
@@ -809,6 +809,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()
diff --git a/src/version.c b/src/version.c
index 92106bc3a..e805e2c54 100644
--- a/src/version.c
+++ b/src/version.c
@@ -704,6 +704,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    469,
 /**/
     468,
 /**/

-- 
-- 
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php

--- 
You received this message because you are subscribed to the Google Groups 
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to vim_dev+unsubscr...@googlegroups.com.
To view this discussion on the web visit 
https://groups.google.com/d/msgid/vim_dev/E1sEvd9-00CG62-LV%40256bit.org.

Raspunde prin e-mail lui