changeset 18102:0d9ec3a2821f v8.1.2046

patch 8.1.2046: SafeState may be triggered at the wrong moment Commit: https://github.com/vim/vim/commit/69198cb8c08f124729c41a4681f2d142228a9139 Author: Bram Moolenaar <Bram@vim.org> Date: Mon Sep 16 21:58:13 2019 +0200 patch 8.1.2046: SafeState may be triggered at the wrong moment Problem: SafeState may be triggered at the wrong moment. Solution: Move it up higher to after where messages are processed. Add a SafeStateAgain event to tigger there.
author Bram Moolenaar <Bram@vim.org>
date Mon, 16 Sep 2019 22:00:04 +0200
parents 6d2d7e46c212
children 71bc51a0d8d8
files runtime/doc/autocmd.txt src/autocmd.c src/channel.c src/getchar.c src/main.c src/proto/main.pro src/version.c src/vim.h
diffstat 8 files changed, 40 insertions(+), 18 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -357,6 +357,7 @@ Name			triggered by ~
 
 |SafeState|		nothing pending, going to wait for the user to type a
 			character
+|SafeStateAgain|	repeated SafeState
 
 |ColorSchemePre|	before loading a color scheme
 |ColorScheme|		after loading a color scheme
@@ -978,6 +979,11 @@ SafeState			When nothing is pending, goi
 				Depending on what you want to do, you may also
 				check more with `state()`, e.g. whether the
 				screen was scrolled for messages.
+							*SafeStateAgain*
+SafeStateAgain			Like SafeState but after processing any
+				messages and invoking callbacks. This may be
+				triggered often, don't do something that takes
+				time.
 
 							*SessionLoadPost*
 SessionLoadPost			After loading the session file created using
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -156,6 +156,7 @@ static struct event_name
     {"QuitPre",		EVENT_QUITPRE},
     {"RemoteReply",	EVENT_REMOTEREPLY},
     {"SafeState",	EVENT_SAFESTATE},
+    {"SafeStateAgain",	EVENT_SAFESTATEAGAIN},
     {"SessionLoadPost",	EVENT_SESSIONLOADPOST},
     {"ShellCmdPost",	EVENT_SHELLCMDPOST},
     {"ShellFilterPost",	EVENT_SHELLFILTERPOST},
--- a/src/channel.c
+++ b/src/channel.c
@@ -3593,10 +3593,6 @@ channel_read_json_block(
 
     ch_log(channel, "Blocking read JSON for id %d", id);
 
-    // Not considered a safe state here, since we are processing a JSON message
-    // and parsing other messages while waiting.
-    enter_unsafe_state();
-
     if (id >= 0)
 	channel_add_block_id(chanpart, id);
 
@@ -3666,9 +3662,6 @@ channel_read_json_block(
     if (id >= 0)
 	channel_remove_block_id(chanpart, id);
 
-    // This may trigger a SafeState autocommand.
-    leave_unsafe_state();
-
     return retval;
 }
 
--- a/src/getchar.c
+++ b/src/getchar.c
@@ -933,6 +933,7 @@ ins_typebuf(
     init_typebuf();
     if (++typebuf.tb_change_cnt == 0)
 	typebuf.tb_change_cnt = 1;
+    state_no_longer_safe();
 
     addlen = (int)STRLEN(str);
 
@@ -1779,10 +1780,11 @@ vgetc(void)
      */
     may_garbage_collect = FALSE;
 #endif
+
 #ifdef FEAT_BEVAL_TERM
     if (c != K_MOUSEMOVE && c != K_IGNORE && c != K_CURSORHOLD)
     {
-	/* Don't trigger 'balloonexpr' unless only the mouse was moved. */
+	// Don't trigger 'balloonexpr' unless only the mouse was moved.
 	bevalexpr_due_set = FALSE;
 	ui_remove_balloon();
     }
@@ -1792,6 +1794,11 @@ vgetc(void)
 	c = K_IGNORE;
 #endif
 
+    // Need to process the character before we know it's safe to do something
+    // else.
+    if (c != K_IGNORE)
+	state_no_longer_safe();
+
     return c;
 }
 
@@ -2039,12 +2046,15 @@ parse_queued_messages(void)
     int	    old_curbuf_fnum = curbuf->b_fnum;
     int	    i;
     int	    save_may_garbage_collect = may_garbage_collect;
+    static int entered = 0;
 
     // Do not handle messages while redrawing, because it may cause buffers to
     // change or be wiped while they are being redrawn.
     if (updating_screen)
 	return;
 
+    ++entered;
+
     // may_garbage_collect is set in main_loop() to do garbage collection when
     // blocking to wait on a character.  We don't want that while parsing
     // messages, a callback may invoke vgetc() while lists and dicts are in use
@@ -2090,12 +2100,19 @@ parse_queued_messages(void)
 	break;
     }
 
