# HG changeset patch # User Bram Moolenaar # Date 1641129303 -3600 # Node ID 5aa9e8db975c062180c7c3d388eacb7e9e48d0e4 # Parent 3c0792bb8be6582afc9b3ab83c3ff584f787f8da patch 8.2.3980: if 'operatorfunc' invokes an operator Visual mode is changed Commit: https://github.com/vim/vim/commit/b3bd1d39e68e2d697c014b9f85482c2c12a3f909 Author: Bram Moolenaar 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) diff --git a/src/ops.c b/src/ops.c --- 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(); diff --git a/src/testdir/test_normal.vim b/src/testdir/test_normal.vim --- 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 diff --git a/src/version.c b/src/version.c --- 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,