# HG changeset patch # User Bram Moolenaar # Date 1568582103 -7200 # Node ID a2870e6f5b453631c7946ce0617b92043ea3be83 # Parent 1608410ff8f3fab6b6bf21e0a7e1661ad84b9a42 patch 8.1.2044: no easy way to process postponed work Commit: https://github.com/vim/vim/commit/8aeec40207b5adcd3a155277dc4f29189343b963 Author: Bram Moolenaar 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. diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt --- 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. diff --git a/src/autocmd.c b/src/autocmd.c --- 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}, diff --git a/src/channel.c b/src/channel.c --- 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 diff --git a/src/edit.c b/src/edit.c --- 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 diff --git a/src/ex_getln.c b/src/ex_getln.c --- 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 diff --git a/src/main.c b/src/main.c --- 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 diff --git a/src/proto/main.pro b/src/proto/main.pro --- 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); diff --git a/src/version.c b/src/version.c --- 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, diff --git a/src/vim.h b/src/vim.h --- 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".