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.

fafcf0dd59
This commit is contained in:
Björn Linse 2017-11-22 22:35:20 +01:00 committed by GitHub
parent 9393be477a
commit a4f6cec7a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 256 additions and 5 deletions

View File

@ -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

View File

@ -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|

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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;
}

View File

@ -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"));

View File

@ -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

View File

@ -78,6 +78,7 @@ static char *features[] = {
// clang-format off
static const int included_patches[] = {
1206,
// 1026,
1025,
1024,

View File

@ -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)