Commit: patch 9.2.0991: autocmd: events are triggered for what happened while they were ignored

Christian Brabandt <[email protected]>
Newsgroups gmane.editors.vim.devel
Message-ID <[email protected]>
patch 9.2.0991: autocmd: events are triggered for what happened while they were ignored

Commit: https://github.com/vim/vim/commit/775df1494afffb964f7c2225b197bac70e19a93f
Author: Hirohito Higashi <[email protected]>
Date:   Thu Aug 20 21:20:03 2026 +0000

    patch 9.2.0991: autocmd: events are triggered for what happened while they were ignored
    
    Problem:  Events that are triggered by comparing the current state against a
              stored one are still reported once they are not ignored anymore.
              This shows up when a command sets 'eventignore', changes something
              and restores the option, since these events are only checked in
              the main loop, which does not run while a command executes.
    Solution: Move the checks done in the main loop into a function and also
              call it when 'eventignore' or 'eventignorewin' changes, with the
              old value in effect.  What happened while an event was ignored is
              then not reported, and what happened before still is.
    
    fixes:  #8641
    closes: #21074
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    Signed-off-by: Hirohito Higashi <[email protected]>
    Signed-off-by: Christian Brabandt <[email protected]>

diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
index 08f286782..ecccba92a 100644
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -1,4 +1,4 @@
-*options.txt*	For Vim version 9.2.  Last change: 2026 Aug 19
+*options.txt*	For Vim version 9.2.  Last change: 2026 Aug 20
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -3583,6 +3583,10 @@ A jump table for the options with a short description can be found at |Q_op|.
 	To ignore all but some events, a "-" prefix can be used: >
 	    :set ei=all,-WinLeave
 <
+	Events that are triggered by comparing the current state against a
+	stored one, such as |CursorMoved| and |TextChanged|, are not triggered
+	afterwards for what happened while they were ignored.
+
 						*'eventignorewin'* *'eiw'*
 'eventignorewin' 'eiw'	string	(default "")
 			window-local
diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt
index e2675c5f2..c0f746f2c 100644
--- a/runtime/doc/version9.txt
+++ b/runtime/doc/version9.txt
@@ -1,4 +1,4 @@
-*version9.txt*	For Vim version 9.2.  Last change: 2026 Aug 18
+*version9.txt*	For Vim version 9.2.  Last change: 2026 Aug 20
 
 
 		  VIM REFERENCE MANUAL	  by Bram Moolenaar
@@ -52703,6 +52703,8 @@ Changed ~
 - |listener_add()| accepts a Dictionary of options and can include the
   resulting text of a change |listener-text|.
 - |extend()| and |extendnew()| also accept |Blobs|.
+- events like |CursorMoved| are no longer triggered for what happened while
+  they were ignored with 'eventignore'.
 
 
 							*added-9.3*
diff --git a/src/main.c b/src/main.c
index 7b5f6aa4c..ba863a3f0 100644
--- a/src/main.c
+++ b/src/main.c
@@ -1297,6 +1297,97 @@ work_pending(void)
     return op_pending() || !is_safe_now();
 }
 
