# HG changeset patch # User Bram Moolenaar # Date 1568835003 -7200 # Node ID 7f57ea9a4ba88f83fea1ec09d7ae8ded877d13f4 # Parent bd18e9db370ef6be4415e8fd1ad49b8949cc828b patch 8.1.2053: SafeStateAgain not triggered if callback uses feedkeys() Commit: https://github.com/vim/vim/commit/d103ee78432f9036d243b18dd5aac1263d3b7dc9 Author: Bram Moolenaar Date: Wed Sep 18 21:15:31 2019 +0200 patch 8.1.2053: SafeStateAgain not triggered if callback uses feedkeys() Problem: SafeStateAgain not triggered if callback uses feedkeys(). Solution: Check for safe state in the input loop. Make log messages easier to find. Add 'S' flag to state(). diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -9060,7 +9060,8 @@ state([{what}]) *state()* added. E.g, this checks if the screen has scrolled: > if state('s') != '' < - These characters indicate the state: + These characters indicate the state, generally indicating that + something is busy: m halfway a mapping, :normal command, feedkeys() or stuffed command o operator pending or waiting for a command argument @@ -9068,7 +9069,9 @@ state([{what}]) *state()* x executing an autocommand w blocked on waiting, e.g. ch_evalexpr() and ch_read(), ch_readraw() when reading json. - c callback invoked (repeats for recursiveness up to "ccc") + S not triggering SafeState or SafeStateAgain + c callback invoked, including timer (repeats for + recursiveness up to "ccc") s screen has scrolled for messages str2float({expr}) *str2float()* diff --git a/src/getchar.c b/src/getchar.c --- a/src/getchar.c +++ b/src/getchar.c @@ -933,7 +933,7 @@ ins_typebuf( init_typebuf(); if (++typebuf.tb_change_cnt == 0) typebuf.tb_change_cnt = 1; - state_no_longer_safe(); + state_no_longer_safe("ins_typebuf()"); addlen = (int)STRLEN(str); @@ -1797,7 +1797,7 @@ vgetc(void) // Need to process the character before we know it's safe to do something // else. if (c != K_IGNORE) - state_no_longer_safe(); + state_no_longer_safe("key typed"); return c; } @@ -2047,6 +2047,7 @@ parse_queued_messages(void) int i; int save_may_garbage_collect = may_garbage_collect; static int entered = 0; + int was_safe = get_was_safe_state(); // Do not handle messages while redrawing, because it may cause buffers to // change or be wiped while they are being redrawn. @@ -2102,7 +2103,7 @@ parse_queued_messages(void) // 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) + if (entered == 1 && was_safe) may_trigger_safestateagain(); may_garbage_collect = save_may_garbage_collect; diff --git a/src/main.c b/src/main.c --- a/src/main.c +++ b/src/main.c @@ -1049,24 +1049,33 @@ op_pending(void) } /* + * Return whether currently it is safe, assuming it was safe before (high level + * state didn't change). + */ + static int +is_safe_now(void) +{ + return stuff_empty() + && typebuf.tb_len == 0 + && scriptin[curscript] == NULL + && !global_busy; +} + +/* * 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 - && scriptin[curscript] == NULL - && !global_busy; + int is_safe = safe && is_safe_now(); #ifdef FEAT_JOB_CHANNEL if (was_safe != is_safe) // Only log when the state changes, otherwise it happens at nearly // every key stroke. - ch_log(NULL, is_safe ? "Start triggering SafeState" - : "Stop triggering SafeState"); + ch_log(NULL, is_safe ? "SafeState: Start triggering" + : "SafeState: Stop triggering"); #endif if (is_safe) apply_autocmds(EVENT_SAFESTATE, NULL, NULL, FALSE, curbuf); @@ -1079,15 +1088,21 @@ may_trigger_safestate(int safe) * may_trigger_safestate(). */ void -state_no_longer_safe(void) +state_no_longer_safe(char *reason UNUSED) { #ifdef FEAT_JOB_CHANNEL if (was_safe) - ch_log(NULL, "safe state reset"); + ch_log(NULL, "SafeState: reset: %s", reason); #endif was_safe = FALSE; } + int +get_was_safe_state(void) +{ + return was_safe; +} + /* * Invoked when leaving code that invokes callbacks. Then trigger * SafeStateAgain, if it was safe when starting to wait for a character. @@ -1095,16 +1110,28 @@ state_no_longer_safe(void) void may_trigger_safestateagain(void) { + if (!was_safe) + { + // If the safe state was reset in state_no_longer_safe(), e.g. because + // of calling feedkeys(), we check if it's now safe again (all keys + // were consumed). + was_safe = is_safe_now(); +#ifdef FEAT_JOB_CHANNEL + if (was_safe) + ch_log(NULL, "SafeState: undo reset"); +#endif + } if (was_safe) { #ifdef FEAT_JOB_CHANNEL - ch_log(NULL, "Leaving unsafe area, triggering SafeStateAgain"); + ch_log(NULL, "SafeState: back to waiting, triggering SafeStateAgain"); #endif apply_autocmds(EVENT_SAFESTATEAGAIN, NULL, NULL, FALSE, curbuf); } #ifdef FEAT_JOB_CHANNEL else - ch_log(NULL, "Leaving unsafe area, not triggering SafeStateAgain"); + ch_log(NULL, + "SafeState: back to waiting, not triggering SafeStateAgain"); #endif } diff --git a/src/proto/main.pro b/src/proto/main.pro --- a/src/proto/main.pro +++ b/src/proto/main.pro @@ -4,7 +4,8 @@ void common_init(mparm_T *paramp); int is_not_a_term(void); int op_pending(void); void may_trigger_safestate(int safe); -void state_no_longer_safe(void); +void state_no_longer_safe(char *reason); +int get_was_safe_state(void); void may_trigger_safestateagain(void); void main_loop(int cmdwin, int noexmode); void getout_preserve_modified(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 */ /**/ + 2053, +/**/ 2052, /**/ 2051,