vim-patch:8.2.4861: it is not easy to restore saved mappings

Problem:    It is not easy to restore saved mappings.
Solution:   Make mapset() accept a dict argument. (Ernie Rael, closes vim/vim#10295)

51d04d16f2

Co-authored-by: Ernie Rael <errael@raelity.com>
This commit is contained in:
zeertzjq 2023-11-09 19:48:50 +08:00
parent d4dbfb092b
commit f748a73a35
9 changed files with 350 additions and 41 deletions

View File

@ -4185,6 +4185,7 @@ maparg({name} [, {mode} [, {abbr} [, {dict}]]]) *maparg()*
"lnum" The line number in "sid", zero if unknown. "lnum" The line number in "sid", zero if unknown.
"nowait" Do not wait for other, longer mappings. "nowait" Do not wait for other, longer mappings.
(|:map-<nowait>|). (|:map-<nowait>|).
"abbr" True if this is an |abbreviation|.
The dictionary can be used to restore a mapping with The dictionary can be used to restore a mapping with
|mapset()|. |mapset()|.
@ -4246,9 +4247,17 @@ mapnew({expr1}, {expr2}) *mapnew()*
don't want that use |deepcopy()| first. don't want that use |deepcopy()| first.
mapset({mode}, {abbr}, {dict}) *mapset()* mapset({mode}, {abbr}, {dict}) *mapset()*
Restore a mapping from a dictionary returned by |maparg()|. Restore a mapping from a dictionary, possibly returned by
{mode} and {abbr} should be the same as for the call to |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
|maparg()|. *E460* is true, is set on the current buffer; it is up to the caller
to ensure that the intended buffer is the current buffer. This
feature allows copying mappings from one buffer to another.
The dict.mode value may restore a single mapping that covers
more than one mode, like with mode values of '!', ' ', "nox",
or 'v'. *E1276*
In the first form, {mode} and {abbr} should be the same as
for the call to |maparg()|. *E460*
{mode} is used to define the mode in which the mapping is set, {mode} is used to define the mode in which the mapping is set,
not the "mode" entry in {dict}. not the "mode" entry in {dict}.
Example for saving and restoring a mapping: >vim Example for saving and restoring a mapping: >vim
@ -4257,8 +4266,21 @@ mapset({mode}, {abbr}, {dict}) *mapset()*
" ... " ...
call mapset('n', 0, save_map) call mapset('n', 0, save_map)
< Note that if you are going to replace a map in several modes, < Note that if you are going to replace a map in several modes,
e.g. with `:map!`, you need to save the mapping for all of e.g. with `:map!`, you need to save/restore the mapping for
them, since they can differ. all of them, when they might differ.
In the second form, with {dict} as the only argument, mode
and abbr are taken from the dict.
Example: >vim
let save_maps = maplist()->filter(
\ {_, m -> m.lhs == 'K'})
nnoremap K somethingelse
cnoremap K somethingelse2
" ...
unmap K
for d in save_maps
call mapset(d)
endfor
match({expr}, {pat} [, {start} [, {count}]]) *match()* match({expr}, {pat} [, {start} [, {count}]]) *match()*
When {expr} is a |List| then this returns the index of the When {expr} is a |List| then this returns the index of the

View File

@ -955,7 +955,7 @@ operator to add quotes around text in the current line: >
\ ->setline(".")}'<CR>g@ \ ->setline(".")}'<CR>g@
============================================================================== ==============================================================================
2. Abbreviations *abbreviations* *Abbreviations* 2. Abbreviations *abbreviation* *abbreviations* *Abbreviations*
Abbreviations are used in Insert mode, Replace mode and Command-line mode. Abbreviations are used in Insert mode, Replace mode and Command-line mode.
If you enter a word that is an abbreviation, it is replaced with the word it If you enter a word that is an abbreviation, it is replaced with the word it

View File

