diff options
-rw-r--r-- | src/nvim/api/private/helpers.c | 5 | ||||
-rw-r--r-- | src/nvim/api/vim.c | 14 | ||||
-rw-r--r-- | src/nvim/buffer_defs.h | 2 | ||||
-rw-r--r-- | src/nvim/eval.c | 2 | ||||
-rw-r--r-- | src/nvim/eval/funcs.c | 2 | ||||
-rw-r--r-- | src/nvim/ex_docmd.c | 3 | ||||
-rw-r--r-- | src/nvim/getchar.c | 549 | ||||
-rw-r--r-- | src/nvim/getchar.h | 6 | ||||
-rw-r--r-- | src/nvim/keymap.c | 111 | ||||
-rw-r--r-- | src/nvim/menu.c | 4 | ||||
-rw-r--r-- | src/nvim/option.c | 4 | ||||
-rw-r--r-- | src/nvim/os/input.c | 3 | ||||
-rw-r--r-- | src/nvim/vim.h | 6 | ||||
-rw-r--r-- | src/nvim/viml/parser/expressions.c | 5 | ||||
-rw-r--r-- | test/functional/api/keymap_spec.lua | 2 | ||||
-rw-r--r-- | test/unit/keymap_spec.lua | 21 |
16 files changed, 402 insertions, 337 deletions
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 5ba4700f62..8383f29400 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -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); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 0f9a4a0e0d..2174cd1620 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -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); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 8d2f7c4545..c71d022018 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -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 diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e192a6de6b..af6e7e470d 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -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) { diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c index f97ceedeb7..71a090afed 100644 --- a/src/nvim/eval/funcs.c +++ b/src/nvim/eval/funcs.c @@ -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); diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 4562f6c751..f3b11cc103 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -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); diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index f2df7b49fd..89a9a1ea95 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -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,298 +2947,323 @@ 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 (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 (keyround == 2) { + if (!did_simplify) { + break; } - if (last && n > 2 && same >= 0 && same < n - 1) { + 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; + } + + // 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 + // 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 (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(); - } + 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 (hash = 0; hash < 256 && !got_int; hash++) { - if (is_abbrev) { - if (hash != 0) { // there is only one abbreviation list - break; + // 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]; } - 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); + 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; + // 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; + } + mpp = abbr_table; + } else { + mpp = &(map_table[hash]); } - 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; + 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 (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)) { + 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 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 { - 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); + // 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 (map_table == buf->b_maphash) { - buf->b_mapped_ctrl_c &= ~mode; - } else { - mapped_ctrl_c &= ~mode; + 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; } - 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")); + 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 } - goto theend; // listing finished - } - if (did_it) { // have added the new entry already - goto theend; - } + 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)); + // 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; - } else { - mapped_ctrl_c |= mode; + // 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; + } else { + mapped_ctrl_c |= mode; + } } - } - 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; + 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); + } + + // 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: @@ -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 { \ diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index 9b8605f1df..237f0632bd 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -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 diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index 3fdc140ebd..07975545be 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -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 Don’t 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; diff --git a/src/nvim/menu.c b/src/nvim/menu.c index f648a06284..d2293a6923 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -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; diff --git a/src/nvim/option.c b/src/nvim/option.c index 49c76d2452..79231385ad 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -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; } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 745b888b5e..4c617c9031 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -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); diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 3c8a865fb1..2672d0b2bc 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -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 diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index b8b0a38f44..ec747d56d4 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -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 { diff --git a/test/functional/api/keymap_spec.lua b/test/functional/api/keymap_spec.lua index c0edcde476..a11b5306f4 100644 --- a/test/functional/api/keymap_spec.lua +++ b/test/functional/api/keymap_spec.lua @@ -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() diff --git a/test/unit/keymap_spec.lua b/test/unit/keymap_spec.lua index 595a19eb17..1255774f11 100644 --- a/test/unit/keymap_spec.lua +++ b/test/unit/keymap_spec.lua @@ -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) |