diff options
author | Björn Linse <bjorn.linse@gmail.com> | 2017-11-22 22:35:20 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-11-22 22:35:20 +0100 |
commit | a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b (patch) | |
tree | 9bcf55ca9e4ecdbea9390dc750d39027ba7d3acf | |
parent | 9393be477a733183c2a992cc8728e3ebc65a3118 (diff) | |
download | rneovim-a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b.tar.gz rneovim-a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b.tar.bz2 rneovim-a4f6cec7a31ff8dbfa089b9e22227afbeb951e9b.zip |
cmdline: CmdlineEnter and CmdlineLeave autocommands (#7422)
vim-patch:fafcf0dd59fd
patch 8.0.1206: no autocmd for entering or leaving the command line
Problem: No autocmd for entering or leaving the command line.
Solution: Add CmdlineEnter and CmdlineLeave.
https://github.com/vim/vim/commit/fafcf0dd59fd9c4ef743bb333ae40d1d322b6079
-rw-r--r-- | runtime/doc/autocmd.txt | 24 | ||||
-rw-r--r-- | runtime/doc/cmdline.txt | 2 | ||||
-rw-r--r-- | runtime/doc/eval.txt | 11 | ||||
-rw-r--r-- | src/nvim/auevents.lua | 2 | ||||
-rw-r--r-- | src/nvim/eval/typval.c | 23 | ||||
-rw-r--r-- | src/nvim/ex_getln.c | 57 | ||||
-rw-r--r-- | src/nvim/globals.h | 2 | ||||
-rw-r--r-- | src/nvim/testdir/test_autocmd.vim | 22 | ||||
-rw-r--r-- | src/nvim/version.c | 1 | ||||
-rw-r--r-- | test/functional/autocmd/cmdline_spec.lua | 117 |
10 files changed, 256 insertions, 5 deletions
diff --git a/runtime/doc/autocmd.txt b/runtime/doc/autocmd.txt index 6cffbf4cb6..740f44414a 100644 --- a/runtime/doc/autocmd.txt +++ b/runtime/doc/autocmd.txt @@ -310,6 +310,8 @@ Name triggered by ~ |TabNew| when creating a new tab page |TabNewEntered| after entering a new tab page |TabClosed| after closing a tab page +|CmdlineEnter| after entering cmdline mode +|CmdlineLeave| before leaving cmdline mode |CmdwinEnter| after entering the command-line window |CmdwinLeave| before leaving the command-line window @@ -492,6 +494,28 @@ CmdUndefined When a user command is used but it isn't command is defined. An alternative is to always define the user command and have it invoke an autoloaded function. See |autoload|. + *CmdlineEnter* +CmdlineEnter After moving the cursor to the command line, + where the user can type a command or search + string. + <afile> is set to a single character, + indicating the type of command-line. + |cmdline-char| + Sets these |v:event| keys: + cmdlevel + cmdtype + *CmdlineLeave* +CmdlineLeave Before leaving the command line. + <afile> is set to a single character, + indicating the type of command-line. + |cmdline-char| + Sets these |v:event| keys: + abort (mutable) + cmdlevel + cmdtype + Note: `abort` can only be changed from false + to true. An autocmd cannot execute an already + aborted cmdline by changing it to false. *CmdwinEnter* CmdwinEnter After entering the command-line window. Useful for setting options specifically for diff --git a/runtime/doc/cmdline.txt b/runtime/doc/cmdline.txt index 4956b9e6fe..00e654841c 100644 --- a/runtime/doc/cmdline.txt +++ b/runtime/doc/cmdline.txt @@ -1088,7 +1088,7 @@ Another example: > :au CmdwinEnter [/?] startinsert This will make Vim start in Insert mode in the command-line window. - *cmdwin-char* + *cmdline-char* *cmdwin-char* The character used for the pattern indicates the type of command-line: : normal Ex command > debug mode command |debug-mode| diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index b752667d9a..ea4393bc6f 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -1527,16 +1527,21 @@ v:event Dictionary of event data for the current |autocommand|. Valid < Keys vary by event; see the documentation for the specific event, e.g. |DirChanged| or |TextYankPost|. KEY DESCRIPTION ~ - cwd Current working directory + abort Whether the event triggered during + an aborting condition, i e |c_Esc| or + |c_CTRL-c|for |CmdlineLeave|. + cmdlevel Level of cmdline. + cmdtype Type of cmdline, |cmdline-char|. + cwd Current working directory. scope Event-specific scope name. operator Current |operator|. Also set for Ex commands (unlike |v:operator|). For example if |TextYankPost| is triggered by the |:yank| Ex command then - `v:event['operator']` is "y". + `v:event.operator` is "y". regcontents Text stored in the register as a |readfile()|-style list of lines. - regname Requested register (e.g "x" for "xyy) + regname Requested register (e.g "x" for "xyy) or the empty string for an unnamed operation. regtype Type of register as returned by diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 68a47c244f..7dfaf54ff0 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -19,6 +19,8 @@ return { 'BufWriteCmd', -- write buffer using command 'BufWritePost', -- after writing a buffer 'BufWritePre', -- before writing a buffer + 'CmdLineEnter', -- after entering cmdline mode + 'CmdLineLeave', -- before leaving cmdline mode 'CmdUndefined', -- command undefined 'CmdWinEnter', -- after entering the cmdline window 'CmdWinLeave', -- before leaving the cmdline window diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index c339a5cdd2..262ea922ef 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1374,6 +1374,29 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, return OK; } +/// Add a special entry to dictionary +/// +/// @param[out] d Dictionary to add entry to. +/// @param[in] key Key to add. +/// @param[in] key_len Key length. +/// @param[in] val SpecialVarValue to add. +/// +/// @return OK in case of success, FAIL when key already exists. +int tv_dict_add_special(dict_T *const d, const char *const key, + const size_t key_len, SpecialVarValue val) +{ + dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); + + item->di_tv.v_lock = VAR_UNLOCKED; + item->di_tv.v_type = VAR_SPECIAL; + item->di_tv.vval.v_special = val; + if (tv_dict_add(d, item) == FAIL) { + tv_dict_item_free(item); + return FAIL; + } + return OK; +} + /// Add a string entry to dictionary /// /// @param[out] d Dictionary to add entry to. diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 9c9ccbca4d..c1500e3121 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -348,8 +348,57 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) got_int = false; s->state.check = command_line_check; s->state.execute = command_line_execute; + + TryState tstate; + Error err = ERROR_INIT; + bool tl_ret = true; + dict_T *dict = get_vim_var_dict(VV_EVENT); + char firstcbuf[2]; + firstcbuf[0] = firstc > 0 ? firstc : '-'; + firstcbuf[1] = 0; + + if (has_event(EVENT_CMDLINEENTER)) { + // set v:event to a dictionary with information about the commandline + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + try_enter(&tstate); + + apply_autocmds(EVENT_CMDLINEENTER, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + tv_dict_clear(dict); + + + tl_ret = try_leave(&tstate, &err); + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + redrawcmd(); + } + tl_ret = true; + } + state_enter(&s->state); + if (has_event(EVENT_CMDLINELEAVE)) { + tv_dict_add_str(dict, S_LEN("cmdtype"), firstcbuf); + tv_dict_add_nr(dict, S_LEN("cmdlevel"), ccline.level); + tv_dict_set_keys_readonly(dict); + // not readonly: + tv_dict_add_special(dict, S_LEN("abort"), + s->gotesc ? kSpecialVarTrue : kSpecialVarFalse); + try_enter(&tstate); + apply_autocmds(EVENT_CMDLINELEAVE, (char_u *)firstcbuf, (char_u *)firstcbuf, + false, curbuf); + // error printed below, to avoid redraw issues + tl_ret = try_leave(&tstate, &err); + if (tv_dict_get_number(dict, "abort") != 0) { + s->gotesc = 1; + } + tv_dict_clear(dict); + } + cmdmsg_rl = false; cmd_fkmap = 0; @@ -410,8 +459,14 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) msg_scroll = s->save_msg_scroll; redir_off = false; + if (!tl_ret && ERROR_SET(&err)) { + msg_putchar('\n'); + msg_printf_attr(hl_attr(HLF_E)|MSG_HIST, (char *)e_autocmd_err, err.msg); + api_clear_error(&err); + } + // When the command line was typed, no need for a wait-return prompt. - if (s->some_key_typed) { + if (s->some_key_typed && tl_ret) { need_wait_return = false; } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 8f804ff888..2b025c2c50 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -1167,6 +1167,8 @@ EXTERN char_u e_dirnotf[] INIT(= N_( EXTERN char_u e_unsupportedoption[] INIT(= N_("E519: Option not supported")); EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); +EXTERN char_u e_autocmd_err[] INIT(=N_( + "E920: autocmd has thrown an exception: %s")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 835df42a10..82c04abf5b 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -420,3 +420,25 @@ function Test_autocmd_bufwipe_in_SessLoadPost2() call delete(file) endfor endfunc + +func Test_Cmdline() + au! CmdlineEnter : let g:entered = expand('<afile>') + au! CmdlineLeave : let g:left = expand('<afile>') + let g:entered = 0 + let g:left = 0 + call feedkeys(":echo 'hello'\<CR>", 'xt') + call assert_equal(':', g:entered) + call assert_equal(':', g:left) + au! CmdlineEnter + au! CmdlineLeave + + au! CmdlineEnter / let g:entered = expand('<afile>') + au! CmdlineLeave / let g:left = expand('<afile>') + let g:entered = 0 + let g:left = 0 + call feedkeys("/hello<CR>", 'xt') + call assert_equal('/', g:entered) + call assert_equal('/', g:left) + au! CmdlineEnter + au! CmdlineLeave +endfunc diff --git a/src/nvim/version.c b/src/nvim/version.c index 1ceda55cf1..205c25b6cd 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -78,6 +78,7 @@ static char *features[] = { // clang-format off static const int included_patches[] = { + 1206, // 1026, 1025, 1024, diff --git a/test/functional/autocmd/cmdline_spec.lua b/test/functional/autocmd/cmdline_spec.lua new file mode 100644 index 0000000000..b0ce03b141 --- /dev/null +++ b/test/functional/autocmd/cmdline_spec.lua @@ -0,0 +1,117 @@ +local helpers = require('test.functional.helpers')(after_each) +local Screen = require('test.functional.ui.screen') + +local clear = helpers.clear +local command = helpers.command +local eq = helpers.eq +local expect = helpers.expect +local next_msg = helpers.next_message +local feed = helpers.feed +local meths = helpers.meths + +describe('cmdline autocommands', function() + local channel + before_each(function() + clear() + channel = meths.get_api_info()[1] + meths.set_var("channel",channel) + command("autocmd CmdlineEnter * call rpcnotify(g:channel, 'CmdlineEnter', v:event)") + command("autocmd CmdlineLeave * call rpcnotify(g:channel, 'CmdlineLeave', v:event)") + command("autocmd CmdWinEnter * call rpcnotify(g:channel, 'CmdWinEnter', v:event)") + command("autocmd CmdWinLeave * call rpcnotify(g:channel, 'CmdWinLeave', v:event)") + end) + + it('works', function() + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) + feed('redraw<cr>') + eq({'notification', 'CmdlineLeave', + {{cmdtype=':', cmdlevel=1, abort=false}}}, next_msg()) + + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) + + -- note: feed('bork<c-c>') might not consume 'bork' + -- due to out-of-band interupt handling + feed('bork<esc>') + eq({'notification', 'CmdlineLeave', + {{cmdtype=':', cmdlevel=1, abort=true}}}, next_msg()) + end) + + it('can abort cmdline', function() + command("autocmd CmdlineLeave * let v:event.abort= len(getcmdline())>15") + feed(":put! ='ok'<cr>") + expect([[ + ok + ]]) + + feed(":put! ='blah blah'<cr>") + expect([[ + ok + ]]) + end) + + it('handles errors correctly', function() + clear() + local screen = Screen.new(72, 8) + screen:attach() + screen:set_default_attr_ids({ + [1] = {bold = true, foreground = Screen.colors.Blue1}, + [2] = {foreground = Screen.colors.Grey100, background = Screen.colors.Red}, + [3] = {bold = true, foreground = Screen.colors.SeaGreen4}, + }) + command("autocmd CmdlineEnter * echoerr 'FAIL'") + command("autocmd CmdlineLeave * echoerr 'very error'") + feed(':') + screen:expect([[ + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + : | + {2:E920: autocmd has thrown an exception: Vim(echoerr):FAIL} | + :^ | + ]]) + feed("put ='lorem ipsum'<cr>") + screen:expect([[ + {1:~ }| + {1:~ }| + : | + {2:E920: autocmd has thrown an exception: Vim(echoerr):FAIL} | + :put ='lorem ipsum' | + {2:E920: autocmd has thrown an exception: Vim(echoerr):very error} | + | + {3:Press ENTER or type command to continue}^ | + ]]) + + feed('<cr>') + screen:expect([[ + | + ^lorem ipsum | + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + {1:~ }| + | + ]]) + end) + + it('works with nested cmdline', function() + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=1}}}, next_msg()) + feed('<c-r>=') + eq({'notification', 'CmdlineEnter', {{cmdtype='=', cmdlevel=2}}}, next_msg()) + feed('<c-f>') + eq({'notification', 'CmdWinEnter', {{}}}, next_msg()) + feed(':') + eq({'notification', 'CmdlineEnter', {{cmdtype=':', cmdlevel=3}}}, next_msg()) + feed('<c-c>') + eq({'notification', 'CmdlineLeave', {{cmdtype=':', cmdlevel=3, abort=true}}}, next_msg()) + feed('<c-c>') + eq({'notification', 'CmdWinLeave', {{}}}, next_msg()) + feed('1+2<cr>') + eq({'notification', 'CmdlineLeave', {{cmdtype='=', cmdlevel=2, abort=false}}}, next_msg()) + end) +end) |