@ -5046,6 +5046,7 @@ function vim.fn.map(expr1, expr2) end
--- "lnum" The line number in "sid", zero if unknown. --- "lnum" The line number in "sid", zero if unknown.
--- "nowait" Do not wait for other, longer mappings. --- "nowait" Do not wait for other, longer mappings.
--- (|:map-<nowait>|). --- (|:map-<nowait>|).
--- "abbr" True if this is an |abbreviation|.
--- ---
--- The dictionary can be used to restore a mapping with --- The dictionary can be used to restore a mapping with
--- |mapset()|. --- |mapset()|.
@ -5124,9 +5125,17 @@ function vim.fn.maplist() end
--- @return any --- @return any
function vim.fn.mapnew(expr1, expr2) end function vim.fn.mapnew(expr1, expr2) end
--- Restore a mapping from a dictionary returned by |maparg()|. --- Restore a mapping from a dictionary, possibly returned by
--- {mode} and {abbr} should be the same as for the call to --- |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
--- |maparg()|. *E460* --- is true, is set on the current buffer; it is up to the caller
--- to ensure that the intended buffer is the current buffer. This
--- feature allows copying mappings from one buffer to another.
--- The dict.mode value may restore a single mapping that covers
--- more than one mode, like with mode values of '!', ' ', "nox",
--- or 'v'. *E1276*
---
--- In the first form, {mode} and {abbr} should be the same as
--- for the call to |maparg()|. *E460*
--- {mode} is used to define the mode in which the mapping is set, --- {mode} is used to define the mode in which the mapping is set,
--- not the "mode" entry in {dict}. --- not the "mode" entry in {dict}.
--- Example for saving and restoring a mapping: >vim --- Example for saving and restoring a mapping: >vim
@ -5135,12 +5144,25 @@ function vim.fn.mapnew(expr1, expr2) end
--- " ... --- " ...
--- call mapset('n', 0, save_map) --- call mapset('n', 0, save_map)
--- <Note that if you are going to replace a map in several modes, --- <Note that if you are going to replace a map in several modes,
--- e.g. with `:map!`, you need to save the mapping for all of --- e.g. with `:map!`, you need to save/restore the mapping for
--- them, since they can differ. --- all of them, when they might differ.
---
--- In the second form, with {dict} as the only argument, mode
--- and abbr are taken from the dict.
--- Example: >vim
--- let save_maps = maplist()->filter(
--- \ {_, m -> m.lhs == 'K'})
--- nnoremap K somethingelse
--- cnoremap K somethingelse2
--- " ...
--- unmap K
--- for d in save_maps
--- call mapset(d)
--- endfor
--- ---
--- @param mode string --- @param mode string
--- @param abbr any --- @param abbr? any
--- @param dict any --- @param dict? any
--- @return any --- @return any
function vim.fn.mapset(mode, abbr, dict) end function vim.fn.mapset(mode, abbr, dict) end

View File

@ -1116,8 +1116,7 @@ func s:DeleteCommands()
if exists('s:k_map_saved') if exists('s:k_map_saved')
if !empty(s:k_map_saved) && !s:k_map_saved.buffer if !empty(s:k_map_saved) && !s:k_map_saved.buffer
nunmap K nunmap K
" call mapset(s:k_map_saved) call mapset(s:k_map_saved)
call mapset('n', 0, s:k_map_saved)
elseif empty(s:k_map_saved) elseif empty(s:k_map_saved)
nunmap K nunmap K
endif endif
@ -1126,8 +1125,7 @@ func s:DeleteCommands()
if exists('s:plus_map_saved') if exists('s:plus_map_saved')
if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer if !empty(s:plus_map_saved) && !s:plus_map_saved.buffer
nunmap + nunmap +
" call mapset(s:plus_map_saved) call mapset(s:plus_map_saved)
call mapset('n', 0, s:plus_map_saved)
elseif empty(s:plus_map_saved) elseif empty(s:plus_map_saved)
nunmap + nunmap +
endif endif
@ -1136,8 +1134,7 @@ func s:DeleteCommands()
if exists('s:minus_map_saved') if exists('s:minus_map_saved')
if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer if !empty(s:minus_map_saved) && !s:minus_map_saved.buffer
nunmap - nunmap -
" call mapset(s:minus_map_saved) call mapset(s:minus_map_saved)
call mapset('n', 0, s:minus_map_saved)
elseif empty(s:minus_map_saved) elseif empty(s:minus_map_saved)
nunmap - nunmap -
endif endif

