changeset 26903:5aa9e8db975c v8.2.3980

patch 8.2.3980: if 'operatorfunc' invokes an operator Visual mode is changed Commit: https://github.com/vim/vim/commit/b3bd1d39e68e2d697c014b9f85482c2c12a3f909 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Jan 2 13:05:45 2022 +0000 patch 8.2.3980: if 'operatorfunc' invokes an operator Visual mode is changed 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)
author Bram Moolenaar <Bram@vim.org>
date Sun, 02 Jan 2022 14:15:03 +0100
parents 3c0792bb8be6
children 06cc23a34556
files src/ops.c src/testdir/test_normal.vim src/version.c
diffstat 3 files changed, 58 insertions(+), 27 deletions(-) [+]
line wrap: on
line diff
--- 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 o
 #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 o
 	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 o
 		}
 		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 o
 
 	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 o
 		}
 		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 o
 	    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 o
 #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();
--- a/src/testdir/test_normal.vim
+++ b/src/testdir/test_normal.vim
@@ -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
 
--- a/src/version.c
+++ b/src/version.c
@@ -751,6 +751,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3980,
+/**/
     3979,
 /**/
     3978,