0
0
mirror of https://github.com/vim/vim.git synced 2025-09-27 04:14:06 -04:00

patch 8.2.4099: Vim9: cannot use Vim9 syntax in mapping

Problem:    Vim9: cannot use Vim9 syntax in mapping.
Solution:   Add <ScriptCmd> to use the script context for a command.
This commit is contained in:
Bram Moolenaar
2022-01-15 18:26:04 +00:00
parent 069613c9e8
commit e32c3c462c
13 changed files with 113 additions and 22 deletions

View File

@@ -284,6 +284,10 @@ This can be solved by inserting <Ignore> before the character that is
expression-mapped: >
nmap ! f!<Ignore>x
When defining a mapping in a |Vim9| script, the expression will be evaluated
in the context of that script. This means that script-local items can be
accessed in the expression.
Be very careful about side effects! The expression is evaluated while
obtaining characters, you may very well make the command dysfunctional.
For this reason the following is blocked:
@@ -342,9 +346,24 @@ Example of using <Cmd> halfway Insert mode: >
Unlike <expr> mappings, there are no special restrictions on the <Cmd>
command: it is executed as if an (unrestricted) |autocommand| was invoked.
*<ScriptCmd>*
<ScriptCmd> is like <Cmd> but sets the context to the script the mapping was
defined in, for the duration of the command execution. This is especially
useful for |Vim9| script. It also works to access an import, which is useful
in a plugin using an autoload script: >
vim9script
import autoload 'implementation.vim' as impl
nnoremap <silent> <F4> <ScriptCmd>impl.DoTheWork()<CR>
No matter where <F4> is typed, the "impl" import will be found in the script
context of where the mapping was defined. And since it's an autoload import,
the "implementation.vim" script will only be loaded once <F4> is typed, not
when the mapping is defined.
Note:
- Because <Cmd> avoids mode-changes it does not trigger |CmdlineEnter| and
|CmdlineLeave| events, because no user interaction is expected.
- Because <Cmd> and <ScriptCmd> avoid mode-changes it does not trigger
|CmdlineEnter| and |CmdlineLeave| events, because no user interaction is
expected.
- For the same reason, |keycodes| like <C-R><C-W> are interpreted as plain,
unmapped keys.
- The command is not echo'ed, no need for <silent>.
@@ -356,12 +375,13 @@ Note:
Visual mode. Use |:smap| to handle Select mode differently.
*E1255* *E1136*
<Cmd> commands must terminate, that is, they must be followed by <CR> in the
{rhs} of the mapping definition. |Command-line| mode is never entered.
<Cmd> and <ScriptCmd> commands must terminate, that is, they must be followed
by <CR> in the {rhs} of the mapping definition. |Command-line| mode is never
entered.
*E1137*
<Cmd> commands can have only normal characters and cannot contain special
characters like function keys.
<Cmd> and <ScriptCmd> commands can have only normal characters and cannot
contain special characters like function keys.
1.3 MAPPING AND MODES *:map-modes*

View File

@@ -1056,7 +1056,8 @@ doESCkey:
break;
case K_COMMAND: // <Cmd>command<CR>
do_cmdline(NULL, getcmdkeycmd, NULL, 0);
case K_SCRIPT_COMMAND: // <ScriptCmd>command<CR>
do_cmdkey_command(c, 0);
#ifdef FEAT_TERMINAL
if (term_use_loop())
// Started a terminal that gets the input, exit Insert mode.

View File

