aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/auevents.lua1
-rw-r--r--src/nvim/edit.c5
-rw-r--r--src/nvim/eval.c13
-rw-r--r--src/nvim/eval.lua37
-rw-r--r--src/nvim/eval/funcs.c44
-rw-r--r--src/nvim/ex_getln.c5
-rw-r--r--src/nvim/generators/gen_eval.lua1
-rw-r--r--src/nvim/getchar.c7
-rw-r--r--src/nvim/normal.c30
-rw-r--r--src/nvim/state.c47
10 files changed, 188 insertions, 2 deletions
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index f023ee1340..696df7c534 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -85,6 +85,7 @@ return {
'RecordingEnter', -- when starting to record a macro
'RecordingLeave', -- just before a macro stops recording
'RemoteReply', -- upon string reception from a remote vim
+ 'SafeState', -- going to wait for a character
'SearchWrapped', -- after the search wrapped around
'SessionLoadPost', -- after loading a session file
'ShellCmdPost', -- after ":!cmd"
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index b8d2eca810..d980699162 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1355,6 +1355,11 @@ void ins_redraw(bool ready)
curbuf->b_changed_invalid = false;
}
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(ready
+ && !ins_compl_active()
+ && !pum_visible());
+
pum_check_clear();
show_cursor_info_later(false);
if (must_redraw) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 08c5ef743b..052d67b05f 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -5994,6 +5994,13 @@ bool callback_from_typval(Callback *const callback, const typval_T *const arg)
return true;
}
+static int callback_depth = 0;
+
+int get_callback_depth(void)
+{
+ return callback_depth;
+}
+
/// @return whether the callback could be called.
bool callback_call(Callback *const callback, const int argcount_in, typval_T *const argvars_in,
typval_T *const rettv)
@@ -6041,7 +6048,11 @@ bool callback_call(Callback *const callback, const int argcount_in, typval_T *co
funcexe.fe_lastline = curwin->w_cursor.lnum;
funcexe.fe_evaluate = true;
funcexe.fe_partial = partial;
- return call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+
+ callback_depth++;
+ int ret = call_func(name, -1, rettv, argcount_in, argvars_in, &funcexe);
+ callback_depth--;
+ return ret;
}
bool set_ref_in_callback(Callback *callback, int copyID, ht_stack_T **ht_stack,
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index be87201bbf..e786901c2f 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -6917,6 +6917,7 @@ M.funcs = {
If [expr] is supplied and it evaluates to a non-zero Number or
a non-empty String (|non-zero-arg|), then the full mode is
returned, otherwise only the first letter is returned.
+ Also see |state()|.
n Normal
no Operator-pending
@@ -10451,6 +10452,42 @@ M.funcs = {
params = { { 'what', 'any' } },
signature = 'stdpath({what})',
},
+ state = {
+ args = {0, 1},
+ base = 1,
+ desc = [=[
+ Return a string which contains characters indicating the
+ current state. Mostly useful in callbacks that want to do
+ work that may not always be safe. Roughly this works like:
+ - callback uses state() to check if work is safe to do.
+ If yes, then do it right away.
+ Otherwise add to work queue and add SafeState autocommand.
+ - When SafeState is triggered, check with state() if the work
+ can be done now, and if yes remove it from the queue and
+ execute.
+ Also see |mode()|.
+
+ When {what} is given only characters in this string will be
+ added. E.g, this checks if the screen has scrolled: >vim
+ if state('s') != ''
+
+ 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
+ a Insert mode autocomplete active
+ x executing an autocommand
+ S not triggering SafeState
+ c callback invoked, including timer (repeats for
+ recursiveness up to "ccc")
+ s screen has scrolled for messages
+ ]=],
+ fast = true,
+ name = 'state',
+ params = { { 'what', 'string' } },
+ signature = 'state([{what}])',
+ },
str2float = {
args = 1,
base = 1,
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index 250b5c5556..3c232a7163 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -4925,6 +4925,50 @@ static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->v_type = VAR_STRING;
}
+static void may_add_state_char(garray_T *gap, const char *include, uint8_t c)
+{
+ if (include == NULL || vim_strchr(include, c) != NULL) {
+ ga_append(gap, c);
+ }
+}
+
+/// "state()" function
+static void f_state(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ garray_T ga;
+ ga_init(&ga, 1, 20);
+ const char *include = NULL;
+
+ if (argvars[0].v_type != VAR_UNKNOWN) {
+ include = tv_get_string(&argvars[0]);
+ }
+
+ if (!(stuff_empty() && typebuf.tb_len == 0 && !using_script())) {
+ may_add_state_char(&ga, include, 'm');
+ }
+ if (op_pending()) {
+ may_add_state_char(&ga, include, 'o');
+ }
+ if (autocmd_busy) {
+ may_add_state_char(&ga, include, 'x');
+ }
+ if (ins_compl_active()) {
+ may_add_state_char(&ga, include, 'a');
+ }
+ if (!get_was_safe_state()) {
+ may_add_state_char(&ga, include, 'S');
+ }
+ for (int i = 0; i < get_callback_depth() && i < 3; i++) {
+ may_add_state_char(&ga, include, 'c');
+ }
+ if (msg_scrolled > 0) {
+ may_add_state_char(&ga, include, 's');
+ }
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = ga.ga_data;
+}
+
/// "msgpackdump()" function
static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
FUNC_ATTR_NONNULL_ALL
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 89f5e92c33..6af79bfd21 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -943,6 +943,8 @@ theend:
static int command_line_check(VimState *state)
{
+ CommandLineState *s = (CommandLineState *)state;
+
redir_off = true; // Don't redirect the typed command.
// Repeated, because a ":redir" inside
// completion may switch it on.
@@ -952,6 +954,9 @@ static int command_line_check(VimState *state)
// that occurs while typing a command should
// cause the command not to be executed.
+ // Trigger SafeState if nothing is pending.
+ may_trigger_safestate(s->xpc.xp_numfiles <= 0);
+
cursorcmd(); // set the cursor on the right spot
ui_cursor_shape();
return 1;
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index c7978f0d19..0433894465 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -36,6 +36,7 @@ hashpipe:write([[
#include "nvim/quickfix.h"
#include "nvim/runtime.h"
#include "nvim/search.h"
+#include "nvim/state.h"
#include "nvim/strings.h"
#include "nvim/sign.h"
#include "nvim/testing.h"
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 2e584e7cff..d10e021f14 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -885,6 +885,7 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
if (++typebuf.tb_change_cnt == 0) {
typebuf.tb_change_cnt = 1;
}
+ state_no_longer_safe("ins_typebuf()");
addlen = (int)strlen(str);
@@ -1625,6 +1626,12 @@ int vgetc(void)
// Execute Lua on_key callbacks.
nlua_execute_on_key(c);
+ // Need to process the character before we know it's safe to do something
+ // else.
+ if (c != K_IGNORE) {
+ state_no_longer_safe("key typed");
+ }
+
return c;
}
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index e72a0fe385..40bef6b580 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -482,6 +482,20 @@ bool check_text_or_curbuf_locked(oparg_T *oap)
return true;
}
+static oparg_T *current_oap = NULL;
+
+/// Check if an operator was started but not finished yet.
+/// Includes typing a count or a register name.
+bool op_pending(void)
+{
+ return !(current_oap != NULL
+ && !finish_op
+ && current_oap->prev_opcount == 0
+ && current_oap->prev_count0 == 0
+ && current_oap->op_type == OP_NOP
+ && current_oap->regname == NUL);
+}
+
/// Normal state entry point. This is called on:
///
/// - Startup, In this case the function never returns.
@@ -490,15 +504,18 @@ bool check_text_or_curbuf_locked(oparg_T *oap)
/// for example. Returns when re-entering ex mode(because ex mode recursion is
/// not allowed)
///
-/// This used to be called main_loop on main.c
+/// This used to be called main_loop() on main.c
void normal_enter(bool cmdwin, bool noexmode)
{
NormalState state;
normal_state_init(&state);
+ oparg_T *prev_oap = current_oap;
+ current_oap = &state.oa;
state.cmdwin = cmdwin;
state.noexmode = noexmode;
state.toplevel = (!cmdwin || cmdwin_result == 0) && !noexmode;
state_enter(&state.state);
+ current_oap = prev_oap;
}
static void normal_prepare(NormalState *s)
@@ -1295,6 +1312,13 @@ static void normal_check_buffer_modified(NormalState *s)
}
}
+/// If nothing is pending and we are going to wait for the user to
+/// type a character, trigger SafeState.
+static void normal_check_safe_state(NormalState *s)
+{
+ may_trigger_safestate(!op_pending() && restart_edit == 0);
+}
+
static void normal_check_folds(NormalState *s)
{
// Include a closed fold completely in the Visual area.
@@ -1387,6 +1411,9 @@ static int normal_check(VimState *state)
}
quit_more = false;
+ // it's not safe unless normal_check_safe_state() is called
+ state_no_longer_safe(NULL);
+
// 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,
// update cursor and redraw.
@@ -1403,6 +1430,7 @@ static int normal_check(VimState *state)
normal_check_text_changed(s);
normal_check_window_scrolled(s);
normal_check_buffer_modified(s);
+ normal_check_safe_state(s);
// Updating diffs from changed() does not always work properly,
// esp. updating folds. Do an update just before redrawing if
diff --git a/src/nvim/state.c b/src/nvim/state.c
index f3c2b52024..9a65e25a90 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -268,3 +268,50 @@ void may_trigger_modechanged(void)
restore_v_event(v_event, &save_v_event);
}
+
+/// When true in a safe state when starting to wait for a character.
+static bool was_safe = false;
+
+/// Return whether currently it is safe, assuming it was safe before (high level
+/// state didn't change).
+static bool is_safe_now(void)
+{
+ return stuff_empty()
+ && typebuf.tb_len == 0
+ && !using_script()
+ && !global_busy
+ && !debug_mode;
+}
+
+/// Trigger SafeState if currently in s safe state, that is "safe" is TRUE and
+/// there is no typeahead.
+void may_trigger_safestate(bool safe)
+{
+ bool is_safe = safe && is_safe_now();
+
+ if (was_safe != is_safe) {
+ // Only log when the state changes, otherwise it happens at nearly
+ // every key stroke.
+ DLOG(is_safe ? "SafeState: Start triggering" : "SafeState: Stop triggering");
+ }
+ if (is_safe) {
+ apply_autocmds(EVENT_SAFESTATE, NULL, NULL, false, curbuf);
+ }
+ was_safe = is_safe;
+}
+
+/// 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 state_no_longer_safe(const char *reason)
+{
+ if (was_safe && reason != NULL) {
+ DLOG("SafeState reset: %s", reason);
+ }
+ was_safe = false;
+}
+
+bool get_was_safe_state(void)
+{
+ return was_safe;
+}