diff options
author | zeertzjq <zeertzjq@outlook.com> | 2023-08-21 14:40:43 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-08-21 14:40:43 +0800 |
commit | 3b0515e674f279d6504a0fc055808cdf01eead99 (patch) | |
tree | 39f1120f66af1d5fb573241d06ce5d2d70d9a028 | |
parent | 91d8f2ac534a51859c0e3c6562d07c94b27f4478 (diff) | |
parent | 6aa29d0f01e715fe51de4f66dee377e4c1726229 (diff) | |
download | rneovim-3b0515e674f279d6504a0fc055808cdf01eead99.tar.gz rneovim-3b0515e674f279d6504a0fc055808cdf01eead99.tar.bz2 rneovim-3b0515e674f279d6504a0fc055808cdf01eead99.zip |
Merge pull request #24816 from zeertzjq/vim-8.1.2044
vim-patch:8.1.{2044,2046,2047,2048,2053,2066,2067,2068,2069,2099},8.2.4299: SafeState, state()
-rw-r--r-- | runtime/doc/autocmd.txt | 21 | ||||
-rw-r--r-- | runtime/doc/builtin.txt | 29 | ||||
-rw-r--r-- | runtime/doc/vim_diff.txt | 1 | ||||
-rw-r--r-- | runtime/lua/vim/_meta/vimfn.lua | 32 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 1 | ||||
-rw-r--r-- | src/nvim/edit.c | 5 | ||||
-rw-r--r-- | src/nvim/eval.c | 13 | ||||
-rw-r--r-- | src/nvim/eval.lua | 37 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 44 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 5 | ||||
-rw-r--r-- | src/nvim/generators/gen_eval.lua | 1 | ||||
-rw-r--r-- | src/nvim/getchar.c | 7 | ||||
-rw-r--r-- | src/nvim/normal.c | 30 | ||||
-rw-r--r-- | src/nvim/state.c | 47 | ||||
-rw-r--r-- | test/functional/autocmd/safestate_spec.lua | 57 | ||||
-rw-r--r-- | test/functional/vimscript/state_spec.lua | 69 | ||||
-rw-r--r-- | test/old/testdir/test_autocmd.vim | 33 | ||||
-rw-r--r-- | test/old/testdir/test_functions.vim | 60 |
18 files changed, 490 insertions, 2 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index a7c28e25d0..90211fc5db 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -862,6 +862,27 @@ RecordingLeave When a macro stops recording. Sets these |v:event| keys: regcontents regname + *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/runtime/doc/builtin.txt b/runtime/doc/builtin.txt index 92cedbdc80..2ce66d8cc2 100644 --- a/runtime/doc/builtin.txt +++ b/runtime/doc/builtin.txt @@ -4753,6 +4753,7 @@ mode([expr]) *mode()* 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 @@ -7368,6 +7369,34 @@ srand([{expr}]) *srand()* echo rand(seed) < +state([{what}]) *state()* + 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 + stdioopen({opts}) *stdioopen()* With |--headless| this opens stdin and stdout as a |channel|. May be called only once. See |channel-stdio|. stderr is not diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index b7bb52333d..29a7c50585 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -619,6 +619,7 @@ Eval: *v:sizeofpointer* Events: + *SafeStateAgain* *SigUSR1* Use |Signal| to detect `SIGUSR1` signal instead. Highlight groups: diff --git a/runtime/lua/vim/_meta/vimfn.lua b/runtime/lua/vim/_meta/vimfn.lua index 300210b296..70d1aa4a79 100644 --- a/runtime/lua/vim/_meta/vimfn.lua +++ b/runtime/lua/vim/_meta/vimfn.lua @@ -5713,6 +5713,7 @@ function vim.fn.mkdir(name, flags, prot) end --- 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 @@ -8744,6 +8745,37 @@ function vim.fn.sqrt(expr) end --- @return any function vim.fn.srand(expr) end +--- 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 +--- +--- @param what? string +--- @return any +function vim.fn.state(what) end + --- With |--headless| this opens stdin and stdout as a |channel|. --- May be called only once. See |channel-stdio|. stderr is not --- handled by this function, see |v:stderr|. 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; +} diff --git a/test/functional/autocmd/safestate_spec.lua b/test/functional/autocmd/safestate_spec.lua new file mode 100644 index 0000000000..73693749e4 --- /dev/null +++ b/test/functional/autocmd/safestate_spec.lua @@ -0,0 +1,57 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local eq = helpers.eq +local exec = helpers.exec +local feed = helpers.feed +local meths = helpers.meths + +before_each(clear) + +describe('SafeState autocommand', function() + local function create_autocmd() + exec([[ + let g:safe = 0 + autocmd SafeState * ++once let g:safe += 1 + ]]) + end + + it('with pending operator', function() + feed('d') + create_autocmd() + eq(0, meths.get_var('safe')) + feed('d') + eq(1, meths.get_var('safe')) + end) + + it('with specified register', function() + feed('"r') + create_autocmd() + eq(0, meths.get_var('safe')) + feed('x') + eq(1, meths.get_var('safe')) + end) + + it('with i_CTRL-O', function() + feed('i<C-O>') + create_autocmd() + eq(0, meths.get_var('safe')) + feed('x') + eq(1, meths.get_var('safe')) + end) + + it('with Insert mode completion', function() + feed('i<C-X><C-V>') + create_autocmd() + eq(0, meths.get_var('safe')) + feed('<C-X><C-Z>') + eq(1, meths.get_var('safe')) + end) + + it('with Cmdline completion', function() + feed(':<Tab>') + create_autocmd() + eq(0, meths.get_var('safe')) + feed('<C-E>') + eq(1, meths.get_var('safe')) + end) +end) diff --git a/test/functional/vimscript/state_spec.lua b/test/functional/vimscript/state_spec.lua new file mode 100644 index 0000000000..70f68a7494 --- /dev/null +++ b/test/functional/vimscript/state_spec.lua @@ -0,0 +1,69 @@ +local helpers = require('test.functional.helpers')(after_each) +local clear = helpers.clear +local eq = helpers.eq +local exec = helpers.exec +local exec_lua = helpers.exec_lua +local feed = helpers.feed +local meths = helpers.meths +local poke_eventloop = helpers.poke_eventloop + +before_each(clear) + +describe('state() function', function() + it('works', function() + meths.ui_attach(80, 24, {}) -- Allow hit-enter-prompt + + exec_lua([[ + function _G.Get_state_mode() + _G.res = { vim.fn.state(), vim.api.nvim_get_mode().mode:sub(1, 1) } + end + function _G.Run_timer() + local timer = vim.uv.new_timer() + timer:start(0, 0, function() + _G.Get_state_mode() + timer:close() + end) + end + ]]) + exec([[ + call setline(1, ['one', 'two', 'three']) + map ;; gg + set complete=. + func RunTimer() + call timer_start(0, {id -> v:lua.Get_state_mode()}) + endfunc + au Filetype foobar call v:lua.Get_state_mode() + ]]) + + -- Using a ":" command Vim is busy, thus "S" is returned + feed([[:call v:lua.Get_state_mode()<CR>]]) + eq({ 'S', 'n' }, exec_lua('return _G.res')) + + -- Using a timer callback + feed([[:call RunTimer()<CR>]]) + poke_eventloop() -- Allow polling for events + eq({ 'c', 'n' }, exec_lua('return _G.res')) + + -- Halfway a mapping + feed([[:call v:lua.Run_timer()<CR>;]]) + meths.get_mode() -- Allow polling for fast events + feed(';') + eq({ 'mS', 'n' }, exec_lua('return _G.res')) + + -- Insert mode completion + feed([[:call RunTimer()<CR>Got<C-N>]]) + poke_eventloop() -- Allow polling for events + feed('<Esc>') + eq({ 'aSc', 'i' }, exec_lua('return _G.res')) + + -- Autocommand executing + feed([[:set filetype=foobar<CR>]]) + eq({ 'xS', 'n' }, exec_lua('return _G.res')) + + -- messages scrolled + feed([[:call v:lua.Run_timer() | echo "one\ntwo\nthree"<CR>]]) + meths.get_mode() -- Allow polling for fast events + feed('<CR>') + eq({ 'Ss', 'r' }, exec_lua('return _G.res')) + end) +end) diff --git a/test/old/testdir/test_autocmd.vim b/test/old/testdir/test_autocmd.vim index c44988321f..959efc0b49 100644 --- a/test/old/testdir/test_autocmd.vim +++ b/test/old/testdir/test_autocmd.vim @@ -2959,6 +2959,39 @@ func Test_autocmd_in_try_block() au! BufEnter endfunc +func Test_autocmd_SafeState() + CheckRunVimInTerminal + + let lines =<< trim END + let g:safe = 0 + let g:again = '' + au SafeState * let g:safe += 1 + au SafeStateAgain * let g:again ..= 'x' + func CallTimer() + call timer_start(10, {id -> execute('let g:again ..= "t"')}) + endfunc + END + call writefile(lines, 'XSafeState') + let buf = RunVimInTerminal('-S XSafeState', #{rows: 6}) + + " Sometimes we loop to handle an K_IGNORE + call term_sendkeys(buf, ":echo g:safe\<CR>") + call WaitForAssert({-> assert_match('^[12] ', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, ":echo g:again\<CR>") + call WaitForAssert({-> assert_match('^xxxx', term_getline(buf, 6))}, 1000) + + call term_sendkeys(buf, ":let g:again = ''\<CR>:call CallTimer()\<CR>") + call term_wait(buf) + call term_sendkeys(buf, ":\<CR>") + call term_wait(buf) + call term_sendkeys(buf, ":echo g:again\<CR>") + call WaitForAssert({-> assert_match('xtx', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) + call delete('XSafeState') +endfunc + func Test_autocmd_CmdWinEnter() CheckRunVimInTerminal " There is not cmdwin switch, so diff --git a/test/old/testdir/test_functions.vim b/test/old/testdir/test_functions.vim index 4ad01c8531..5981b5214b 100644 --- a/test/old/testdir/test_functions.vim +++ b/test/old/testdir/test_functions.vim @@ -2565,6 +2565,66 @@ func Test_bufadd_bufload() call delete('XotherName') endfunc +func Test_state() + CheckRunVimInTerminal + + let lines =<< trim END + call setline(1, ['one', 'two', 'three']) + map ;; gg + set complete=. + func RunTimer() + call timer_start(10, {id -> execute('let g:state = state()') .. execute('let g:mode = mode()')}) + endfunc + au Filetype foobar let g:state = state()|let g:mode = mode() + END + call writefile(lines, 'XState') + let buf = RunVimInTerminal('-S XState', #{rows: 6}) + + " Using a ":" command Vim is busy, thus "S" is returned + call term_sendkeys(buf, ":echo 'state: ' .. state() .. '; mode: ' .. mode()\<CR>") + call WaitForAssert({-> assert_match('state: S; mode: n', term_getline(buf, 6))}, 1000) + call term_sendkeys(buf, ":\<CR>") + + " Using a timer callback + call term_sendkeys(buf, ":call RunTimer()\<CR>") + call term_wait(buf, 50) + let getstate = ":echo 'state: ' .. g:state .. '; mode: ' .. g:mode\<CR>" + call term_sendkeys(buf, getstate) + call WaitForAssert({-> assert_match('state: c; mode: n', term_getline(buf, 6))}, 1000) + + " Halfway a mapping + call term_sendkeys(buf, ":call RunTimer()\<CR>;") + call term_wait(buf, 50) + call term_sendkeys(buf, ";") + call term_sendkeys(buf, getstate) + call WaitForAssert({-> assert_match('state: mSc; mode: n', term_getline(buf, 6))}, 1000) + + " Insert mode completion (bit slower on Mac) + call term_sendkeys(buf, ":call RunTimer()\<CR>Got\<C-N>") + call term_wait(buf, 200) + call term_sendkeys(buf, "\<Esc>") + call term_sendkeys(buf, getstate) + call WaitForAssert({-> assert_match('state: aSc; mode: i', term_getline(buf, 6))}, 1000) + + " Autocommand executing + call term_sendkeys(buf, ":set filetype=foobar\<CR>") + call term_wait(buf, 50) + call term_sendkeys(buf, getstate) + call WaitForAssert({-> assert_match('state: xS; mode: n', term_getline(buf, 6))}, 1000) + + " Todo: "w" - waiting for ch_evalexpr() + + " messages scrolled + call term_sendkeys(buf, ":call RunTimer()\<CR>:echo \"one\\ntwo\\nthree\"\<CR>") + call term_wait(buf, 50) + call term_sendkeys(buf, "\<CR>") + call term_sendkeys(buf, getstate) + call WaitForAssert({-> assert_match('state: Scs; mode: r', term_getline(buf, 6))}, 1000) + + call StopVimInTerminal(buf) + call delete('XState') +endfunc + func Test_range() " destructuring let [x, y] = range(2) |