@@ -1772,11 +1772,11 @@ getcmdline_int(
c = safe_vgetc();
} while (c == K_IGNORE || c == K_NOP);
if (c == K_COMMAND)
if (c == K_COMMAND || c == K_SCRIPT_COMMAND)
{
int clen = ccline.cmdlen;
if (do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT) == OK)
if (do_cmdkey_command(c, DOCMD_NOWAIT) == OK)
{
if (clen == ccline.cmdlen)
trigger_cmdlinechanged = FALSE;

View File

@@ -83,6 +83,10 @@ static char_u noremapbuf_init[TYPELEN_INIT]; // initial typebuf.tb_noremap
static int last_recorded_len = 0; // number of last recorded chars
#ifdef FEAT_EVAL
mapblock_T *last_used_map = NULL;
#endif
static int read_readbuf(buffheader_T *buf, int advance);
static void init_typebuf(void);
static void may_sync_undo(void);
@@ -2893,6 +2897,7 @@ handle_mapping(
#ifdef FEAT_EVAL
if (save_m_expr)
vim_free(map_str);
last_used_map = mp;
#endif
}
#ifdef FEAT_EVAL
@@ -3708,7 +3713,7 @@ input_available(void)
* Function passed to do_cmdline() to get the command after a <Cmd> key from
* typeahead.
*/
char_u *
static char_u *
getcmdkeycmd(
int promptc UNUSED,
void *cookie UNUSED,
@@ -3774,7 +3779,7 @@ getcmdkeycmd(
c1 = NUL; // end the line
else if (c1 == ESC)
aborted = TRUE;
else if (c1 == K_COMMAND)
else if (c1 == K_COMMAND || c1 == K_SCRIPT_COMMAND)
{
// give a nicer error message for this special case
emsg(_(e_cmd_mapping_must_end_with_cr_before_second_cmd));
@@ -3804,3 +3809,35 @@ getcmdkeycmd(
return (char_u *)line_ga.ga_data;
}
int
do_cmdkey_command(int key, int flags)
{
int res;
#ifdef FEAT_EVAL
sctx_T save_current_sctx = {0, 0, 0, 0};
if (key == K_SCRIPT_COMMAND && last_used_map != NULL)
{
save_current_sctx = current_sctx;
current_sctx = last_used_map->m_script_ctx;
}
#endif
res = do_cmdline(NULL, getcmdkeycmd, NULL, flags);
#ifdef FEAT_EVAL
if (save_current_sctx.sc_sid > 0)
current_sctx = save_current_sctx;
#endif
return res;
}
#if defined(FEAT_EVAL) || defined(PROTO)
void
reset_last_used_map(void)
{
last_used_map = NULL;
}
#endif

View File

@@ -2281,7 +2281,8 @@ ins_compl_prep(int c)
// Ignore end of Select mode mapping and mouse scroll buttons.
if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP
|| c == K_MOUSELEFT || c == K_MOUSERIGHT || c == K_COMMAND)
|| c == K_MOUSELEFT || c == K_MOUSERIGHT
|| c == K_COMMAND || c == K_SCRIPT_COMMAND)
return retval;
#ifdef FEAT_PROP_POPUP

View File

@@ -276,6 +276,7 @@ enum key_extra
, KE_MOUSEMOVE_XY = 101 // KE_MOUSEMOVE with coordinates
, KE_CANCEL = 102 // return from vgetc()
, KE_COMMAND = 103 // <Cmd> special key
, KE_SCRIPT_COMMAND = 104 // <ScriptCmd> special key
};
/*
@@ -480,6 +481,7 @@ enum key_extra
#define K_CURSORHOLD TERMCAP2KEY(KS_EXTRA, KE_CURSORHOLD)
#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND)
#define K_SCRIPT_COMMAND TERMCAP2KEY(KS_EXTRA, KE_SCRIPT_COMMAND)
// Bits for modifier mask
// 0x01 cannot be used, because the modifier must be 0x02 or higher

View File

@@ -1057,6 +1057,7 @@ static struct key_name_entry
{K_CURSORHOLD, (char_u *)"CursorHold"},
{K_IGNORE, (char_u *)"Ignore"},
{K_COMMAND, (char_u *)"Cmd"},
{K_SCRIPT_COMMAND, (char_u *)"ScriptCmd"},
{K_FOCUSGAINED, (char_u *)"FocusGained"},
{K_FOCUSLOST, (char_u *)"FocusLost"},
{0, NULL}

View File

@@ -373,6 +373,7 @@ static const struct nv_cmd
{K_CURSORHOLD, nv_cursorhold, NV_KEEPREG, 0},
{K_PS, nv_edit, 0, 0},
{K_COMMAND, nv_colon, 0, 0},
{K_SCRIPT_COMMAND, nv_colon, 0, 0},
};
// Number of commands in nv_cmds[].
@@ -3429,7 +3430,9 @@ nv_colon(cmdarg_T *cap)
{
int old_p_im;
int cmd_result;
int is_cmdkey = cap->cmdchar == K_COMMAND;
int is_cmdkey = cap->cmdchar == K_COMMAND
|| cap->cmdchar == K_SCRIPT_COMMAND;
int flags;
if (VIsual_active && !is_cmdkey)
nv_operator(cap);
@@ -3459,8 +3462,11 @@ nv_colon(cmdarg_T *cap)
old_p_im = p_im;
// get a command line and execute it
cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL,
cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0);
flags = cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0;
if (is_cmdkey)
cmd_result = do_cmdkey_command(cap->cmdchar, flags);
else
cmd_result = do_cmdline(NULL, getexline, NULL, flags);
// If 'insertmode' changed, enter or exit Insert mode
if (p_im != old_p_im)

View File

@@ -3501,6 +3501,14 @@ typedef struct {
int rv_arg; // extra argument
} redo_VIsual_T;
static int
is_ex_cmdchar(cmdarg_T *cap)
{
return cap->cmdchar == ':'
|| cap->cmdchar == K_COMMAND
|| cap->cmdchar == K_SCRIPT_COMMAND;
}
/*
* Handle an operator after Visual mode or when the movement is finished.
* "gui_yank" is true when yanking text for the clipboard.
@@ -3583,8 +3591,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
&& ((!VIsual_active || oap->motion_force)
// Also redo Operator-pending Visual mode mappings
|| (VIsual_active
&& (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
&& oap->op_type != OP_COLON))
&& is_ex_cmdchar(cap) && oap->op_type != OP_COLON))
&& cap->cmdchar != 'D'
#ifdef FEAT_FOLDING
&& oap->op_type != OP_FOLD
@@ -3608,7 +3615,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
AppendToRedobuffLit(cap->searchbuf, -1);
AppendToRedobuff(NL_STR);
}
else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND)
else if (is_ex_cmdchar(cap))
{
// do_cmdline() has stored the first typed line in
// "repeat_cmdline". When several lines are typed repeating
@@ -3806,7 +3813,7 @@ do_pending_operator(cmdarg_T *cap, int old_col, int gui_yank)
get_op_char(oap->op_type),
get_extra_op_char(oap->op_type),
oap->motion_force, cap->cmdchar, cap->nchar);
else if (cap->cmdchar != ':' && cap->cmdchar != K_COMMAND)
else if (!is_ex_cmdchar(cap))
{
int opchar = get_op_char(oap->op_type);
int extra_opchar = get_extra_op_char(oap->op_type);

View File

@@ -52,5 +52,6 @@ void parse_queued_messages(void);
void vungetc(int c);
int fix_input_buffer(char_u *buf, int len);
int input_available(void);
char_u *getcmdkeycmd(int promptc, void *cookie, int indent, getline_opt_T do_concat);
int do_cmdkey_command(int key, int flags);
void reset_last_used_map(void);
/* vim: set ft=c : */

View File

@@ -2229,7 +2229,8 @@ send_keys_to_term(term_T *term, int c, int modmask, int typed)
break;
case K_COMMAND:
return do_cmdline(NULL, getcmdkeycmd, NULL, 0);
case K_SCRIPT_COMMAND:
return do_cmdkey_command(c, 0);
}
if (typed)
mouse_was_outside = FALSE;

