aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBjörn Linse <bjorn.linse@gmail.com>2017-11-22 22:35:20 +0100
committerGitHub <noreply@github.com>2017-11-22 22:35:20 +0100
commita4f6cec7a31ff8dbfa089b9e22227afbeb951e9b (patch)
tree9bcf55ca9e4ecdbea9390dc750d39027ba7d3acf
parent9393be477a733183c2a992cc8728e3ebc65a3118 (diff)
downloadrneovim-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.txt24
-rw-r--r--runtime/doc/cmdline.txt2
-rw-r--r--runtime/doc/eval.txt11
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/eval/typval.c23
-rw-r--r--src/nvim/ex_getln.c57
-rw-r--r--src/nvim/globals.h2
-rw-r--r--src/nvim/testdir/test_autocmd.vim22
-rw-r--r--src/nvim/version.c1
-rw-r--r--test/functional/autocmd/cmdline_spec.lua117
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)