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,13 +1527,18 @@ 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)

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)