changeset 18098:a2870e6f5b45 v8.1.2044

patch 8.1.2044: no easy way to process postponed work Commit: https://github.com/vim/vim/commit/8aeec40207b5adcd3a155277dc4f29189343b963 Author: Bram Moolenaar <Bram@vim.org> Date: Sun Sep 15 23:02:04 2019 +0200 patch 8.1.2044: no easy way to process postponed work Problem: No easy way to process postponed work. (Paul Jolly) Solution: Add the SafeState autocommand event.
author Bram Moolenaar <Bram@vim.org>
date Sun, 15 Sep 2019 23:15:03 +0200
parents 1608410ff8f3
children 349ea1199661
files runtime/doc/autocmd.txt src/autocmd.c src/channel.c src/edit.c src/ex_getln.c src/main.c src/proto/main.pro src/version.c src/vim.h
diffstat 9 files changed, 128 insertions(+), 13 deletions(-) [+]
line wrap: on
line diff
--- a/runtime/doc/autocmd.txt
+++ b/runtime/doc/autocmd.txt
@@ -355,6 +355,9 @@ Name			triggered by ~
 			when popup menu visible
 |TextYankPost|		after text has been yanked or deleted
 
+|SafeState|		nothing pending, going to wait for the user to type a
+			character
+
 |ColorSchemePre|	before loading a color scheme
 |ColorScheme|		after loading a color scheme
 
@@ -955,6 +958,27 @@ RemoteReply			When a reply from a Vim th
 				Note that even if an autocommand is defined,
 				the reply should be read with |remote_read()|
 				to consume it.
+							*SafeState*
+SafeState			When nothing is pending, going to wait for the
+				user to type a character.
+				This will not be triggered when:
+				- an operator is pending
+				- a register was entered with "r
+				- halfway executing a command
+				- executing a mapping
+				- there is typeahead
+				- Insert mode completion is active
+				- Command line completion is active
+				You can use `mode()` to find out what state
+				Vim is in.  That may be:
+				- VIsual mode
+				- Normal mode
+				- Insert mode
+				- Command-line mode
+				Depending on what you want to do, you may also
+				check more with `state()`, e.g. whether the
+				screen was scrolled for messages.
+
 							*SessionLoadPost*
 SessionLoadPost			After loading the session file created using
 				the |:mksession| command.
--- a/src/autocmd.c
+++ b/src/autocmd.c
@@ -155,6 +155,7 @@ static struct event_name
     {"QuickFixCmdPre",	EVENT_QUICKFIXCMDPRE},
     {"QuitPre",		EVENT_QUITPRE},
     {"RemoteReply",	EVENT_REMOTEREPLY},
+    {"SafeState",	EVENT_SAFESTATE},
     {"SessionLoadPost",	EVENT_SESSIONLOADPOST},
     {"ShellCmdPost",	EVENT_SHELLCMDPOST},
     {"ShellFilterPost",	EVENT_SHELLFILTERPOST},
--- a/src/channel.c
+++ b/src/channel.c
@@ -3589,10 +3589,17 @@ channel_read_json_block(
     sock_T	fd;
     int		timeout;
     chanpart_T	*chanpart = &channel->ch_part[part];
+    int		retval = FAIL;
 
     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);
+
     for (;;)
     {
 	more = channel_parse_json(channel, part);
@@ -3600,10 +3607,9 @@ channel_read_json_block(
 	// search for message "id"
 	if (channel_get_json(channel, part, id, TRUE, rettv) == OK)
 	{
-	    if (id >= 0)
-		channel_remove_block_id(chanpart, id);
 	    ch_log(channel, "Received JSON for id %d", id);
-	    return OK;
+	    retval = OK;
+	    break;
 	}
 
 	if (!more)
@@ -3659,7 +3665,11 @@ channel_read_json_block(
     }
     if (id >= 0)
 	channel_remove_block_id(chanpart, id);
-    return FAIL;
+
+    // This may trigger a SafeState autocommand.
+    leave_unsafe_state();
+
+    return retval;
 }
 
 /*
@@ -4195,9 +4205,9 @@ ch_raw_common(typval_T *argvars, typval_
     free_job_options(&opt);
 }
 
-# define KEEP_OPEN_TIME 20  /* msec */
-
-# if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO)
+#define KEEP_OPEN_TIME 20  /* msec */
+
+#if (defined(UNIX) && !defined(HAVE_SELECT)) || defined(PROTO)
 /*
  * Add open channels to the poll struct.
  * Return the adjusted struct index.
@@ -4288,9 +4298,9 @@ channel_poll_check(int ret_in, void *fds
 
     return ret;
 }
-# endif /* UNIX && !HAVE_SELECT */
-
-# if (!defined(MSWIN) && defined(HAVE_SELECT)) || defined(PROTO)
+#endif /* UNIX && !HAVE_SELECT */
+
+#if (!defined(MSWIN) && defined(HAVE_SELECT)) || defined(PROTO)
 
 /*
  * The "fd_set" type is hidden to avoid problems with the function proto.
@@ -4381,7 +4391,7 @@ channel_select_check(int ret_in, void *r
 	if (ret > 0 && in_part->ch_fd != INVALID_FD
 					    && FD_ISSET(in_part->ch_fd, wfds))
 	{
-	    /* Clear the flag first, ch_fd may change in channel_write_input(). */
+	    // Clear the flag first, ch_fd may change in channel_write_input().
 	    FD_CLR(in_part->ch_fd, wfds);
 	    channel_write_input(channel);
 	    --ret;
@@ -4390,11 +4400,12 @@ channel_select_check(int ret_in, void *r
 
     return ret;
 }
-# endif /* !MSWIN && HAVE_SELECT */
+#endif // !MSWIN && HAVE_SELECT
 
 /*
  * Execute queued up commands.
- * Invoked from the main loop when it's safe to execute received commands.
+ * Invoked from the main loop when it's safe to execute received commands,
+ * and during a blocking wait for ch_evalexpr().
  * Return TRUE when something was done.
  */
     int
--- a/src/edit.c
+++ b/src/edit.c
@@ -1509,6 +1509,11 @@ ins_redraw(int ready)	    // not busy wi
 					(linenr_T)(curwin->w_cursor.lnum + 1));
     }
 
