changeset 26042:6b39ab99e367 v8.2.3555

patch 8.2.3555: ModeChanged is not triggered on every mode change Commit: https://github.com/vim/vim/commit/25def2c8b8bd7b0c3d5f020207c717a880b05d50 Author: =?UTF-8?q?Magnus=20Gro=C3=9F?= <magnus.gross@rwth-aachen.de> Date: Fri Oct 22 18:56:39 2021 +0100 patch 8.2.3555: ModeChanged is not triggered on every mode change Problem: ModeChanged is not triggered on every mode change. Solution: Also trigger on minor mode changes. (Maguns Gross, closes https://github.com/vim/vim/issues/8999)
author Bram Moolenaar <Bram@vim.org>
date Fri, 22 Oct 2021 20:00:05 +0200
parents 1f1f9e2a021b
children 65c9f328be7a
files runtime/doc/autocmd.txt src/autocmd.c src/insexpand.c src/misc1.c src/normal.c src/terminal.c src/testdir/test_edit.vim src/version.c
diffstat 8 files changed, 81 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -930,18 +930,23 @@ MenuPopup			Just before showing the popu
 							*ModeChanged*
 ModeChanged			After changing the mode. The pattern is
 				matched against `'old_mode:new_mode'`, for
-				example match against `i:*` to simulate
-				|InsertLeave|.
+				example match against `*:c*` to simulate
+				|CmdlineEnter|.
 				The following values of |v:event| are set:
 				   old_mode	The mode before it changed.
 				   new_mode	The new mode as also returned
-						by |mode()|.
+						by |mode()| called with a
+						non-zero argument.
 				When ModeChanged is triggered, old_mode will
 				have the value of new_mode when the event was
 				last triggered.
+				This will be triggered on every minor mode
+				change.
 				Usage example to use relative line numbers
 				when entering Visual mode: >
-	:autocmd ModeChanged *:v set relativenumber
+	:au ModeChanged [vV\x16]*:* let &l:rnu = mode() =~# '^[vV\x16]'
+	:au ModeChanged *:[vV\x16]* let &l:rnu = mode() =~# '^[vV\x16]'
+	:au WinEnter,WinLeave * let &l:rnu = mode() =~# '^[vV\x16]'
 <							*OptionSet*
 OptionSet			After setting an option.  The pattern is
 				matched against the long option name.
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -1218,6 +1218,23 @@ do_autocmd_event(
 		    return FAIL;
 		}
 