+#ifdef FEAT_CONCEAL
+static linenr_T	conceal_old_cursor_line = 0;
+static linenr_T	conceal_new_cursor_line = 0;
+static int	conceal_update_lines = FALSE;
+#endif
+
+/*
+ * Trigger the events that are not triggered where they happen, but by
+ * comparing the current state against the state stored when they were last
+ * triggered. Also updates what depends on the cursor having moved, which is
+ * not affected by 'eventignore'.
+ * Called from the main loop and when 'eventignore(win)' changes, so that what
+ * happened with an event ignored is not reported once it is not ignored
+ * anymore.
+ */
+    void
+may_trigger_deferred_events(void)
+{
+    static bool	recursive = false;
+
+    if (recursive)
+	return;
+    recursive = true;
+
+#ifdef FEAT_CONCEAL
+    if (curwin->w_p_cole == 0)
+	conceal_update_lines = FALSE;
+#endif
+
+    // Trigger CursorMoved if the cursor moved.
+    if (!finish_op && (has_cursormoved()
+#ifdef FEAT_PROP_POPUP
+		|| popup_visible
+#endif
+#ifdef FEAT_CONCEAL
+		|| curwin->w_p_cole > 0
+#endif
+		) && !EQUAL_POS(last_cursormoved, curwin->w_cursor))
+    {
+	if (has_cursormoved())
+	    apply_autocmds(EVENT_CURSORMOVED, NULL, NULL, FALSE, curbuf);
+#ifdef FEAT_PROP_POPUP
+	if (popup_visible)
+	    popup_check_cursor_pos();
+#endif
+#ifdef FEAT_CONCEAL
+	if (curwin->w_p_cole > 0)
+	{
+	    conceal_old_cursor_line = last_cursormoved.lnum;
+	    conceal_new_cursor_line = curwin->w_cursor.lnum;
+	    conceal_update_lines = TRUE;
+	}
+#endif
+	last_cursormoved = curwin->w_cursor;
+    }
+
+#ifdef FEAT_CONCEAL
+    if (conceal_update_lines
+	    && (conceal_old_cursor_line != conceal_new_cursor_line
+		|| conceal_cursor_line(curwin)
+		|| need_cursor_line_redraw))
+    {
+	if (conceal_old_cursor_line != conceal_new_cursor_line
+		&& conceal_old_cursor_line != 0
+		&& conceal_old_cursor_line <= curbuf->b_ml.ml_line_count)
+	    redrawWinline(curwin, conceal_old_cursor_line);
+	redrawWinline(curwin, conceal_new_cursor_line);
+	curwin->w_valid &= ~VALID_CROW;
+	need_cursor_line_redraw = FALSE;
+    }
+#endif
+
+    // Trigger TextChanged if b:changedtick differs.
+    if (!finish_op && has_textchanged()
+	    && curbuf->b_last_changedtick != CHANGEDTICK(curbuf))
+    {
+	apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, FALSE, curbuf);
+	curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
+    }
+
+    // Ensure curwin->w_topline and curwin->w_leftcol are up to date before
+    // triggering a WinScrolled autocommand.
+    update_topline();
+    validate_cursor();
+
+    if (!finish_op)
+	may_trigger_win_scrolled_resized();
+
+    recursive = false;
+}
+
 
 /*
  * Main loop: Execute Normal mode commands until exiting Vim.
@@ -1313,12 +1404,6 @@ main_loop(
     oparg_T	oa;		// operator arguments
     oparg_T	*prev_oap;	// operator arguments
     volatile int previous_got_int = FALSE;	// "got_int" was TRUE
-#ifdef FEAT_CONCEAL
-    // these are static to avoid a compiler warning
-    static linenr_T	conceal_old_cursor_line = 0;
-    static linenr_T	conceal_new_cursor_line = 0;
-    static int		conceal_update_lines = FALSE;
-#endif
 
     prev_oap = current_oap;
     current_oap = &oa;
@@ -1413,72 +1498,7 @@ main_loop(
 	    // locked, this would be a good time to handle the drop.
 	    handle_any_postponed_drop();
 #endif
-#ifdef FEAT_CONCEAL
-	    if (curwin->w_p_cole == 0)
-		conceal_update_lines = FALSE;
-#endif
-
-	    // Trigger CursorMoved if the cursor moved.
-	    if (!finish_op && (has_cursormoved()
-#ifdef FEAT_PROP_POPUP
-				|| popup_visible
-#endif
-#ifdef FEAT_CONCEAL
-				|| curwin->w_p_cole > 0
-#endif
-			      )
-		    && !EQUAL_POS(last_cursormoved, curwin->w_cursor))
-	    {
-		if (has_cursormoved())
-		    apply_autocmds(EVENT_CURSORMOVED, NULL, NULL,
-							       FALSE, curbuf);
-#ifdef FEAT_PROP_POPUP
-		if (popup_visible)
-		    popup_check_cursor_pos();
-#endif
-#ifdef FEAT_CONCEAL
-		if (curwin->w_p_cole > 0)
-		{
-		    conceal_old_cursor_line = last_cursormoved.lnum;
-		    conceal_new_cursor_line = curwin->w_cursor.lnum;
-		    conceal_update_lines = TRUE;
-		}
-#endif
-		last_cursormoved = curwin->w_cursor;
-	    }
-
-#if defined(FEAT_CONCEAL)
-	    if (conceal_update_lines
-		    && (conceal_old_cursor_line != conceal_new_cursor_line
-			|| conceal_cursor_line(curwin)
-			|| need_cursor_line_redraw))
-	    {
-		if (conceal_old_cursor_line != conceal_new_cursor_line
-			&& conceal_old_cursor_line != 0
-			&& conceal_old_cursor_line
-						<= curbuf->b_ml.ml_line_count)
-		    redrawWinline(curwin, conceal_old_cursor_line);
-		redrawWinline(curwin, conceal_new_cursor_line);
-		curwin->w_valid &= ~VALID_CROW;
-		need_cursor_line_redraw = FALSE;
-	    }
-#endif
-
-	    // Trigger TextChanged if b:changedtick differs.
-	    if (!finish_op && has_textchanged()
-		    && curbuf->b_last_changedtick != CHANGEDTICK(curbuf))
-	    {
-		apply_autocmds(EVENT_TEXTCHANGED, NULL, NULL, FALSE, curbuf);
-		curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
-	    }
-
-	    // Ensure curwin->w_topline and curwin->w_leftcol are up to date
-	    // before triggering a WinScrolled autocommand.
-	    update_topline();
-	    validate_cursor();
-
-	    if (!finish_op)
-		may_trigger_win_scrolled_resized();
+	    may_trigger_deferred_events();
 
 	    // If nothing is pending and we are going to wait for the user to
 	    // type a character, trigger SafeState.
diff --git a/src/optionstr.c b/src/optionstr.c
index 9a275ef85..aec6fef55 100644
--- a/src/optionstr.c
+++ b/src/optionstr.c
@@ -2411,9 +2411,24 @@ expand_set_encoding(optexpand_T *args, int *numMatches, char_u ***matches)
 did_set_eventignore(optset_T *args)
 {
     char_u	**varp = (char_u **)args->os_varp;
+    char_u	*oldval = args->os_oldval.string;
+    char_u	*newval;
 
     if (check_ei(*varp) == FAIL)
 	return e_invalid_argument;
+
+    if (oldval == NULL || STRCMP(oldval, *varp) == 0)
+	return NULL;
+
+    // Deal with the events that are triggered by comparing against a stored
+    // state, with the old value in effect: what happened while an event was
+    // ignored must not be reported once it is not ignored anymore, and what
+    // happened before must still be reported.
+    newval = *varp;
+    *varp = oldval;
+    may_trigger_deferred_events();
+    *varp = newval;
+
     return NULL;
 }
 
diff --git a/src/proto/main.pro b/src/proto/main.pro
index 7e4c50803..025a9994f 100644
--- a/src/proto/main.pro
+++ b/src/proto/main.pro
@@ -12,6 +12,7 @@ void state_no_longer_safe(char *reason);
 int get_was_safe_state(void);
 void may_trigger_safestateagain(void);
 int work_pending(void);
+void may_trigger_deferred_events(void);
 void main_loop(int cmdwin, int noexmode);
 void getout_preserve_modified(int exitval);
 void getout(int exitval);
diff --git a/src/testdir/test_autocmd.vim b/src/testdir/test_autocmd.vim
index 1f33bd2b4..52cb33439 100644
--- a/src/testdir/test_autocmd.vim
+++ b/src/testdir/test_autocmd.vim
@@ -5951,6 +5951,169 @@ func Test_eventignore_subtract()
   %bw!
 endfunc
 
+" A cursor move made while CursorMoved is ignored is not reported once the
+" event is not ignored anymore.
+func Test_eventignore_cursormoved()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+    call setline(1, range(1, 300))
+    let g:moved = 0
+    autocmd CursorMoved * let g:moved += 1
+
+    func MoveWhileIgnored()
+      set eventignore=all
+      call cursor(100, 1)
+      call cursor(200, 1)
+      set eventignore=
+    endfunc
+
+    func MoveThenIgnore()
+      call cursor(50, 1)
+      set eventignore=all
+      set eventignore=
+    endfunc
+
+    func MoveExWhileIgnored()
+      set eventignore=all
+      100
+      200
+      set eventignore=
+    endfunc
+
+    " The move under "all" is not reported, the one under "WinEnter" is.
+    func MoveWithTwoStages()
+      set eventignore=all
+      call cursor(100, 1)
+      set eventignore=WinEnter
+      call cursor(200, 1)
+      set eventignore=
+    endfunc
+  END
+  call writefile(lines, 'XTest_eventignore_cm', 'D')
+  let buf = RunVimInTerminal('-S XTest_eventignore_cm', {'rows': 10})
+
+  " Reset the counter in the same command line as the move, so that the main
+  " loop only runs once the move has been made.
+  call term_sendkeys(buf, ":let g:moved = 0 | call MoveWhileIgnored()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'moved:' g:moved\<CR>")
+  call WaitForAssert({-> assert_match('^moved: 0\>', term_getline(buf, 10))}, 1000)
+
+  " A move made before the event was ignored is still reported.
+  call term_sendkeys(buf, ":let g:moved = 0 | call MoveThenIgnore()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'moved:' g:moved\<CR>")
+  call WaitForAssert({-> assert_match('^moved: 1\>', term_getline(buf, 10))}, 1000)
+
+  " Same as the first check, but moving with an Ex line address.
+  call term_sendkeys(buf, ":let g:moved = 0 | call MoveExWhileIgnored()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'moved:' g:moved\<CR>")
+  call WaitForAssert({-> assert_match('^moved: 0\>', term_getline(buf, 10))}, 1000)
+
+  " Only the move made while CursorMoved was not ignored is reported.
+  call term_sendkeys(buf, ":let g:moved = 0 | call MoveWithTwoStages()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'moved:' g:moved\<CR>")
+  call WaitForAssert({-> assert_match('^moved: 1\>', term_getline(buf, 10))}, 1000)
+
+  call StopVimInTerminal(buf)
+endfunc
+
+" Same as Test_eventignore_cursormoved(), but driven from Python, which
+" updates the screen after every command.
+func Test_eventignore_cursormoved_python()
+  CheckFeature python3
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+    call setline(1, range(1, 300))
+    let g:moved = 0
+    autocmd CursorMoved * let g:moved += 1
+    py3 << PYEOF
+    import vim
+    def Fun():
+        vim.command('set ei=all')
+        vim.command('100')
+        vim.command('200')
+        vim.command('echom "set ei: line = " .. line(".")')
+        vim.command('set ei=')
+    PYEOF
+  END
+  call writefile(lines, 'XTest_eventignore_cm_py', 'D')
+  let buf = RunVimInTerminal('-S XTest_eventignore_cm_py', {'rows': 10})
+
+  call term_sendkeys(buf, ":let g:moved = 0 | py3 Fun()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'moved:' g:moved\<CR>")
+  call WaitForAssert({-> assert_match('^moved: 0\>', term_getline(buf, 10))}, 1000)
+
+  call StopVimInTerminal(buf)
+endfunc
+
+" The other events that are triggered by comparing against a stored value.
+func Test_eventignore_deferred_events()
+  CheckRunVimInTerminal
+
+  let lines =<< trim END
+    call setline(1, range(1, 300))
+    let g:textchanged = 0
+    let g:winscrolled = 0
+    autocmd TextChanged * let g:textchanged += 1
+    autocmd WinScrolled * let g:winscrolled += 1
+
+    func ChangeWhileIgnored()
+      set eventignore=all
+      call setline(1, 'changed')
+      set eventignore=
+    endfunc
+
+    func ScrollWhileIgnored()
+      set eventignore=all
+      call winrestview({'topline': 100, 'lnum': 100})
+      set eventignore=
+    endfunc
+
+    func ChangeThenIgnore()
+      call setline(2, 'changed too')
+      set eventignore=all
+      set eventignore=
+    endfunc
+
+    func ScrollThenIgnore()
+      call winrestview({'topline': 150, 'lnum': 150})
+      set eventignore=all
+      set eventignore=
+    endfunc
+  END
+  call writefile(lines, 'XTest_eventignore_deferred', 'D')
+  let buf = RunVimInTerminal('-S XTest_eventignore_deferred', {'rows': 10})
+
+  call term_sendkeys(buf, ":let g:textchanged = 0 | call ChangeWhileIgnored()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'textchanged:' g:textchanged\<CR>")
+  call WaitForAssert({-> assert_match('^textchanged: 0\>', term_getline(buf, 10))}, 1000)
+
+  call term_sendkeys(buf, ":let g:winscrolled = 0 | call ScrollWhileIgnored()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'winscrolled:' g:winscrolled\<CR>")
+  call WaitForAssert({-> assert_match('^winscrolled: 0\>', term_getline(buf, 10))}, 1000)
+
+  " What happened before the events were ignored is still reported.
+  call term_sendkeys(buf, ":let g:textchanged = 0 | call ChangeThenIgnore()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'textchanged:' g:textchanged\<CR>")
+  call WaitForAssert({-> assert_match('^textchanged: 1\>', term_getline(buf, 10))}, 1000)
+
+  call term_sendkeys(buf, ":let g:winscrolled = 0 | call ScrollThenIgnore()\<CR>")
+  call TermWait(buf)
+  call term_sendkeys(buf, ":echo 'winscrolled:' g:winscrolled\<CR>")
+  call WaitForAssert({-> assert_match('^winscrolled: 1\>', term_getline(buf, 10))}, 1000)
+
+  call StopVimInTerminal(buf)
+endfunc
+
 func Test_VimResized_and_window_width_not_equalized()
   CheckRunVimInTerminal
 
diff --git a/src/version.c b/src/version.c
index a2040e4eb..494d36f01 100644
--- a/src/version.c
+++ b/src/version.c
@@ -763,6 +763,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    991,
 /**/
     990,
 /**/

-- 
-- 
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 [email protected].
To view this discussion visit https://groups.google.com/d/msgid/vim_dev/E1wxAKv-00E7QC-FO%40256bit.org.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.