View File

@ -6194,6 +6194,7 @@ M.funcs = {
"lnum" The line number in "sid", zero if unknown. "lnum" The line number in "sid", zero if unknown.
"nowait" Do not wait for other, longer mappings. "nowait" Do not wait for other, longer mappings.
(|:map-<nowait>|). (|:map-<nowait>|).
"abbr" True if this is an |abbreviation|.
The dictionary can be used to restore a mapping with The dictionary can be used to restore a mapping with
|mapset()|. |mapset()|.
@ -6287,12 +6288,20 @@ M.funcs = {
signature = 'mapnew({expr1}, {expr2})', signature = 'mapnew({expr1}, {expr2})',
}, },
mapset = { mapset = {
args = 3, args = { 1, 3 },
base = 1, base = 1,
desc = [=[ desc = [=[
Restore a mapping from a dictionary returned by |maparg()|. Restore a mapping from a dictionary, possibly returned by
{mode} and {abbr} should be the same as for the call to |maparg()| or |maplist()|. A buffer mapping, when dict.buffer
|maparg()|. *E460* is true, is set on the current buffer; it is up to the caller
to ensure that the intended buffer is the current buffer. This
feature allows copying mappings from one buffer to another.
The dict.mode value may restore a single mapping that covers
more than one mode, like with mode values of '!', ' ', "nox",
or 'v'. *E1276*
In the first form, {mode} and {abbr} should be the same as
for the call to |maparg()|. *E460*
{mode} is used to define the mode in which the mapping is set, {mode} is used to define the mode in which the mapping is set,
not the "mode" entry in {dict}. not the "mode" entry in {dict}.
Example for saving and restoring a mapping: >vim Example for saving and restoring a mapping: >vim
@ -6301,8 +6310,21 @@ M.funcs = {
" ... " ...
call mapset('n', 0, save_map) call mapset('n', 0, save_map)
<Note that if you are going to replace a map in several modes, <Note that if you are going to replace a map in several modes,
e.g. with `:map!`, you need to save the mapping for all of e.g. with `:map!`, you need to save/restore the mapping for
them, since they can differ. all of them, when they might differ.
In the second form, with {dict} as the only argument, mode
and abbr are taken from the dict.
Example: >vim
let save_maps = maplist()->filter(
\ {_, m -> m.lhs == 'K'})
nnoremap K somethingelse
cnoremap K somethingelse2
" ...
unmap K
for d in save_maps
call mapset(d)
endfor
]=], ]=],
name = 'mapset', name = 'mapset',
params = { { 'mode', 'string' }, { 'abbr', 'any' }, { 'dict', 'any' } }, params = { { 'mode', 'string' }, { 'abbr', 'any' }, { 'dict', 'any' } },

View File

@ -123,6 +123,8 @@ static const char e_mapping_already_exists_for_str[]
= N_("E227: Mapping already exists for %s"); = N_("E227: Mapping already exists for %s");
static const char e_entries_missing_in_mapset_dict_argument[] static const char e_entries_missing_in_mapset_dict_argument[]
= N_("E460: Entries missing in mapset() dict argument"); = N_("E460: Entries missing in mapset() dict argument");
static const char e_illegal_map_mode_string_str[]
= N_("E1276: Illegal map mode string: '%s'");
/// Get the start of the hashed map list for "state" and first character "c". /// Get the start of the hashed map list for "state" and first character "c".
mapblock_T *get_maphash_list(int state, int c) mapblock_T *get_maphash_list(int state, int c)
@ -2070,11 +2072,12 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// ///
/// @param mp The maphash that contains the mapping information /// @param mp The maphash that contains the mapping information
/// @param buffer_value The "buffer" value /// @param buffer_value The "buffer" value
/// @param abbr True if abbreviation
/// @param compatible True for compatible with old maparg() dict /// @param compatible True for compatible with old maparg() dict
/// ///
/// @return A Dictionary. /// @return A Dictionary.
static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt, static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt,
const int buffer_value, const bool compatible) const int buffer_value, const bool abbr, const bool compatible)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(1)
{ {
Dictionary dict = ARRAY_DICT_INIT; Dictionary dict = ARRAY_DICT_INIT;
@ -2121,6 +2124,7 @@ static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhs
PUT(dict, "replace_keycodes", INTEGER_OBJ(1)); PUT(dict, "replace_keycodes", INTEGER_OBJ(1));
} }
PUT(dict, "mode", CSTR_AS_OBJ(mapmode)); PUT(dict, "mode", CSTR_AS_OBJ(mapmode));
PUT(dict, "abbr", INTEGER_OBJ(abbr ? 1 : 0));
return dict; return dict;
} }
@ -2193,7 +2197,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) { if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
Dictionary dict = mapblock_fill_dict(mp, Dictionary dict = mapblock_fill_dict(mp,
did_simplify ? keys_simplified : NULL, did_simplify ? keys_simplified : NULL,
buffer_local, true); buffer_local, abbr, true);
(void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL); (void)object_to_vim(DICTIONARY_OBJ(dict), rettv, NULL);
api_free_dictionary(dict); api_free_dictionary(dict);
} else { } else {
@ -2206,21 +2210,99 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
xfree(alt_keys_buf); xfree(alt_keys_buf);
} }
/// Get the mapping mode from the mode string.
/// It may contain multiple characters, eg "nox", or "!", or ' '
/// Return 0 if there is an error.
static int get_map_mode_string(const char *const mode_string, const bool abbr)
{
const char *p = mode_string;
const int MASK_V = MODE_VISUAL | MODE_SELECT;
const int MASK_MAP = MODE_VISUAL | MODE_SELECT | MODE_NORMAL | MODE_OP_PENDING;
const int MASK_BANG = MODE_INSERT | MODE_CMDLINE;
if (*p == NUL) {
p = " "; // compatibility
}
int mode = 0;
int modec;
while ((modec = (uint8_t)(*p++))) {
int tmode;
switch (modec) {
case 'i':
tmode = MODE_INSERT; break;
case 'l':
tmode = MODE_LANGMAP; break;
case 'c':
tmode = MODE_CMDLINE; break;
case 'n':
tmode = MODE_NORMAL; break;
case 'x':
tmode = MODE_VISUAL; break;
case 's':
tmode = MODE_SELECT; break;
case 'o':
tmode = MODE_OP_PENDING; break;
case 't':
tmode = MODE_TERMINAL; break;
case 'v':
tmode = MASK_V; break;
case '!':
tmode = MASK_BANG; break;
case ' ':
tmode = MASK_MAP; break;
default:
return 0; // error, unknown mode character
}
mode |= tmode;
}
if ((abbr && (mode & ~MASK_BANG) != 0)
|| (!abbr && (mode & (mode - 1)) != 0 // more than one bit set
&& (
// false if multiple bits set in mode and mode is fully
// contained in one mask
!(((mode & MASK_BANG) != 0 && (mode & ~MASK_BANG) == 0)
|| ((mode & MASK_MAP) != 0 && (mode & ~MASK_MAP) == 0))))) {
return 0;
}
return mode;
}
/// "mapset()" function /// "mapset()" function
void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr) void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{ {
const char *which;
char buf[NUMBUFLEN]; char buf[NUMBUFLEN];
const char *which = tv_get_string_buf_chk(&argvars[0], buf); int is_abbr;
if (which == NULL) { dict_T *d;
return;
}
const int mode = get_map_mode((char **)&which, 0);
const bool is_abbr = tv_get_number(&argvars[1]) != 0;
if (tv_check_for_dict_arg(argvars, 2) == FAIL) { // If first arg is a dict, then that's the only arg permitted.
const bool dict_only = argvars[0].v_type == VAR_DICT;
if (dict_only) {
d = argvars[0].vval.v_dict;
which = tv_dict_get_string(d, "mode", false);
is_abbr = (int)tv_dict_get_bool(d, "abbr", -1);
if (which == NULL || is_abbr < 0) {
emsg(_(e_entries_missing_in_mapset_dict_argument));
return;
}
} else {
which = tv_get_string_buf_chk(&argvars[0], buf);
if (which == NULL) {
return;
}
is_abbr = (int)tv_get_bool(&argvars[1]);
if (tv_check_for_dict_arg(argvars, 2) == FAIL) {
return;
}
d = argvars[2].vval.v_dict;
}
const int mode = get_map_mode_string(which, is_abbr);
if (mode == 0) {
semsg(_(e_illegal_map_mode_string_str), which);
return; return;
} }
dict_T *d = argvars[2].vval.v_dict;
// Get the values in the same order as above in get_maparg(). // Get the values in the same order as above in get_maparg().
char *lhs = tv_dict_get_string(d, "lhs", false); char *lhs = tv_dict_get_string(d, "lhs", false);
@ -2322,7 +2404,7 @@ void f_maplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
Dictionary dict = mapblock_fill_dict(mp, Dictionary dict = mapblock_fill_dict(mp,
did_simplify ? keys_buf : NULL, did_simplify ? keys_buf : NULL,
buffer_local, true); buffer_local, abbr, true);
typval_T d = TV_INITIAL_VALUE; typval_T d = TV_INITIAL_VALUE;
(void)object_to_vim(DICTIONARY_OBJ(dict), &d, NULL); (void)object_to_vim(DICTIONARY_OBJ(dict), &d, NULL);
assert(d.v_type == VAR_DICT); assert(d.v_type == VAR_DICT);
@ -2751,7 +2833,8 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf)
// Check for correct mode // Check for correct mode
if (int_mode & current_maphash->m_mode) { if (int_mode & current_maphash->m_mode) {
ADD(mappings, ADD(mappings,
DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value, false))); DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL,
buffer_value, false, false)));
} }
} }
} }

