vim-patch:8.1.2145: cannot map <C-H> when modifyOtherKeys is enabled

Problem:    Cannot map <C-H> when modifyOtherKeys is enabled.
Solution:   Add the <C-H> mapping twice, both with modifier and as 0x08.  Use
            only the first one when modifyOtherKeys has been detected.
459fd785e4

Add REPTERM_NO_SPECIAL instead of REPTERM_SPECIAL because the meaning of
"special" is different between Vim and Nvim.
Omit seenModifyOtherKeys as Nvim supports attaching multiple UIs.
Omit tests as they send terminal codes.
Keep the behavior of API functions.
This commit is contained in:
zeertzjq 2022-03-31 15:47:53 +08:00
parent 188537efb3
commit dde4f09f51
16 changed files with 410 additions and 345 deletions

View File

@ -632,7 +632,7 @@ void modify_keymap(uint64_t channel_id, Buffer buffer, bool is_unmap, String mod
} else {
parsed_args.desc = NULL;
}
if (parsed_args.lhs_len > MAXMAPLEN) {
if (parsed_args.lhs_len > MAXMAPLEN || parsed_args.alt_lhs_len > MAXMAPLEN) {
api_set_error(err, kErrorTypeValidation, "LHS exceeds maximum map length: %s", lhs.data);
goto fail_and_free;
}
@ -1128,6 +1128,9 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, bool from_lua)
for (const mapblock_T *current_maphash = get_maphash(i, buf);
current_maphash;
current_maphash = current_maphash->m_next) {
if (current_maphash->m_simplified) {
continue;
}
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
mapblock_fill_dict(dict, current_maphash, buffer_value, false);

View File

@ -403,9 +403,19 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, Bool
return (String) { .data = NULL, .size = 0 };
}
int flags = 0;
if (from_part) {
flags |= REPTERM_FROM_PART;
}
if (do_lt) {
flags |= REPTERM_DO_LT;
}
if (!special) {
flags |= REPTERM_NO_SPECIAL;
}
char *ptr = NULL;
replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr,
from_part, do_lt, special, CPO_TO_CPO_FLAGS);
replace_termcodes((char_u *)str.data, str.size, (char_u **)&ptr, flags, NULL, CPO_TO_CPO_FLAGS);
return cstr_as_string(ptr);
}

View File

