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:
parent
9393be477a
commit
a4f6cec7a3
|
@ -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
|
||||
|
|
|
@ -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|
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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"));
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -78,6 +78,7 @@ static char *features[] = {
|
|||
|
||||
// clang-format off
|
||||
static const int included_patches[] = {
|
||||
1206,
|
||||
// 1026,
|
||||
1025,
|
||||
1024,
|
||||
|
|
|
@ -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)
|
Loading…
Reference in New Issue