View File

@@ -1337,6 +1337,9 @@ def Test_autoload_mapping()
export def Toggle(): string
return ":g:toggle_called = 'yes'\<CR>"
enddef
export def Doit()
g:doit_called = 'yes'
enddef
END
writefile(lines, 'Xdir/autoload/toggle.vim')
@@ -1346,6 +1349,8 @@ def Test_autoload_mapping()
import autoload 'toggle.vim'
nnoremap <silent> <expr> tt toggle.Toggle()
nnoremap <silent> xx <ScriptCmd>toggle.Doit()<CR>
nnoremap <silent> yy <Cmd>toggle.Doit()<CR>
END
CheckScriptSuccess(lines)
assert_false(exists("g:toggle_loaded"))
@@ -1355,7 +1360,14 @@ def Test_autoload_mapping()
assert_equal('yes', g:toggle_loaded)
assert_equal('yes', g:toggle_called)
feedkeys("xx", 'xt')
assert_equal('yes', g:doit_called)
assert_fails('call feedkeys("yy", "xt")', 'E121: Undefined variable: toggle')
nunmap tt
nunmap xx
nunmap yy
unlet g:toggle_loaded
unlet g:toggle_called
delete('Xdir', 'rf')

View File

@@ -750,6 +750,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
4099,
/**/
4098,
/**/