+#ifdef FEAT_EVAL
+		// need to initialize last_mode for the first ModeChanged
+		// autocmd
+		if (event == EVENT_MODECHANGED && !has_modechanged())
+		{
+		    typval_T rettv;
+		    typval_T tv[2];
+
+		    tv[0].v_type = VAR_NUMBER;
+		    tv[0].vval.v_number = 1;
+		    tv[1].v_type = VAR_UNKNOWN;
+		    f_mode(tv, &rettv);
+		    STRCPY(last_mode, rettv.vval.v_string);
+		    vim_free(rettv.vval.v_string);
+		}
+#endif
+
 		if (is_buflocal)
 		{
 		    ap->buflocal_nr = buflocal_nr;
--- a/src/insexpand.c
+++ b/src/insexpand.c
@@ -243,6 +243,8 @@ ins_ctrl_x(void)
 	// CTRL-X in CTRL-X CTRL-V mode behaves differently to make CTRL-X
 	// CTRL-V look like CTRL-N
 	ctrl_x_mode = CTRL_X_CMDLINE_CTRL_X;
+
+    trigger_modechanged();
 }
 
 /*
@@ -2150,6 +2152,8 @@ ins_compl_prep(int c)
 	// upon the (possibly failed) completion.
 	ins_apply_autocmds(EVENT_COMPLETEDONE);
 
+    trigger_modechanged();
+
     // reset continue_* if we left expansion-mode, if we stay they'll be
     // (re)set properly in ins_complete()
     if (!vim_is_ctrl_x_key(c))
@@ -2487,6 +2491,7 @@ set_completion(colnr_T startcol, list_T 
     // Lazily show the popup menu, unless we got interrupted.
     if (!compl_interrupted)
 	show_pum(save_w_wrow, save_w_leftcol);
+    trigger_modechanged();
     out_flush();
 }
 
@@ -3255,6 +3260,8 @@ ins_compl_get_exp(pos_T *ini)
 	if (compl_curr_match == NULL)
 	    compl_curr_match = compl_old_match;
     }
+    trigger_modechanged();
+
     return i;
 }
 
--- a/src/misc1.c
+++ b/src/misc1.c
@@ -2670,12 +2670,17 @@ trigger_modechanged()
     if (!has_modechanged())
 	return;
 
-    v_event = get_vim_var_dict(VV_EVENT);
-
     tv[0].v_type = VAR_NUMBER;
     tv[0].vval.v_number = 1;	    // get full mode
     tv[1].v_type = VAR_UNKNOWN;
     f_mode(tv, &rettv);
+    if (STRCMP(rettv.vval.v_string, last_mode) == 0)
+    {
+	vim_free(rettv.vval.v_string);
+	return;
+    }
+
+    v_event = get_vim_var_dict(VV_EVENT);
     (void)dict_add_string(v_event, "new_mode", rettv.vval.v_string);
     (void)dict_add_string(v_event, "old_mode", last_mode);
     dict_set_items_ro(v_event);
@@ -2688,9 +2693,9 @@ trigger_modechanged()
     apply_autocmds(EVENT_MODECHANGED, pat, NULL, FALSE, curbuf);
     STRCPY(last_mode, rettv.vval.v_string);
 
-    vim_free(rettv.vval.v_string);
     vim_free(pat);
     dict_free_contents(v_event);
     hash_init(&v_event->dv_hashtab);
+    vim_free(rettv.vval.v_string);
 #endif
 }
--- a/src/normal.c
+++ b/src/normal.c
@@ -527,6 +527,7 @@ normal_cmd(
 # endif
     }
 #endif
+    trigger_modechanged();
 
     // When not finishing an operator and no register name typed, reset the
     // count.
@@ -1221,6 +1222,7 @@ normal_end:
     c = finish_op;
 #endif
     finish_op = FALSE;
+    trigger_modechanged();
 #ifdef CURSOR_SHAPE
     // Redraw the cursor with another shape, if we were in Operator-pending
     // mode or did a replace command.
@@ -1278,6 +1280,7 @@ normal_end:
 	if (restart_VIsual_select == 1)
 	{
 	    VIsual_select = TRUE;
+	    trigger_modechanged();
 	    showmode();
 	    restart_VIsual_select = 0;
 	}
@@ -1386,7 +1389,6 @@ end_visual_mode_keep_button()
 #endif
 
     VIsual_active = FALSE;
-    trigger_modechanged();
     setmouse();
     mouse_dragging = 0;
 
@@ -1403,6 +1405,7 @@ end_visual_mode_keep_button()
     may_clear_cmdline();
 
     adjust_cursor_eol();
+    trigger_modechanged();
 }
 
 /*
@@ -3439,6 +3442,7 @@ nv_ctrlg(cmdarg_T *cap)
     if (VIsual_active)	// toggle Selection/Visual mode
     {
 	VIsual_select = !VIsual_select;
+	trigger_modechanged();
 	showmode();
     }
     else if (!checkclearop(cap->oap))
@@ -3501,6 +3505,7 @@ nv_ctrlo(cmdarg_T *cap)
     if (VIsual_active && VIsual_select)
     {
 	VIsual_select = FALSE;
+	trigger_modechanged();
 	showmode();
 	restart_VIsual_select = 2;	// restart Select mode later
     }
--- a/src/terminal.c
+++ b/src/terminal.c
@@ -1995,6 +1995,7 @@ term_check_timers(int next_due_arg, prof
 set_terminal_mode(term_T *term, int normal_mode)
 {
     term->tl_normal_mode = normal_mode;
+    trigger_modechanged();
     if (!normal_mode)
 	handle_postponed_scrollback(term);
     VIM_CLEAR(term->tl_status_text);
--- a/src/testdir/test_edit.vim
+++ b/src/testdir/test_edit.vim
@@ -1959,12 +1959,8 @@ endfunc
 
 " Test for ModeChanged pattern
 func Test_mode_changes()
-  let g:count = 0
-  func! DoIt()
-    let g:count += 1
-  endfunc
   let g:index = 0
-  let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'n', 'V', 'v', 'n']
+  let g:mode_seq = ['n', 'i', 'n', 'v', 'V', 'i', 'ix', 'i', 'ic', 'i', 'n', 'no', 'n', 'V', 'v', 's', 'n']
   func! TestMode()
     call assert_equal(g:mode_seq[g:index], get(v:event, "old_mode"))
     call assert_equal(g:mode_seq[g:index + 1], get(v:event, "new_mode"))
@@ -1973,13 +1969,15 @@ func Test_mode_changes()
   endfunc
 
   au ModeChanged * :call TestMode()
-  au ModeChanged n:* :call DoIt()
-  call feedkeys("i\<esc>vV\<esc>", 'tnix')
-  call assert_equal(2, g:count)
+  let g:n_to_any = 0
+  au ModeChanged n:* let g:n_to_any += 1
+  call feedkeys("i\<esc>vVca\<CR>\<C-X>\<C-L>\<esc>ggdG", 'tnix')
 
-  au ModeChanged V:v :call DoIt()
-  call feedkeys("Vv\<esc>", 'tnix')
-  call assert_equal(4, g:count)
+  let g:V_to_v = 0
+  au ModeChanged V:v let g:V_to_v += 1
+  call feedkeys("Vv\<C-G>\<esc>", 'tnix')
+  call assert_equal(len(filter(g:mode_seq[1:], {idx, val -> val == 'n'})), g:n_to_any)
+  call assert_equal(1, g:V_to_v)
   call assert_equal(len(g:mode_seq) - 1, g:index)
 
   let g:n_to_i = 0
@@ -2008,12 +2006,32 @@ func Test_mode_changes()
   call assert_equal(2, g:i_to_any)
   call assert_equal(3, g:nori_to_any)
 
+  if has('terminal')
+    let g:mode_seq += ['c', 'n', 't', 'nt', 'c', 'nt', 'n']
+    call feedkeys(":term\<CR>\<C-W>N:bd!\<CR>", 'tnix')
+    call assert_equal(len(g:mode_seq) - 1, g:index)
+    call assert_equal(1, g:n_to_i)
+    call assert_equal(1, g:n_to_niI)
+    call assert_equal(1, g:niI_to_i)
+    call assert_equal(2, g:nany_to_i)
+    call assert_equal(1, g:i_to_n)
+    call assert_equal(2, g:i_to_any)
+    call assert_equal(5, g:nori_to_any)
+  endif
+
   au! ModeChanged
   delfunc TestMode
   unlet! g:mode_seq
   unlet! g:index
-  delfunc DoIt
-  unlet! g:count
+  unlet! g:n_to_any
+  unlet! g:V_to_v
+  unlet! g:n_to_i
+  unlet! g:n_to_niI
+  unlet! g:niI_to_i
+  unlet! g:nany_to_i
+  unlet! g:i_to_n
+  unlet! g:nori_to_any
+  unlet! g:i_to_any
 endfunc
 
 " vim: shiftwidth=2 sts=2 expandtab
--- a/src/version.c
+++ b/src/version.c
@@ -758,6 +758,8 @@ static char *(features[]) =
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    3555,
+/**/
     3554,
 /**/
     3553,