@ -356,6 +356,8 @@ struct mapblock {
LuaRef m_luaref; // lua function reference as rhs
int m_keylen; // strlen(m_keys)
int m_mode; // valid mode
int m_simplified; // m_keys was simplified, do no use this map
// if keys are typed
int m_noremap; // if non-zero no re-mapping for m_str
char m_silent; // <silent> used, don't echo commands
char m_nowait; // <nowait> used

View File

@ -4965,7 +4965,7 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
// Special key, e.g.: "\<C-W>"
case '<':
extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true);
extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true, true, NULL);
if (extra != 0) {
name += extra;
if (name >= rettv->vval.v_string + len) {

View File

@ -5684,7 +5684,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
mode = get_map_mode((char_u **)&which, 0);
keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, true, true, true,
keys = replace_termcodes(keys, STRLEN(keys), &keys_buf, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
CPO_TO_CPO_FLAGS);
rhs = check_map(keys, mode, exact, false, abbr, &mp, &buffer_local, &rhs_lua);
xfree(keys_buf);

View File

@ -5180,8 +5180,7 @@ int uc_add_command(char_u *name, size_t name_len, char_u *rep, uint32_t argt, lo
char_u *rep_buf = NULL;
garray_T *gap;
replace_termcodes(rep, STRLEN(rep), &rep_buf, false, false, true,
CPO_TO_CPO_FLAGS);
replace_termcodes(rep, STRLEN(rep), &rep_buf, 0, NULL, CPO_TO_CPO_FLAGS);
if (rep_buf == NULL) {
// Can't replace termcodes - try using the string as is
rep_buf = vim_strsave(rep);

View File

@ -2711,11 +2711,12 @@ int fix_input_buffer(char_u *buf, int len)
/// @param[in] orig_rhs_len `strlen` of orig_rhs.
/// @param[in] cpo_flags See param docs for @ref replace_termcodes.
/// @param[out] mapargs MapArguments struct holding the replaced strings.
void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const char_u *orig_rhs,
const size_t orig_rhs_len, LuaRef rhs_lua, int cpo_flags,
MapArguments *mapargs)
void set_maparg_lhs_rhs(const char_u *const orig_lhs, const size_t orig_lhs_len,
const char_u *const orig_rhs, const size_t orig_rhs_len,
const LuaRef rhs_lua, const int cpo_flags, MapArguments *const mapargs)
{
char_u *lhs_buf = NULL;
char_u *alt_lhs_buf = NULL;
char_u *rhs_buf = NULL;
// If mapping has been given as ^V<C_UP> say, then replace the term codes
@ -2725,10 +2726,22 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const
// replace_termcodes() may move the result to allocated memory, which
// needs to be freed later (*lhs_buf and *rhs_buf).
// replace_termcodes() also removes CTRL-Vs and sometimes backslashes.
char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf,
true, true, true, cpo_flags);
// If something like <C-H> is simplified to 0x08 then mark it as simplified.
bool did_simplify = false;
const int flags = REPTERM_FROM_PART | REPTERM_DO_LT;
char_u *replaced = replace_termcodes(orig_lhs, orig_lhs_len, &lhs_buf, flags, &did_simplify,
cpo_flags);
mapargs->lhs_len = STRLEN(replaced);
STRLCPY(mapargs->lhs, replaced, sizeof(mapargs->lhs));
if (did_simplify) {
replaced = replace_termcodes(orig_lhs, orig_lhs_len, &alt_lhs_buf, flags | REPTERM_NO_SIMPLIFY,
NULL, cpo_flags);
mapargs->alt_lhs_len = STRLEN(replaced);
STRLCPY(mapargs->alt_lhs, replaced, sizeof(mapargs->alt_lhs));
} else {
mapargs->alt_lhs_len = 0;
}
mapargs->rhs_lua = rhs_lua;
if (rhs_lua == LUA_NOREF) {
@ -2741,8 +2754,8 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const
mapargs->rhs_len = 0;
mapargs->rhs_is_noop = true;
} else {
replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf,
false, true, true, cpo_flags);
replaced = replace_termcodes(orig_rhs, orig_rhs_len, &rhs_buf, REPTERM_DO_LT, NULL,
cpo_flags);
mapargs->rhs_len = STRLEN(replaced);
mapargs->rhs_is_noop = false;
mapargs->rhs = xcalloc(mapargs->rhs_len + 1, sizeof(char_u));
@ -2760,6 +2773,7 @@ void set_maparg_lhs_rhs(const char_u *orig_lhs, const size_t orig_lhs_len, const
}
xfree(lhs_buf);
xfree(alt_lhs_buf);
xfree(rhs_buf);
}
@ -2894,15 +2908,9 @@ int str_to_mapargs(const char_u *strargs, bool is_unmap, MapArguments *mapargs)
int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T *buf)
{
mapblock_T *mp, **mpp;
char_u *p;
const char_u *p;
int n;
int len = 0; // init for GCC
int did_it = false;
int did_local = false;
int round;
int retval = 0;
int hash;
int new_hash;
mapblock_T **abbr_table;
mapblock_T **map_table;
int noremap;
@ -2929,8 +2937,9 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
validate_maphash();
bool has_lhs = (args->lhs[0] != NUL);
bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
const bool has_lhs = (args->lhs[0] != NUL);
const bool has_rhs = args->rhs_lua != LUA_NOREF || (args->rhs[0] != NUL) || args->rhs_is_noop;
const bool do_print = !has_lhs || (maptype != 1 && !has_rhs);
// check for :unmap without argument
if (maptype == 1 && !has_lhs) {
@ -2938,300 +2947,325 @@ int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev, buf_T
goto theend;
}
char_u *lhs = (char_u *)&args->lhs;
char_u *rhs = args->rhs;
char_u *orig_rhs = args->orig_rhs;
const char_u *lhs = (char_u *)&args->lhs;
const char_u *const rhs = args->rhs;
const char_u *const orig_rhs = args->orig_rhs;
const bool did_simplify = args->alt_lhs_len != 0;
// check arguments and translate function keys
if (has_lhs) {
len = (int)args->lhs_len;
if (len > MAXMAPLEN) {
retval = 1;
goto theend;
// The following is done twice if we have two versions of keys
for (int keyround = 1; keyround <= 2; keyround++) {
bool did_it = false;
bool did_local = false;
int len = (int)args->lhs_len;
if (keyround == 2) {
if (!did_simplify) {
break;
}
lhs = (char_u *)&args->alt_lhs;
len = (int)args->alt_lhs_len;
} else if (did_simplify && do_print) {
// when printing always use the not-simplified map
lhs = (char_u *)&args->alt_lhs;
len = (int)args->alt_lhs_len;
}
if (is_abbrev && maptype != 1) {
//
// If an abbreviation ends in a keyword character, the
// rest must be all keyword-char or all non-keyword-char.
// Otherwise we won't be able to find the start of it in a
// vi-compatible way.
//
int same = -1;
const int first = vim_iswordp(lhs);
int last = first;
p = lhs + utfc_ptr2len(lhs);
n = 1;
while (p < lhs + len) {
n++; // nr of (multi-byte) chars
last = vim_iswordp(p); // type of last char
if (same == -1 && last != first) {
same = n - 1; // count of same char type
}
p += utfc_ptr2len(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
// check arguments and translate function keys
if (has_lhs) {
if (len > MAXMAPLEN) {
retval = 1;
goto theend;
}
// An abbreviation cannot contain white space.
for (n = 0; n < len; n++) {
if (ascii_iswhite(lhs[n])) {
if (is_abbrev && maptype != 1) {
//
// If an abbreviation ends in a keyword character, the
// rest must be all keyword-char or all non-keyword-char.
// Otherwise we won't be able to find the start of it in a
// vi-compatible way.
//
int same = -1;
const int first = vim_iswordp(lhs);
int last = first;
p = lhs + utfc_ptr2len(lhs);
n = 1;
while (p < lhs + len) {
n++; // nr of (multi-byte) chars
last = vim_iswordp(p); // type of last char
if (same == -1 && last != first) {
same = n - 1; // count of same char type
}
p += utfc_ptr2len(p);
}
if (last && n > 2 && same >= 0 && same < n - 1) {
retval = 1;
goto theend;
}
} // for
}
}
if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
no_abbr = false; // reset flag that indicates there are no abbreviations
}
if (!has_lhs || (maptype != 1 && !has_rhs)) {
msg_start();
}
// Check if a new local mapping wasn't already defined globally.
if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
// need to loop over all global hash lists
for (hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
}
mp = first_abbr;
} else {
mp = maphash[hash];
}
for (; mp != NULL && !got_int; mp = mp->m_next) {
// check entries with the same mode
if ((mp->m_mode & mode) != 0
&& mp->m_keylen == len
&& args->unique
&& STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
if (is_abbrev) {
semsg(_("E224: global abbreviation already exists for %s"),
mp->m_keys);
} else {
semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
// An abbreviation cannot contain white space.
for (n = 0; n < len; n++) {
if (ascii_iswhite(lhs[n])) {
retval = 1;
goto theend;
}
} // for
}
}
if (has_lhs && has_rhs && is_abbrev) { // if we will add an abbreviation,
no_abbr = false; // reset flag that indicates there are no abbreviations
}
if (do_print) {
msg_start();
}
// Check if a new local mapping wasn't already defined globally.
if (map_table == buf->b_maphash && has_lhs && has_rhs && maptype != 1) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
}
mp = first_abbr;
} else {
mp = maphash[hash];
}
for (; mp != NULL && !got_int; mp = mp->m_next) {
// check entries with the same mode
if ((mp->m_mode & mode) != 0
&& mp->m_keylen == len
&& args->unique
&& STRNCMP(mp->m_keys, lhs, (size_t)len) == 0) {
if (is_abbrev) {
semsg(_("E224: global abbreviation already exists for %s"),
mp->m_keys);
} else {
semsg(_("E225: global mapping already exists for %s"), mp->m_keys);
}
retval = 5;
goto theend;
}
retval = 5;
goto theend;
}
}
}
}
// When listing global mappings, also list buffer-local ones here.
if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
// need to loop over all global hash lists
for (hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
// When listing global mappings, also list buffer-local ones here.
if (map_table != buf->b_maphash && !has_rhs && maptype != 1) {
// need to loop over all global hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash != 0) { // there is only one abbreviation list
break;
}
mp = buf->b_first_abbr;
} else {
mp = buf->b_maphash[hash];
}
mp = buf->b_first_abbr;
} else {
mp = buf->b_maphash[hash];
}
for (; mp != NULL && !got_int; mp = mp->m_next) {
// check entries with the same mode
if ((mp->m_mode & mode) != 0) {
if (!has_lhs) { // show all entries
showmap(mp, true);
did_local = true;
} else {
n = mp->m_keylen;
if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
for (; mp != NULL && !got_int; mp = mp->m_next) {
// check entries with the same mode
if ((mp->m_mode & mode) != 0) {
if (!has_lhs) { // show all entries
showmap(mp, true);
did_local = true;
} else {
n = mp->m_keylen;
if (STRNCMP(mp->m_keys, lhs, (size_t)(n < len ? n : len)) == 0) {
showmap(mp, true);
did_local = true;
}
}
}
}
}
}
}
// Find an entry in the maphash[] list that matches.
// For :unmap we may loop two times: once to try to unmap an entry with a
// matching 'from' part, a second time, if the first fails, to unmap an
// entry with a matching 'to' part. This was done to allow ":ab foo bar"
// to be unmapped by typing ":unab foo", where "foo" will be replaced by
// "bar" because of the abbreviation.
for (round = 0; (round == 0 || maptype == 1) && round <= 1
&& !did_it && !got_int; round++) {
// need to loop over all hash lists
for (hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash > 0) { // there is only one abbreviation list
break;
}
mpp = abbr_table;
} else {
mpp = &(map_table[hash]);
}
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
if (!(mp->m_mode & mode)) { // skip entries with wrong mode
mpp = &(mp->m_next);
continue;
}
if (!has_lhs) { // show all entries
showmap(mp, map_table != maphash);
did_it = true;
} else { // do we have a match?
if (round) { // second round: Try unmap "rhs" string
n = (int)STRLEN(mp->m_str);
p = mp->m_str;
} else {
n = mp->m_keylen;
p = mp->m_keys;
// Find an entry in the maphash[] list that matches.
// For :unmap we may loop two times: once to try to unmap an entry with a
// matching 'from' part, a second time, if the first fails, to unmap an
// entry with a matching 'to' part. This was done to allow ":ab foo bar"
// to be unmapped by typing ":unab foo", where "foo" will be replaced by
// "bar" because of the abbreviation.
for (int round = 0; (round == 0 || maptype == 1) && round <= 1
&& !did_it && !got_int; round++) {
// need to loop over all hash lists
for (int hash = 0; hash < 256 && !got_int; hash++) {
if (is_abbrev) {
if (hash > 0) { // there is only one abbreviation list
break;
}
if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
if (maptype == 1) { // delete entry
// Only accept a full match. For abbreviations we
// ignore trailing space when matching with the
// "lhs", since an abbreviation can't have
// trailing space.
if (n != len && (!is_abbrev || round || n > len
|| *skipwhite(lhs + n) != NUL)) {
mpp = abbr_table;
} else {
mpp = &(map_table[hash]);
}
for (mp = *mpp; mp != NULL && !got_int; mp = *mpp) {
if (!(mp->m_mode & mode)) { // skip entries with wrong mode
mpp = &(mp->m_next);
continue;
}
if (!has_lhs) { // show all entries
showmap(mp, map_table != maphash);
did_it = true;
} else { // do we have a match?
if (round) { // second round: Try unmap "rhs" string
n = (int)STRLEN(mp->m_str);
p = mp->m_str;
} else {
n = mp->m_keylen;
p = mp->m_keys;
}
if (STRNCMP(p, lhs, (size_t)(n < len ? n : len)) == 0) {
if (maptype == 1) {
// Delete entry.
// Only accept a full match. For abbreviations
// we ignore trailing space when matching with
// the "lhs", since an abbreviation can't have
// trailing space.
if (n != len && (!is_abbrev || round || n > len
|| *skipwhite(lhs + n) != NUL)) {
mpp = &(mp->m_next);
continue;
}
// We reset the indicated mode bits. If nothing
// is left the entry is deleted below.
mp->m_mode &= ~mode;
did_it = true; // remember we did something
} else if (!has_rhs) { // show matching entry
showmap(mp, map_table != maphash);
did_it = true;
} else if (n != len) { // new entry is ambiguous
mpp = &(mp->m_next);
continue;
}
// We reset the indicated mode bits. If nothing is
// left the entry is deleted below.
mp->m_mode &= ~mode;
did_it = true; // remember we did something
} else if (!has_rhs) { // show matching entry
showmap(mp, map_table != maphash);
did_it = true;
} else if (n != len) { // new entry is ambiguous
mpp = &(mp->m_next);
continue;
} else if (args->unique) {
if (is_abbrev) {
semsg(_("E226: abbreviation already exists for %s"), p);
} else {
semsg(_("E227: mapping already exists for %s"), p);
}
retval = 5;
goto theend;
} else { // new rhs for existing entry
mp->m_mode &= ~mode; // remove mode bits
if (mp->m_mode == 0 && !did_it) { // reuse entry
XFREE_CLEAR(mp->m_str);
XFREE_CLEAR(mp->m_orig_str);
XFREE_CLEAR(mp->m_desc);
NLUA_CLEAR_REF(mp->m_luaref);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_luaref = args->rhs_lua;
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
mp->m_mode = mode;
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
nlua_set_sctx(&mp->m_script_ctx);
if (args->desc != NULL) {
mp->m_desc = xstrdup(args->desc);
} else if (args->unique) {
if (is_abbrev) {
semsg(_("E226: abbreviation already exists for %s"), p);
} else {
semsg(_("E227: mapping already exists for %s"), p);
}
retval = 5;
goto theend;
} else {
// new rhs for existing entry
mp->m_mode &= ~mode; // remove mode bits
if (mp->m_mode == 0 && !did_it) { // reuse entry
XFREE_CLEAR(mp->m_str);
XFREE_CLEAR(mp->m_orig_str);
XFREE_CLEAR(mp->m_desc);
NLUA_CLEAR_REF(mp->m_luaref);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_luaref = args->rhs_lua;
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
mp->m_mode = mode;
mp->m_simplified = did_simplify && keyround == 1;
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
nlua_set_sctx(&mp->m_script_ctx);
if (args->desc != NULL) {
mp->m_desc = xstrdup(args->desc);
}
did_it = true;
}
did_it = true;
}
}
if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
continue; // continue with *mpp
}
if (mp->m_mode == 0) { // entry can be deleted
mapblock_free(mpp);
continue; // continue with *mpp
}
// May need to put this entry into another hash list.
new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
if (!is_abbrev && new_hash != hash) {
*mpp = mp->m_next;
mp->m_next = map_table[new_hash];
map_table[new_hash] = mp;
// May need to put this entry into another hash list.
int new_hash = MAP_HASH(mp->m_mode, mp->m_keys[0]);
if (!is_abbrev && new_hash != hash) {
*mpp = mp->m_next;
mp->m_next = map_table[new_hash];
map_table[new_hash] = mp;
continue; // continue with *mpp
continue; // continue with *mpp
}
}
}
mpp = &(mp->m_next);
}
mpp = &(mp->m_next);
}
}
}
if (maptype == 1) { // delete entry
if (!did_it) {
retval = 2; // no match
} else if (*lhs == Ctrl_C) {
// If CTRL-C has been unmapped, reuse it for Interrupting.
if (maptype == 1) {
// delete entry
if (!did_it) {
retval = 2; // no match
} else if (*lhs == Ctrl_C) {
// If CTRL-C has been unmapped, reuse it for Interrupting.
if (map_table == buf->b_maphash) {
buf->b_mapped_ctrl_c &= ~mode;
} else {
mapped_ctrl_c &= ~mode;
}
}
continue;
}
if (!has_lhs || !has_rhs) {
// print entries
if (!did_it && !did_local) {
if (is_abbrev) {
msg(_("No abbreviation found"));
} else {
msg(_("No mapping found"));
}
}
goto theend; // listing finished
}
if (did_it) {
continue; // have added the new entry already
}
// Get here when adding a new entry to the maphash[] list or abbrlist.
mp = xmalloc(sizeof(mapblock_T));
// If CTRL-C has been mapped, don't always use it for Interrupting.
if (*lhs == Ctrl_C) {
if (map_table == buf->b_maphash) {
buf->b_mapped_ctrl_c &= ~mode;
buf->b_mapped_ctrl_c |= mode;
} else {
mapped_ctrl_c &= ~mode;
mapped_ctrl_c |= mode;
}
}
goto theend;
}
if (!has_lhs || !has_rhs) { // print entries
if (!did_it && !did_local) {
if (is_abbrev) {
msg(_("No abbreviation found"));
} else {
msg(_("No mapping found"));
}
mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_luaref = args->rhs_lua;
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
mp->m_mode = mode;
mp->m_simplified = did_simplify && keyround == 1;
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
nlua_set_sctx(&mp->m_script_ctx);
mp->m_desc = NULL;
if (args->desc != NULL) {
mp->m_desc = xstrdup(args->desc);
}
goto theend; // listing finished
}
if (did_it) { // have added the new entry already
goto theend;
}
// Get here when adding a new entry to the maphash[] list or abbrlist.
mp = xmalloc(sizeof(mapblock_T));
// If CTRL-C has been mapped, don't always use it for Interrupting.
if (*lhs == Ctrl_C) {
if (map_table == buf->b_maphash) {
buf->b_mapped_ctrl_c |= mode;
// add the new entry in front of the abbrlist or maphash[] list
if (is_abbrev) {
mp->m_next = *abbr_table;
*abbr_table = mp;
} else {
mapped_ctrl_c |= mode;
n = MAP_HASH(mp->m_mode, mp->m_keys[0]);
mp->m_next = map_table[n];
map_table[n] = mp;
}
}
mp->m_keys = vim_strsave(lhs);
mp->m_str = vim_strsave(rhs);
mp->m_orig_str = vim_strsave(orig_rhs);
mp->m_luaref = args->rhs_lua;
mp->m_keylen = (int)STRLEN(mp->m_keys);
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
mp->m_mode = mode;
mp->m_expr = args->expr;
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += sourcing_lnum;
nlua_set_sctx(&mp->m_script_ctx);
mp->m_desc = NULL;
if (args->desc != NULL) {
mp->m_desc = xstrdup(args->desc);
}
// add the new entry in front of the abbrlist or maphash[] list
if (is_abbrev) {
mp->m_next = *abbr_table;
*abbr_table = mp;
} else {
n = MAP_HASH(mp->m_mode, mp->m_keys[0]);
mp->m_next = map_table[n];
map_table[n] = mp;
}
theend:
return retval;
}
@ -3600,9 +3634,8 @@ bool map_to_exists(const char *const str, const char *const modechars, const boo
int retval;
char_u *buf;
char_u *const rhs = replace_termcodes((const char_u *)str, strlen(str), &buf,
false, true, true,
CPO_TO_CPO_FLAGS);
const char_u *const rhs = replace_termcodes((const char_u *)str, strlen(str), &buf, REPTERM_DO_LT,
NULL, CPO_TO_CPO_FLAGS);
#define MAPMODE(mode, modechars, chr, modeflags) \
do { \

View File

@ -48,6 +48,10 @@ struct map_arguments {
char_u lhs[MAXMAPLEN + 1];
size_t lhs_len;
/// Unsimplifed {lhs} of the mapping. If no simplification has been done then alt_lhs_len is 0.
char_u alt_lhs[MAXMAPLEN + 1];
size_t alt_lhs_len;
char_u *rhs; /// The {rhs} of the mapping.
size_t rhs_len;
LuaRef rhs_lua; /// lua function as rhs
@ -59,7 +63,7 @@ struct map_arguments {
};
typedef struct map_arguments MapArguments;
#define MAP_ARGUMENTS_INIT { false, false, false, false, false, false, false, \
{ 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
{ 0 }, 0, { 0 }, 0, NULL, 0, LUA_NOREF, false, NULL, 0, NULL }
#define KEYLEN_PART_KEY (-1) // keylen value for incomplete key-code
#define KEYLEN_PART_MAP (-2) // keylen value for incomplete mapping

View File

@ -570,16 +570,18 @@ char_u *get_special_key_name(int c, int modifiers)
// be at least 19 bytes per "<x>" form.
/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL.
/// @param[in] in_string Inside a double quoted string
/// @param[in] simplify simplify <C-H>, etc.
/// @param[out] did_simplify found <C-H>, etc.
///
/// @return Number of characters added to dst, zero for no match.
unsigned int trans_special(const char_u **srcp, const size_t src_len, char_u *const dst,
const bool keycode, const bool in_string)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
unsigned int trans_special(const char_u **const srcp, const size_t src_len, char_u *const dst,
const bool keycode, const bool in_string, const bool simplify,
bool *const did_simplify)
FUNC_ATTR_NONNULL_ARG(1, 3) FUNC_ATTR_WARN_UNUSED_RESULT
{
int modifiers = 0;
int key;
key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string);
int key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string, simplify,
did_simplify);
if (key == 0) {
return 0;
}
@ -626,11 +628,14 @@ unsigned int special_to_buf(int key, int modifiers, bool keycode, char_u *dst)
/// @param[in] keycode Prefer key code, e.g. K_DEL in place of DEL.
/// @param[in] keep_x_key Dont translate xHome to Home key.
/// @param[in] in_string In string, double quote is escaped
/// @param[in] simplify simplify <C-H>, etc.
/// @param[out] did_simplify found <C-H>, etc.
///
/// @return Key and modifiers or 0 if there is no match.
int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, const bool keycode,
const bool keep_x_key, const bool in_string)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
int find_special_key(const char_u **const srcp, const size_t src_len, int *const modp,
const bool keycode, const bool keep_x_key, const bool in_string,
const bool simplify, bool *const did_simplify)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1, 3)
{
const char_u *last_dash;
const char_u *end_of_name;
@ -748,7 +753,7 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
// Normal Key with modifier:
// Try to make a single byte code (except for Alt/Meta modifiers).
if (!IS_SPECIAL(key)) {
key = extract_modifiers(key, &modifiers);
key = extract_modifiers(key, &modifiers, simplify, did_simplify);
}
*modp = modifiers;
@ -762,7 +767,10 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp,
/// Try to include modifiers (except alt/meta) in the key.
/// Changes "Shift-a" to 'A', "Ctrl-@" to <Nul>, etc.
static int extract_modifiers(int key, int *modp)
/// @param[in] simplify if false, don't do Ctrl
/// @param[out] did_simplify set when it is not NULL and "simplify" is true and
/// Ctrl is removed from modifiers
static int extract_modifiers(int key, int *modp, const bool simplify, bool *const did_simplify)
{
int modifiers = *modp;
@ -773,15 +781,19 @@ static int extract_modifiers(int key, int *modp)
modifiers &= ~MOD_MASK_SHIFT;
}
}
if ((modifiers & MOD_MASK_CTRL) && ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
// <C-H> and <C-h> mean the same thing, always use "H"
if ((modifiers & MOD_MASK_CTRL) && ASCII_ISALPHA(key)) {
key = TOUPPER_ASC(key);
int new_key = CTRL_CHR(key);
if (new_key != TAB && new_key != CAR && new_key != ESC) {
key = new_key;
modifiers &= ~MOD_MASK_CTRL;
if (key == 0) { // <C-@> is <Nul>
key = K_ZERO;
}
}
if (simplify && (modifiers & MOD_MASK_CTRL)
&& ((key >= '?' && key <= '_') || ASCII_ISALPHA(key))) {
key = CTRL_CHR(key);
modifiers &= ~MOD_MASK_CTRL;
if (key == 0) { // <C-@> is <Nul>
key = K_ZERO;
}
if (did_simplify != NULL) {
*did_simplify = true;
}
}
@ -853,34 +865,31 @@ int get_mouse_button(int code, bool *is_click, bool *is_drag)
return 0; // Shouldn't get here
}
/// Replace any terminal code strings with the equivalent internal
/// representation
/// Replace any terminal code strings with the equivalent internal representation.
///
/// Used for the "from" and "to" part of a mapping, and the "to" part of
/// a menu command. Any strings like "<C-UP>" are also replaced, unless
/// `special` is false. K_SPECIAL by itself is replaced by K_SPECIAL
/// KS_SPECIAL KE_FILLER.
/// Used for the "from" and "to" part of a mapping, and the "to" part of a menu command.
/// Any strings like "<C-UP>" are also replaced, unless `special` is false.
/// K_SPECIAL by itself is replaced by K_SPECIAL KS_SPECIAL KE_FILLER.
///
/// When "flags" has REPTERM_FROM_PART, trailing <C-v> is included, otherwise it is removed (to make
/// ":map xx ^V" map xx to nothing). When cpo_flags contains FLAG_CPO_BSLASH, a backslash can be
/// used in place of <C-v>. All other <C-v> characters are removed.
///
/// @param[in] from What characters to replace.
/// @param[in] from_len Length of the "from" argument.
/// @param[out] bufp Location where results were saved in case of success
/// (allocated). Will be set to NULL in case of failure.
/// @param[in] do_lt If true, also translate <lt>.
/// @param[in] from_part If true, trailing <C-v> is included, otherwise it is
/// removed (to make ":map xx ^V" map xx to nothing).
/// When cpo_flags contains #FLAG_CPO_BSLASH, a backslash
/// can be used in place of <C-v>. All other <C-v>
/// characters are removed.
/// @param[in] special Replace keycodes, e.g. <CR> becomes a "\n" char.
/// @param[in] cpo_flags Relevant flags derived from p_cpo, see
/// #CPO_TO_CPO_FLAGS.
/// @param[out] bufp Location where results were saved in case of success (allocated).
/// Will be set to NULL in case of failure.
/// @param[in] flags REPTERM_FROM_PART see above
/// REPTERM_DO_LT also translate <lt>
/// REPTERM_NO_SPECIAL do not accept <key> notation
/// REPTERM_NO_SIMPLIFY do not simplify <C-H> into 0x08, etc.
/// @param[out] did_simplify set when some <C-H> code was simplied, unless it is NULL.
/// @param[in] cpo_flags Relevant flags derived from p_cpo, see CPO_TO_CPO_FLAGS.
///
/// @return Pointer to an allocated memory in case of success, "from" in case of
/// failure. In case of success returned pointer is also saved to
/// "bufp".
char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bufp,
const bool from_part, const bool do_lt, const bool special, int cpo_flags)
FUNC_ATTR_NONNULL_ALL
/// @return Pointer to an allocated memory, which is also saved to "bufp".
char_u *replace_termcodes(const char_u *const from, const size_t from_len, char_u **const bufp,
const int flags, bool *const did_simplify, const int cpo_flags)
FUNC_ATTR_NONNULL_ARG(1, 3)
{
ssize_t i;
size_t slen;
@ -888,10 +897,10 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
size_t dlen = 0;
const char_u *src;
const char_u *const end = from + from_len - 1;
int do_backslash; // backslash is a special character
char_u *result; // buffer for resulting string
do_backslash = !(cpo_flags&FLAG_CPO_BSLASH);
const bool do_backslash = !(cpo_flags & FLAG_CPO_BSLASH); // backslash is a special character
const bool do_special = !(flags & REPTERM_NO_SPECIAL);
// Allocate space for the translation. Worst case a single character is
// replaced by 6 bytes (shifted special key), plus a NUL at the end.
@ -901,7 +910,7 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
src = from;
// Check for #n at start only: function key n
if (from_part && from_len > 1 && src[0] == '#'
if ((flags & REPTERM_FROM_PART) && from_len > 1 && src[0] == '#'
&& ascii_isdigit(src[1])) { // function key
result[dlen++] = K_SPECIAL;
result[dlen++] = 'k';
@ -916,8 +925,8 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
// Copy each byte from *from to result[dlen]
while (src <= end) {
// Check for special <> keycodes, like "<C-S-LeftMouse>"
if (special && (do_lt || ((end - src) >= 3
&& STRNCMP(src, "<lt>", 4) != 0))) {
if (do_special && ((flags & REPTERM_DO_LT) || ((end - src) >= 3
&& STRNCMP(src, "<lt>", 4) != 0))) {
// Replace <SID> by K_SNR <script-nr> _.
// (room: 5 * 6 = 30 bytes; needed: 3 + <nr> + 1 <= 14)
if (end - src >= 4 && STRNICMP(src, "<SID>", 5) == 0) {
@ -936,15 +945,15 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
}
}
slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen, true,
false);
slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen, true, false,
(flags & REPTERM_NO_SIMPLIFY) == 0, did_simplify);
if (slen) {
dlen += slen;
continue;
}
}
if (special) {
if (do_special) {
char_u *p, *s, len;
// Replace <Leader> by the value of "mapleader".
@ -984,7 +993,7 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, char_u **bu
if (key == Ctrl_V || (do_backslash && key == '\\')) {
src++; // skip CTRL-V or backslash
if (src > end) {
if (from_part) {
if (flags & REPTERM_FROM_PART) {
result[dlen++] = key;
}
break;

View File

@ -236,8 +236,8 @@ void ex_menu(exarg_T *eap)
} else if (modes & MENU_TIP_MODE) {
map_buf = NULL; // Menu tips are plain text.
} else {
map_to = (char *)replace_termcodes((char_u *)map_to, STRLEN(map_to),
(char_u **)&map_buf, false, true, true, CPO_TO_CPO_FLAGS);
map_to = (char *)replace_termcodes((char_u *)map_to, STRLEN(map_to), (char_u **)&map_buf,
REPTERM_DO_LT, NULL, CPO_TO_CPO_FLAGS);
}
menuarg.modes = modes;
menuarg.noremap[0] = noremap;

View File

@ -3015,7 +3015,7 @@ ambw_end:
} else if (varp == &p_pt) {
// 'pastetoggle': translate key codes like in a mapping
if (*p_pt) {
(void)replace_termcodes(p_pt, STRLEN(p_pt), &p, true, true, true,
(void)replace_termcodes(p_pt, STRLEN(p_pt), &p, REPTERM_FROM_PART | REPTERM_DO_LT, NULL,
CPO_TO_CPO_FLAGS);
if (p != NULL) {
if (new_value_alloced) {
@ -5222,7 +5222,7 @@ int find_key_option_len(const char_u *arg_arg, size_t len, bool has_lt)
} else if (has_lt) {
arg--; // put arg at the '<'
modifiers = 0;
key = find_special_key(&arg, len + 1, &modifiers, true, true, false);
key = find_special_key(&arg, len + 1, &modifiers, true, true, false, true, NULL);
if (modifiers) { // can't handle modifiers here
key = 0;
}

View File

@ -239,8 +239,7 @@ size_t input_enqueue(String keys)
// K_SPECIAL(0x80).
uint8_t buf[19] = { 0 };
unsigned int new_size
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true,
false);
= trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, false, true, NULL);
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);

View File

@ -318,4 +318,10 @@ enum { FOLD_TEXT_LEN = 51, }; //!< buffer size for get_foldtext()
#define REPLACE_CR_NCHAR (-1)
#define REPLACE_NL_NCHAR (-2)
// Flags for replace_termcodes()
#define REPTERM_FROM_PART 1
#define REPTERM_DO_LT 2
#define REPTERM_NO_SPECIAL 4
#define REPTERM_NO_SIMPLIFY 8
#endif // NVIM_VIM_H

View File

@ -1817,9 +1817,8 @@ static void parse_quoted_string(ParserState *const pstate, ExprASTNode *const no
}
// Special key, e.g.: "\<C-W>"
case '<': {
const size_t special_len = (
trans_special((const char_u **)&p, (size_t)(e - p),
(char_u *)v_p, true, true));
const size_t special_len = trans_special((const char_u **)&p, (size_t)(e - p),
(char_u *)v_p, true, true, true, NULL);
if (special_len != 0) {
v_p += special_len;
} else {

View File

@ -582,7 +582,7 @@ describe('nvim_set_keymap, nvim_del_keymap', function()
it('can set mappings containing literal keycodes', function()
meths.set_keymap('n', '\n\r\n', 'rhs', {})
local expected = generate_mapargs('n', '<NL><CR><NL>', 'rhs')
eq(expected, get_mapargs('n', '<C-j><CR><C-j>'))
eq(expected, get_mapargs('n', '<NL><CR><NL>'))
end)
it('can set mappings whose RHS is a <Nop>', function()

View File

@ -6,6 +6,7 @@ local eq = helpers.eq
local neq = helpers.neq
local keymap = helpers.cimport("./src/nvim/keymap.h")
local NULL = helpers.NULL
describe('keymap.c', function()
@ -15,12 +16,12 @@ describe('keymap.c', function()
itp('no keycode', function()
srcp[0] = 'abc'
eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false))
eq(0, keymap.find_special_key(srcp, 3, modp, false, false, false, false, NULL))
end)
itp('keycode with multiple modifiers', function()
srcp[0] = '<C-M-S-A>'
neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false))
neq(0, keymap.find_special_key(srcp, 9, modp, false, false, false, false, NULL))
neq(0, modp[0])
end)
@ -28,22 +29,22 @@ describe('keymap.c', function()
-- Compare other capitalizations to this.
srcp[0] = '<C-A>'
local all_caps_key =
keymap.find_special_key(srcp, 5, modp, false, false, false)
keymap.find_special_key(srcp, 5, modp, false, false, false, false, NULL)
local all_caps_mod = modp[0]
srcp[0] = '<C-a>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
keymap.find_special_key(srcp, 5, modp, false, false, false, false, NULL))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-A>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
keymap.find_special_key(srcp, 5, modp, false, false, false, false, NULL))
eq(all_caps_mod, modp[0])
srcp[0] = '<c-a>'
eq(all_caps_key,
keymap.find_special_key(srcp, 5, modp, false, false, false))
keymap.find_special_key(srcp, 5, modp, false, false, false, false, NULL))
eq(all_caps_mod, modp[0])
end)
@ -51,20 +52,20 @@ describe('keymap.c', function()
-- Unescaped with in_string=false
srcp[0] = '<C-">'
eq(string.byte('"'),
keymap.find_special_key(srcp, 5, modp, false, false, false))
keymap.find_special_key(srcp, 5, modp, false, false, false, false, NULL))
-- Unescaped with in_string=true
eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true))
eq(0, keymap.find_special_key(srcp, 5, modp, false, false, true, false, NULL))
-- Escaped with in_string=false
srcp[0] = '<C-\\">'
-- Should fail because the key is invalid
-- (more than 1 non-modifier character).
eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false))
eq(0, keymap.find_special_key(srcp, 6, modp, false, false, false, false, NULL))
-- Escaped with in_string=true
eq(string.byte('"'),
keymap.find_special_key(srcp, 6, modp, false, false, true))
keymap.find_special_key(srcp, 6, modp, false, false, true, false, NULL))
end)
end)