]> granicus.if.org Git - vim/commitdiff
patch 8.2.3980: if 'operatorfunc' invokes an operator Visual mode is changed v8.2.3980
authorBram Moolenaar <Bram@vim.org>
Sun, 2 Jan 2022 13:05:45 +0000 (13:05 +0000)
committerBram Moolenaar <Bram@vim.org>
Sun, 2 Jan 2022 13:05:45 +0000 (13:05 +0000)
Problem:    If 'operatorfunc' invokes an operator the remembered Visual mode
            may be changed. (Naohiro Ono)
Solution:   Save and restore the information for redoing the Visual area.
            (closes #9455)

src/ops.c
src/testdir/test_normal.vim
src/version.c

index 21df1a6bac42efbbdbaa95e30adf0eeac0eda146..0685a06e0ea6cc038c0e7c11e810294dc236ff5b 100644 (file)
--- a/src/ops.c
+++ b/src/ops.c
@@ -3492,6 +3492,15 @@ get_op_vcol(
     oap->start = curwin->w_cursor;
 }
 
+// Information for redoing the previous Visual selection.
+typedef struct {
+    int                rv_mode;        // 'v', 'V', or Ctrl-V
+    linenr_T   rv_line_count;  // number of lines
+    colnr_T    rv_vcol;        // number of cols or end column
+    long       rv_count;       // count for Visual operator
+    int                rv_arg;         // extra argument
+} redo_VIsual_T;
+
 /*
  * Handle an operator after Visual mode or when the movement is finished.
  * "gui_yank" is true when yanking text for the clipboard.
@@ -3508,11 +3517,8 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
 #endif
 
     // The visual area is remembered for redo
-    static int     redo_VIsual_mode = NUL; // 'v', 'V', or Ctrl-V
-    static linenr_T redo_VIsual_line_count; // number of lines
-    static colnr_T  redo_VIsual_vcol;      // number of cols or end column
-    static long            redo_VIsual_count;      // count for Visual operator
-    static int     redo_VIsual_arg;        // extra argument
+    static redo_VIsual_T   redo_VIsual = {NUL, 0, 0, 0,0};
+
     int                    include_line_break = FALSE;
 
 #if defined(FEAT_CLIPBOARD)
@@ -3621,24 +3627,24 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
        if (redo_VIsual_busy)
        {
            // Redo of an operation on a Visual area. Use the same size from
-           // redo_VIsual_line_count and redo_VIsual_vcol.
+           // redo_VIsual.rv_line_count and redo_VIsual.rv_vcol.
            oap->start = curwin->w_cursor;
-           curwin->w_cursor.lnum += redo_VIsual_line_count - 1;
+           curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1;
            if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count)
                curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
-           VIsual_mode = redo_VIsual_mode;
-           if (redo_VIsual_vcol == MAXCOL || VIsual_mode == 'v')
+           VIsual_mode = redo_VIsual.rv_mode;
+           if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v')
            {
                if (VIsual_mode == 'v')
                {
-                   if (redo_VIsual_line_count <= 1)
+                   if (redo_VIsual.rv_line_count <= 1)
                    {
                        validate_virtcol();
                        curwin->w_curswant =
-                                    curwin->w_virtcol + redo_VIsual_vcol - 1;
+                                    curwin->w_virtcol + redo_VIsual.rv_vcol - 1;
                    }
                    else
-                       curwin->w_curswant = redo_VIsual_vcol;
+                       curwin->w_curswant = redo_VIsual.rv_vcol;
                }
                else
                {
@@ -3646,9 +3652,9 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
                }
                coladvance(curwin->w_curswant);
            }
-           cap->count0 = redo_VIsual_count;
-           if (redo_VIsual_count != 0)
-               cap->count1 = redo_VIsual_count;
+           cap->count0 = redo_VIsual.rv_count;
+           if (redo_VIsual.rv_count != 0)
+               cap->count1 = redo_VIsual.rv_count;
            else
                cap->count1 = 1;
        }
@@ -3750,7 +3756,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
 
        if (VIsual_active || redo_VIsual_busy)
        {
-           get_op_vcol(oap, redo_VIsual_vcol, TRUE);
+           get_op_vcol(oap, redo_VIsual.rv_vcol, TRUE);
 
            if (!redo_VIsual_busy && !gui_yank)
            {
@@ -3822,11 +3828,11 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
                }
                if (!redo_VIsual_busy)
                {
-                   redo_VIsual_mode = resel_VIsual_mode;
-                   redo_VIsual_vcol = resel_VIsual_vcol;
-                   redo_VIsual_line_count = resel_VIsual_line_count;
-                   redo_VIsual_count = cap->count0;
-                   redo_VIsual_arg = cap->arg;
+                   redo_VIsual.rv_mode = resel_VIsual_mode;
+                   redo_VIsual.rv_vcol = resel_VIsual_vcol;
+                   redo_VIsual.rv_line_count = resel_VIsual_line_count;
+                   redo_VIsual.rv_count = cap->count0;
+                   redo_VIsual.rv_arg = cap->arg;
                }
            }
 
@@ -4114,13 +4120,22 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
            break;
 
        case OP_FUNCTION:
+           {
+               redo_VIsual_T   save_redo_VIsual = redo_VIsual;
+
 #ifdef FEAT_LINEBREAK
-           // Restore linebreak, so that when the user edits it looks as
-           // before.
-           curwin->w_p_lbr = lbr_saved;
+               // Restore linebreak, so that when the user edits it looks as
+               // before.
+               curwin->w_p_lbr = lbr_saved;
 #endif
-           op_function(oap);           // call 'operatorfunc'
-           break;
+               // call 'operatorfunc'
+               op_function(oap);
+
+               // Restore the info for redoing Visual mode, the function may
+               // invoke another operator and unintentionally change it.
+               redo_VIsual = save_redo_VIsual;
+               break;
+           }
 
        case OP_INSERT:
        case OP_APPEND:
@@ -4216,7 +4231,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
 #ifdef FEAT_LINEBREAK
                curwin->w_p_lbr = lbr_saved;
 #endif
-               op_addsub(oap, cap->count1, redo_VIsual_arg);
+               op_addsub(oap, cap->count1, redo_VIsual.rv_arg);
                VIsual_active = FALSE;
            }
            check_cursor_col();
index 1005694e41ea9a0795f91db219e4b610cde21d53..90b7f9d9bb09cc1d402700402605632ca7cce4c2 100644 (file)
@@ -464,6 +464,10 @@ func OperatorfuncRedo(_)
   let g:opfunc_count = v:count
 endfunc
 
+func Underscorize(_)
+  normal! '[V']r_
+endfunc
+
 func Test_normal09c_operatorfunc()
   " Test redoing operatorfunc
   new
@@ -477,6 +481,16 @@ func Test_normal09c_operatorfunc()
 
   bw!
   unlet g:opfunc_count
+
+  " Test redoing Visual mode
+  set operatorfunc=Underscorize
+  new
+  call setline(1, ['first', 'first', 'third', 'third', 'second'])
+  normal! 1GVjr_
+  normal! 5G.
+  normal! 3G.
+  call assert_equal(['_____', '_____', '_____', '_____', '______'], getline(1, '$'))
+  bwipe!
   set operatorfunc=
 endfunc
 
index 8f47ebb4fc37ac2022a4cc5ebf1300bcce697535..2ca87fec33614d7d7f3c224ded575a56af51c259 100644 (file)
@@ -750,6 +750,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    3980,
 /**/
     3979,
 /**/