aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/getchar.c110
-rw-r--r--test/functional/ui/input_spec.lua8
3 files changed, 106 insertions, 14 deletions
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index b8f88895a4..076cf445cf 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -5640,7 +5640,7 @@ int get_literal(bool no_simplify)
for (;;) {
nc = plain_vgetc();
if (!no_simplify) {
- nc = merge_modifiers(nc);
+ nc = merge_modifiers(nc, &mod_mask);
}
if ((mod_mask & ~MOD_MASK_SHIFT) != 0) {
// A character with non-Shift modifiers should not be a valid
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 63e26fd0a4..b7894da206 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -1442,12 +1442,12 @@ static void updatescript(int c)
}
}
-/// Merge "mod_mask" into "c_arg"
-int merge_modifiers(int c_arg)
+/// Merge "modifiers" into "c_arg".
+int merge_modifiers(int c_arg, int *modifiers)
{
int c = c_arg;
- if (mod_mask & MOD_MASK_CTRL) {
+ if (*modifiers & MOD_MASK_CTRL) {
if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) {
c &= 0x1f;
if (c == 0) {
@@ -1458,7 +1458,7 @@ int merge_modifiers(int c_arg)
c = 0x1e;
}
if (c != c_arg) {
- mod_mask &= ~MOD_MASK_CTRL;
+ *modifiers &= ~MOD_MASK_CTRL;
}
}
return c;
@@ -1636,7 +1636,7 @@ int vgetc(void)
// cases they are put back in the typeahead buffer.
vgetc_mod_mask = mod_mask;
vgetc_char = c;
- c = merge_modifiers(c);
+ c = merge_modifiers(c, &mod_mask);
// If mappings are enabled (i.e., not Ctrl-v) and the user directly typed
// something with a meta- or alt- modifier that was not mapped, interpret
@@ -1752,6 +1752,55 @@ typedef enum {
map_result_nomatch, // no matching mapping, get char
} map_result_T;
+/// Put "string[new_slen]" in typebuf.
+/// Remove "slen" bytes.
+/// @return FAIL for error, OK otherwise.
+static int put_string_in_typebuf(int offset, int slen, char_u *string, int new_slen)
+{
+ int extra = new_slen - slen;
+ string[new_slen] = NUL;
+ if (extra < 0) {
+ // remove matched chars, taking care of noremap
+ del_typebuf(-extra, offset);
+ } else if (extra > 0) {
+ // insert the extra space we need
+ ins_typebuf(string + slen, REMAP_YES, offset, false, false);
+ }
+ // Careful: del_typebuf() and ins_typebuf() may have reallocated
+ // typebuf.tb_buf[]!
+ memmove(typebuf.tb_buf + typebuf.tb_off + offset, string, (size_t)new_slen);
+ return OK;
+}
+
+/// Check if typebuf.tb_buf[] contains a modifer plus key that can be changed
+/// into just a key, apply that.
+/// Check from typebuf.tb_buf[typebuf.tb_off] to typebuf.tb_buf[typebuf.tb_off + "max_offset"].
+/// @return the length of the replaced bytes, zero if nothing changed.
+static int check_simplify_modifier(int max_offset)
+{
+ for (int offset = 0; offset < max_offset; offset++) {
+ if (offset + 3 >= typebuf.tb_len) {
+ break;
+ }
+ char_u *tp = typebuf.tb_buf + typebuf.tb_off + offset;
+ if (tp[0] == K_SPECIAL && tp[1] == KS_MODIFIER) {
+ int modifier = tp[2];
+ int new_c = merge_modifiers(tp[3], &modifier);
+
+ if (new_c != tp[3] && modifier == 0) {
+ char_u new_string[MB_MAXBYTES];
+ int len = utf_char2bytes(new_c, new_string);
+
+ if (put_string_in_typebuf(offset, 4, new_string, len) == FAIL) {
+ return -1;
+ }
+ return len;
+ }
+ }
+ }
+ return 0;
+}
+
/// Handle mappings in the typeahead buffer.
/// - When something was mapped, return map_result_retry for recursive mappings.
/// - When nothing mapped and typeahead has a character: return map_result_get.
@@ -1950,16 +1999,51 @@ static int handle_mapping(int *keylenp, bool *timedout, int *mapdepth)
}
if ((mp == NULL || max_mlen >= mp_match_len) && keylen != KEYLEN_PART_MAP) {
- // No matching mapping found or found a non-matching mapping that
- // matches at least what the matching mapping matched
- keylen = 0;
- (void)keylen; // suppress clang/dead assignment
- // If there was no mapping, use the character from the typeahead
- // buffer right here. Otherwise, use the mapping (loop around).
- if (mp == NULL) {
+ // When no matching mapping found or found a non-matching mapping that
+ // matches at least what the matching mapping matched:
+ // Try to include the modifier into the key, when:
+ // - mapping is allowed,
+ // - keys have not been mapped,
+ // - and when not timed out,
+ if ((no_mapping == 0 || allow_keys != 0)
+ && (typebuf.tb_maplen == 0
+ || (p_remap && typebuf.tb_noremap[typebuf.tb_off] == RM_YES))
+ && !*timedout) {
+ if (tb_c1 == K_SPECIAL
+ && (typebuf.tb_len < 2
+ || (typebuf.tb_buf[typebuf.tb_off + 1] == KS_MODIFIER
+ && typebuf.tb_len < 4))) {
+ // Incomplete modifier sequence: cannot decide whether to simplify yet.
+ keylen = KEYLEN_PART_KEY;
+ // Don't simplify if 'pastetoggle' matched partially.
+ } else if (keylen != KEYLEN_PART_KEY) {
+ // Try to include the modifier into the key.
+ keylen = check_simplify_modifier(max_mlen + 1);
+ assert(keylen >= 0);
+ }
+ } else {
+ keylen = 0;
+ }
+ if (keylen == 0) { // no simplication has been done
+ // If there was no mapping at all use the character from the
+ // typeahead buffer right here.
+ if (mp == NULL) {
+ *keylenp = keylen;
+ return map_result_get; // get character from typeahead
+ }
+ }
+
+ if (keylen > 0) { // keys have been simplified
*keylenp = keylen;
- return map_result_get; // get character from typeahead
+ return map_result_retry; // try mapping again
+ }
+
+ if (keylen < 0) {
+ // Incomplete key sequence: get some more characters.
+ assert(keylen == KEYLEN_PART_KEY);
} else {
+ assert(mp != NULL);
+ // When a matching mapping was found use that one.
keylen = mp_match_len;
}
}
diff --git a/test/functional/ui/input_spec.lua b/test/functional/ui/input_spec.lua
index 799aad8d55..43b6c19b44 100644
--- a/test/functional/ui/input_spec.lua
+++ b/test/functional/ui/input_spec.lua
@@ -249,6 +249,14 @@ it('typing a simplifiable key at hit-enter prompt triggers mapping vim-patch:8.2
]])
end)
+it('mixing simplified and unsimplified keys can trigger mapping vim-patch:8.2.0916', function()
+ command('set timeoutlen=10')
+ command([[imap ' <C-W>]])
+ command('imap <C-W><C-A> c-a')
+ feed([[a'<C-A>]])
+ expect('c-a')
+end)
+
describe('input non-printable chars', function()
after_each(function()
os.remove('Xtest-overwrite')