View File

@ -36,6 +36,7 @@ describe('nvim_get_keymap', function()
buffer=0, buffer=0,
nowait=0, nowait=0,
mode='n', mode='n',
abbr=0,
noremap=1, noremap=1,
lnum=0, lnum=0,
} }
@ -262,6 +263,7 @@ describe('nvim_get_keymap', function()
scriptversion=1, scriptversion=1,
buffer=0, buffer=0,
nowait=0, nowait=0,
abbr=0,
noremap=1, noremap=1,
lnum=0, lnum=0,
} }
@ -325,6 +327,7 @@ describe('nvim_get_keymap', function()
lhsraw='| |', lhsraw='| |',
rhs='| |', rhs='| |',
mode='n', mode='n',
abbr=0,
script=0, script=0,
silent=0, silent=0,
expr=0, expr=0,
@ -372,6 +375,7 @@ describe('nvim_get_keymap', function()
buffer=0, buffer=0,
nowait=0, nowait=0,
mode='n', mode='n',
abbr=0,
noremap=0, noremap=0,
lnum=0, lnum=0,
}, mapargs[1]) }, mapargs[1])
@ -391,6 +395,7 @@ describe('nvim_get_keymap', function()
buffer=0, buffer=0,
nowait=0, nowait=0,
mode='n', mode='n',
abbr=0,
noremap=0, noremap=0,
lnum=0, lnum=0,
desc='map description' desc='map description'
@ -426,6 +431,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
local to_return = {} local to_return = {}
to_return.mode = normalize_mapmode(mode, true) to_return.mode = normalize_mapmode(mode, true)
to_return.abbr = mode:sub(-1) == 'a' and 1 or 0
to_return.noremap = not opts.noremap and 0 or 1 to_return.noremap = not opts.noremap and 0 or 1
to_return.lhs = lhs to_return.lhs = lhs
to_return.rhs = rhs to_return.rhs = rhs

View File

@ -30,6 +30,7 @@ describe('maparg()', function()
buffer=0, buffer=0,
nowait=0, nowait=0,
mode='n', mode='n',
abbr=0,
noremap=1, noremap=1,
lnum=0, lnum=0,
} }
@ -156,6 +157,7 @@ describe('maparg()', function()
buffer = 0, buffer = 0,
expr = 0, expr = 0,
mode = 'n', mode = 'n',
abbr = 0,
noremap = 1, noremap = 1,
nowait = 0, nowait = 0,
script = 0, script = 0,

View File

@ -21,13 +21,13 @@ func Test_maparg()
\ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16", \ 'lhsraw': "foo\x80\xfc\x04V", 'lhsrawalt': "foo\x16",
\ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1, \ 'mode': ' ', 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
\ 'lnum': lnum + 1, \ 'lnum': lnum + 1,
\ 'rhs': 'is<F4>foo', 'buffer': 0}, \ 'rhs': 'is<F4>foo', 'buffer': 0, 'abbr': 0},
\ maparg('foo<C-V>', '', 0, 1)) \ maparg('foo<C-V>', '', 0, 1))
call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar', call assert_equal({'silent': 1, 'noremap': 1, 'script': 1, 'lhs': 'bar',
\ 'lhsraw': 'bar', 'mode': 'v', \ 'lhsraw': 'bar', 'mode': 'v',
\ 'nowait': 0, 'expr': 1, 'sid': sid, 'scriptversion': 1, \ 'nowait': 0, 'expr': 1, 'sid': sid, 'scriptversion': 1,
\ 'lnum': lnum + 2, \ 'lnum': lnum + 2,
\ 'rhs': 'isbar', 'buffer': 1}, \ 'rhs': 'isbar', 'buffer': 1, 'abbr': 0},
\ 'bar'->maparg('', 0, 1)) \ 'bar'->maparg('', 0, 1))
let lnum = expand('<sflnum>') let lnum = expand('<sflnum>')
map <buffer> <nowait> foo bar map <buffer> <nowait> foo bar
@ -35,7 +35,7 @@ func Test_maparg()
\ 'lhsraw': 'foo', 'mode': ' ', \ 'lhsraw': 'foo', 'mode': ' ',
\ 'nowait': 1, 'expr': 0, 'sid': sid, 'scriptversion': 1, \ 'nowait': 1, 'expr': 0, 'sid': sid, 'scriptversion': 1,
\ 'lnum': lnum + 1, 'rhs': 'bar', \ 'lnum': lnum + 1, 'rhs': 'bar',
\ 'buffer': 1}, \ 'buffer': 1, 'abbr': 0},
\ maparg('foo', '', 0, 1)) \ maparg('foo', '', 0, 1))
let lnum = expand('<sflnum>') let lnum = expand('<sflnum>')
tmap baz foo tmap baz foo
@ -43,8 +43,17 @@ func Test_maparg()
\ 'lhsraw': 'baz', 'mode': 't', \ 'lhsraw': 'baz', 'mode': 't',
\ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1, \ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
\ 'lnum': lnum + 1, 'rhs': 'foo', \ 'lnum': lnum + 1, 'rhs': 'foo',
\ 'buffer': 0}, \ 'buffer': 0, 'abbr': 0},
\ maparg('baz', 't', 0, 1)) \ maparg('baz', 't', 0, 1))
let lnum = expand('<sflnum>')
iab A B
call assert_equal({'silent': 0, 'noremap': 0, 'script': 0, 'lhs': 'A',
\ 'lhsraw': 'A', 'mode': 'i',
\ 'nowait': 0, 'expr': 0, 'sid': sid, 'scriptversion': 1,
\ 'lnum': lnum + 1, 'rhs': 'B',
\ 'buffer': 0, 'abbr': 1},
\ maparg('A', 'i', 1, 1))
iuna A
map abc x<char-114>x map abc x<char-114>x
call assert_equal("xrx", maparg('abc')) call assert_equal("xrx", maparg('abc'))
@ -278,6 +287,152 @@ func Test_mapset()
call assert_fails('call mapset("i", 0, {})', 'E460:') call assert_fails('call mapset("i", 0, {})', 'E460:')
endfunc endfunc
func Test_mapset_arg1_dir()
" This test is mostly about get_map_mode_string.
" Once the code gets past that, it's common with the 3 arg mapset.
" GetModes() return list of modes for 'XZ' lhs using maplist.
" There is one list item per mapping
func s:GetModes(abbr = v:false)
return maplist(a:abbr)->filter({_, m -> m.lhs == 'XZ'})
\ ->mapnew({_, m -> m.mode})
endfunc
func s:UnmapAll(lhs)
const unmap_cmds = [ 'unmap', 'unmap!', 'tunmap', 'lunmap' ]
for cmd in unmap_cmds
try | call execute(cmd .. ' ' .. a:lhs) | catch /E31/ | endtry
endfor
endfunc
let tmap = {}
" some mapset(mode, abbr, dict) tests using get_map_mode_str
map XZ x
let tmap = maplist()->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
" this splits the mapping into 2 mappings
call mapset('ox', v:false, tmap)
call assert_equal(2, len(s:GetModes()))
call mapset('o', v:false, tmap)
call assert_equal(3, len(s:GetModes()))
" test that '' acts like ' ', and that the 3 mappings become 1
call mapset('', v:false, tmap)
call assert_equal([' '], s:GetModes())
" dict's mode/abbr are ignored
call s:UnmapAll('XZ')
let tmap.mode = '!'
let tmap.abbr = v:true
call mapset('o', v:false, tmap)
call assert_equal(['o'], s:GetModes())
" test the 3 arg version handles bad mode string, dict not used
call assert_fails("call mapset('vi', v:false, {})", 'E1276:')
" get the abbreviations out of the way
abbreviate XZ ZX
let tmap = maplist(v:true)->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
abclear
" 'ic' is the default ab command, shows up as '!'
let tmap.mode = 'ic'
call mapset(tmap)
call assert_equal(['!'], s:GetModes(v:true))
abclear
let tmap.mode = 'i'
call mapset(tmap)
call assert_equal(['i'], s:GetModes(v:true))
abclear
let tmap.mode = 'c'
call mapset(tmap)
call assert_equal(['c'], s:GetModes(v:true))
abclear
let tmap.mode = '!'
call mapset(tmap)
call assert_equal(['!'], s:GetModes(v:true))
call assert_fails("call mapset(#{mode: ' !', abbr: 1})", 'E1276:')
call assert_fails("call mapset(#{mode: 'cl', abbr: 1})", 'E1276:')
call assert_fails("call mapset(#{mode: 'in', abbr: 1})", 'E1276:')
" the map commands
map XZ x
let tmap = maplist()->filter({_, m -> m.lhs == 'XZ'})[0]->copy()
" try the combos
call s:UnmapAll('XZ')
" 'nxso' is ' ', the unadorned :map
let tmap.mode = 'nxso'
call mapset(tmap)
call assert_equal([' '], s:GetModes())
cal s:UnmapAll('XZ')
" 'ic' is '!'
let tmap.mode = 'ic'
call mapset(tmap)
call assert_equal(['!'], s:GetModes())
call s:UnmapAll('XZ')
" 'xs' is really 'v'
let tmap.mode = 'xs'
call mapset(tmap)
call assert_equal(['v'], s:GetModes())
" try the individual modes
call s:UnmapAll('XZ')
let tmap.mode = 'n'
call mapset(tmap)
call assert_equal(['n'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 'x'
call mapset(tmap)
call assert_equal(['x'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 's'
call mapset(tmap)
call assert_equal(['s'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 'o'
call mapset(tmap)
call assert_equal(['o'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 'i'
call mapset(tmap)
call assert_equal(['i'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 'c'
call mapset(tmap)
call assert_equal(['c'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 't'
call mapset(tmap)
call assert_equal(['t'], s:GetModes())
call s:UnmapAll('XZ')
let tmap.mode = 'l'
call mapset(tmap)
call assert_equal(['l'], s:GetModes())
call s:UnmapAll('XZ')
" get errors for modes that can't be in one mapping
call assert_fails("call mapset(#{mode: 'nxsoi', abbr: 0})", 'E1276:')
call assert_fails("call mapset(#{mode: ' !', abbr: 0})", 'E1276:')
call assert_fails("call mapset(#{mode: 'ix', abbr: 0})", 'E1276:')
call assert_fails("call mapset(#{mode: 'tl', abbr: 0})", 'E1276:')
call assert_fails("call mapset(#{mode: ' l', abbr: 0})", 'E1276:')
call assert_fails("call mapset(#{mode: ' t', abbr: 0})", 'E1276:')
endfunc
func Check_ctrlb_map(d, check_alt) func Check_ctrlb_map(d, check_alt)
call assert_equal('<C-B>', a:d.lhs) call assert_equal('<C-B>', a:d.lhs)
if a:check_alt if a:check_alt