+    // When not nested we'll go back to waiting for a typed character.  If it
+    // was safe before then this triggers a SafeStateAgain autocommand event.
+    if (entered == 1)
+	leave_unsafe_state();
+
     may_garbage_collect = save_may_garbage_collect;
 
     // If the current window or buffer changed we need to bail out of the
     // waiting loop.  E.g. when a job exit callback closes the terminal window.
     if (curwin->w_id != old_curwin_id || curbuf->b_fnum != old_curbuf_fnum)
 	ins_char_typebuf(K_IGNORE);
+
+    --entered;
 }
 #endif
 
--- a/src/main.c
+++ b/src/main.c
@@ -1029,8 +1029,8 @@ is_not_a_term()
 }
 
 
+// When TRUE in a safe state when starting to wait for a character.
 static int	was_safe = FALSE;
-static int	not_safe_now = 0;
 
 /*
  * Trigger SafeState if currently in a safe state for main_loop().
@@ -1057,6 +1057,7 @@ may_trigger_safestate(int safe)
     int is_safe = safe
 		    && stuff_empty()
 		    && typebuf.tb_len == 0
+		    && scriptin[curscript] == NULL
 		    && !global_busy;
 
     if (is_safe)
@@ -1065,24 +1066,25 @@ may_trigger_safestate(int safe)
 }
 
 /*
- * Entering a not-safe state.
+ * Something changed which causes the state possibly to be unsafe, e.g. a
+ * character was typed.  It will remain unsafe until the next call to
+ * may_trigger_safestate().
  */
     void
-enter_unsafe_state(void)
+state_no_longer_safe(void)
 {
-    ++not_safe_now;
+    was_safe = FALSE;
 }
 
 /*
- * Leaving a not-safe state.  Trigger SafeState if we were in a safe state
- * before first calling enter_not_safe_state().
+ * Invoked when leaving code that invokes callbacks.  Then trigger
+ * SafeStateAgain, if it was safe when starting to wait for a character.
  */
     void
 leave_unsafe_state(void)
 {
-    --not_safe_now;
-    if (not_safe_now == 0 && was_safe)
-	apply_autocmds(EVENT_SAFESTATE, NULL, NULL, FALSE, curbuf);
+    if (was_safe)
+	apply_autocmds(EVENT_SAFESTATEAGAIN, NULL, NULL, FALSE, curbuf);
 }
 
 
--- a/src/proto/main.pro
+++ b/src/proto/main.pro
@@ -3,7 +3,7 @@ int vim_main2(void);
 void common_init(mparm_T *paramp);
 int is_not_a_term(void);
 void may_trigger_safestate(int safe);
-void enter_unsafe_state(void);
+void state_no_longer_safe(void);
 void leave_unsafe_state(void);
 void main_loop(int cmdwin, int noexmode);
 void getout_preserve_modified(int exitval);
--- 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 */
 /**/
+    2046,
+/**/
     2045,
 /**/
     2044,
--- a/src/vim.h
+++ b/src/vim.h
@@ -1316,6 +1316,7 @@ enum auto_event
     EVENT_QUITPRE,		// before :quit
     EVENT_REMOTEREPLY,		// upon string reception from a remote vim
     EVENT_SAFESTATE,		// going to wait for a character
+    EVENT_SAFESTATEAGAIN,	// still waiting for a character
     EVENT_SESSIONLOADPOST,	// after loading a session file
     EVENT_SHELLCMDPOST,		// after ":!cmd"
     EVENT_SHELLFILTERPOST,	// after ":1,2!cmd", ":w !cmd", ":r !cmd".