+    // Trigger SafeState if nothing is pending.
+    may_trigger_safestate(ready
+	    && !ins_compl_active()
+	    && !pum_visible());
+
 #if defined(FEAT_CONCEAL)
     if ((conceal_update_lines
 	    && (conceal_old_cursor_line != conceal_new_cursor_line
--- a/src/ex_getln.c
+++ b/src/ex_getln.c
@@ -971,6 +971,9 @@ getcmdline_int(
 				   that occurs while typing a command should
 				   cause the command not to be executed. */
 
+	// Trigger SafeState if nothing is pending.
+	may_trigger_safestate(xpc.xp_numfiles <= 0);
+
 	cursorcmd();		/* set the cursor on the right spot */
 
 	/* Get a character.  Ignore K_IGNORE and K_NOP, they should not do
--- a/src/main.c
+++ b/src/main.c
@@ -1028,6 +1028,64 @@ is_not_a_term()
     return params.not_a_term;
 }
 
+
+static int	was_safe = FALSE;
+static int	not_safe_now = 0;
+
+/*
+ * Trigger SafeState if currently in a safe state for main_loop().
+ */
+    static void
+may_trigger_safestate_main(oparg_T *oap)
+{
+    may_trigger_safestate(
+	    !finish_op
+	    && oap->prev_opcount > 0
+	    && oap->prev_count0 == 0
+	    && oap->op_type == OP_NOP
+	    && oap->regname == NUL
+	    && restart_edit == 0);
+}
+
+/*
+ * Trigger SafeState if currently in s safe state, that is "safe" is TRUE and
+ * there is no typeahead.
+ */
+    void
+may_trigger_safestate(int safe)
+{
+    int is_safe = safe
+		    && stuff_empty()
+		    && typebuf.tb_len == 0
+		    && !global_busy;
+
+    if (is_safe)
+	apply_autocmds(EVENT_SAFESTATE, NULL, NULL, FALSE, curbuf);
+    was_safe = is_safe;
+}
+
+/*
+ * Entering a not-safe state.
+ */
+    void
+enter_unsafe_state(void)
+{
+    ++not_safe_now;
+}
+
+/*
+ * Leaving a not-safe state.  Trigger SafeState if we were in a safe state
+ * before first calling enter_not_safe_state().
+ */
+    void
+leave_unsafe_state(void)
+{
+    --not_safe_now;
+    if (not_safe_now == 0 && was_safe)
+	apply_autocmds(EVENT_SAFESTATE, NULL, NULL, FALSE, curbuf);
+}
+
+
 /*
  * Main loop: Execute Normal mode commands until exiting Vim.
  * Also used to handle commands in the command-line window, until the window
@@ -1133,6 +1191,9 @@ main_loop(
 	    msg_scroll = FALSE;
 	quit_more = FALSE;
 
+	// it's not safe unless may_trigger_safestate_main() is called
+	was_safe = FALSE;
+
 	/*
 	 * If skip redraw is set (for ":" in wait_return()), don't redraw now.
 	 * If there is nothing in the stuff_buffer or do_redraw is TRUE,
@@ -1211,6 +1272,10 @@ main_loop(
 		curbuf->b_last_changedtick = CHANGEDTICK(curbuf);
 	    }
 
+	    // If nothing is pending and we are going to wait for the user to
+	    // type a character, trigger SafeState.
+	    may_trigger_safestate_main(&oa);
+
 #if defined(FEAT_DIFF)
 	    // Updating diffs from changed() does not always work properly,
 	    // esp. updating folds.  Do an update just before redrawing if
--- a/src/proto/main.pro
+++ b/src/proto/main.pro
@@ -2,6 +2,9 @@
 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 leave_unsafe_state(void);
 void main_loop(int cmdwin, int noexmode);
 void getout_preserve_modified(int exitval);
 void getout(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 */
 /**/
+    2044,
+/**/
     2043,
 /**/
     2042,
--- a/src/vim.h
+++ b/src/vim.h
@@ -1315,6 +1315,7 @@ enum auto_event
     EVENT_QUICKFIXCMDPRE,	// before :make, :grep etc.
     EVENT_QUITPRE,		// before :quit
     EVENT_REMOTEREPLY,		// upon string reception from a remote vim
+    EVENT_SAFESTATE,		// going to wait for a character
     EVENT_SESSIONLOADPOST,	// after loading a session file
     EVENT_SHELLCMDPOST,		// after ":!cmd"
     EVENT_SHELLFILTERPOST,	// after ":1,2!cmd", ":w !cmd", ":r !cmd".