# HG changeset patch # User Bram Moolenaar # Date 1570977904 -7200 # Node ID 506bf60a30a0e9debbc4cd05fa1c66235e2b407c # Parent e502ed410b63c81cfdd63e99a56912859701a0dc patch 8.1.2145: cannot map when modifyOtherKeys is enabled Commit: https://github.com/vim/vim/commit/459fd785e4a8d044147a3f83a5fca8748528aa84 Author: Bram Moolenaar Date: Sun Oct 13 16:43:39 2019 +0200 patch 8.1.2145: cannot map when modifyOtherKeys is enabled Problem: Cannot map when modifyOtherKeys is enabled. Solution: Add the mapping twice, both with modifier and as 0x08. Use only the first one when modifyOtherKeys has been detected. diff --git a/src/eval.c b/src/eval.c --- a/src/eval.c +++ b/src/eval.c @@ -3526,7 +3526,8 @@ get_string_tv(char_u **arg, typval_T *re break; /* Special key, e.g.: "\" */ - case '<': extra = trans_special(&p, name, TRUE, TRUE); + case '<': extra = trans_special(&p, name, TRUE, TRUE, + TRUE, NULL); if (extra != 0) { name += extra; diff --git a/src/getchar.c b/src/getchar.c --- a/src/getchar.c +++ b/src/getchar.c @@ -52,7 +52,7 @@ static int typeahead_char = 0; /* typea */ static int block_redo = FALSE; -static int KeyNoremap = 0; /* remapping flags */ +static int KeyNoremap = 0; // remapping flags /* * Variables used by vgetorpeek() and flush_buffers(). @@ -1771,7 +1771,7 @@ vgetc(void) if (!no_reduce_keys) { // A modifier was not used for a mapping, apply it to ASCII - // keys. + // keys. Shift would already have been applied. if ((mod_mask & MOD_MASK_CTRL) && ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_'))) @@ -2240,6 +2240,7 @@ handle_mapping( // Skip ":lmap" mappings if keys were mapped. if (mp->m_keys[0] == tb_c1 && (mp->m_mode & local_State) + && !(mp->m_simplified && seenModifyOtherKeys) && ((mp->m_mode & LANGMAP) == 0 || typebuf.tb_maplen == 0)) { #ifdef FEAT_LANGMAP diff --git a/src/globals.h b/src/globals.h --- a/src/globals.h +++ b/src/globals.h @@ -1002,6 +1002,10 @@ EXTERN int ex_no_reprint INIT(= FALSE); EXTERN int reg_recording INIT(= 0); // register for recording or zero EXTERN int reg_executing INIT(= 0); // register being executed or zero +// Set when a modifyOtherKeys sequence was seen, then simplified mappings will +// no longer be used. +EXTERN int seenModifyOtherKeys INIT(= FALSE); + EXTERN int no_mapping INIT(= FALSE); // currently no mapping allowed EXTERN int no_zero_mapping INIT(= 0); // mapping zero not allowed EXTERN int allow_keys INIT(= FALSE); // allow key codes when no_mapping diff --git a/src/gui_mac.c b/src/gui_mac.c --- a/src/gui_mac.c +++ b/src/gui_mac.c @@ -2177,7 +2177,8 @@ gui_mac_unicode_key_event( key_char = simplify_key(key_char, (int *)&vimModifiers); /* Interpret META, include SHIFT, etc. */ - key_char = extract_modifiers(key_char, (int *)&vimModifiers); + key_char = extract_modifiers(key_char, (int *)&vimModifiers, + TRUE, NULL); if (key_char == CSI) key_char = K_CSI; @@ -4772,7 +4773,8 @@ gui_mch_add_menu_item(vimmenu_T *menu, i char_u *p_actext; p_actext = menu->actext; - key = find_special_key(&p_actext, &modifiers, FALSE, FALSE, FALSE); + key = find_special_key(&p_actext, &modifiers, FALSE, FALSE, FALSE, + TRUE, NULL); if (*p_actext != 0) key = 0; /* error: trailing text */ /* find_special_key() returns a keycode with as many of the diff --git a/src/gui_w32.c b/src/gui_w32.c --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -850,7 +850,7 @@ char_to_string(int ch, char_u *string, i modifiers &= ~MOD_MASK_SHIFT; /* Interpret the ALT key as making the key META, include SHIFT, etc. */ - ch = extract_modifiers(ch, &modifiers); + ch = extract_modifiers(ch, &modifiers, TRUE, NULL); if (ch == CSI) ch = K_CSI; diff --git a/src/highlight.c b/src/highlight.c --- a/src/highlight.c +++ b/src/highlight.c @@ -1417,7 +1417,8 @@ do_highlight( */ for (p = arg, off = 0; off < 100 - 6 && *p; ) { - len = trans_special(&p, buf + off, FALSE, FALSE); + len = trans_special(&p, buf + off, FALSE, FALSE, + TRUE, NULL); if (len > 0) // recognized special char off += len; else // copy as normal char diff --git a/src/if_ole.cpp b/src/if_ole.cpp --- a/src/if_ole.cpp +++ b/src/if_ole.cpp @@ -330,7 +330,7 @@ CVim::SendKeys(BSTR keys) } /* Translate key codes like */ - str = replace_termcodes((char_u *)buffer, &ptr, FALSE, TRUE, FALSE); + str = replace_termcodes((char_u *)buffer, &ptr, REPTERM_DO_LT, NULL); /* If ptr was set, then a new buffer was allocated, * so we can free the old one. diff --git a/src/main.c b/src/main.c --- a/src/main.c +++ b/src/main.c @@ -4339,7 +4339,7 @@ server_to_input_buf(char_u *str) * sequence is recognised - needed for a real backslash. */ p_cpo = (char_u *)"Bk"; - str = replace_termcodes((char_u *)str, &ptr, FALSE, TRUE, FALSE); + str = replace_termcodes((char_u *)str, &ptr, REPTERM_DO_LT, NULL); p_cpo = cpo_save; if (*ptr != NUL) /* trailing CTRL-V results in nothing */ diff --git a/src/map.c b/src/map.c --- a/src/map.c +++ b/src/map.c @@ -256,18 +256,15 @@ do_map( char_u *p; int n; int len = 0; // init for GCC - char_u *newstr; int hasarg; int haskey; - int did_it = FALSE; - int did_local = FALSE; - int round; + int do_print; + int keyround; char_u *keys_buf = NULL; + char_u *alt_keys_buf = NULL; char_u *arg_buf = NULL; int retval = 0; int do_backslash; - int hash; - int new_hash; mapblock_T **abbr_table; mapblock_T **map_table; int unique = FALSE; @@ -277,6 +274,7 @@ do_map( #ifdef FEAT_EVAL int expr = FALSE; #endif + int did_simplify = FALSE; int noremap; char_u *orig_rhs; @@ -375,6 +373,7 @@ do_map( rhs = p; hasarg = (*rhs != NUL); haskey = (*keys != NUL); + do_print = !haskey || (maptype != 1 && !hasarg); // check for :unmap without argument if (maptype == 1 && !haskey) @@ -389,373 +388,427 @@ do_map( // replace_termcodes() may move the result to allocated memory, which // needs to be freed later (*keys_buf and *arg_buf). // replace_termcodes() also removes CTRL-Vs and sometimes backslashes. + // If something like is simplified to 0x08 then mark it as simplified + // and also add a n entry with a modifier, which will work when + // modifyOtherKeys is working. if (haskey) - keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, special); + { + char_u *new_keys; + int flags = REPTERM_FROM_PART | REPTERM_DO_LT; + + if (special) + flags |= REPTERM_SPECIAL; + new_keys = replace_termcodes(keys, &keys_buf, flags, &did_simplify); + if (did_simplify) + (void)replace_termcodes(keys, &alt_keys_buf, + flags | REPTERM_NO_SIMPLIFY, NULL); + keys = new_keys; + } orig_rhs = rhs; if (hasarg) { if (STRICMP(rhs, "") == 0) // "" means nothing rhs = (char_u *)""; else - rhs = replace_termcodes(rhs, &arg_buf, FALSE, TRUE, special); + rhs = replace_termcodes(rhs, &arg_buf, + REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL); } - // check arguments and translate function keys - if (haskey) + /* + * The following is done twice if we have two versions of keys: + * "alt_keys_buf" is not NULL. + */ + for (keyround = 1; keyround <= 2; ++keyround) { - len = (int)STRLEN(keys); - if (len > MAXMAPLEN) // maximum length of MAXMAPLEN chars + int did_it = FALSE; + int did_local = FALSE; + int round; + int hash; + int new_hash; + + if (keyround == 2) { - retval = 1; - goto theend; + if (alt_keys_buf == NULL) + break; + keys = alt_keys_buf; } + else if (alt_keys_buf != NULL && do_print) + // when printing always use the not-simplified map + keys = alt_keys_buf; - if (abbrev && maptype != 1) + // check arguments and translate function keys + if (haskey) { - // 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. - if (has_mbyte) + len = (int)STRLEN(keys); + if (len > MAXMAPLEN) // maximum length of MAXMAPLEN chars { - int first, last; - int same = -1; + retval = 1; + goto theend; + } - first = vim_iswordp(keys); - last = first; - p = keys + (*mb_ptr2len)(keys); - n = 1; - while (p < keys + len) + if (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. + if (has_mbyte) { - ++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 += (*mb_ptr2len)(p); + int first, last; + int same = -1; + + first = vim_iswordp(keys); + last = first; + p = keys + (*mb_ptr2len)(keys); + n = 1; + while (p < keys + 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 += (*mb_ptr2len)(p); + } + if (last && n > 2 && same >= 0 && same < n - 1) + { + retval = 1; + goto theend; + } } - if (last && n > 2 && same >= 0 && same < n - 1) - { - retval = 1; - goto theend; - } - } - else if (vim_iswordc(keys[len - 1])) // ends in keyword char + else if (vim_iswordc(keys[len - 1])) + // ends in keyword char for (n = 0; n < len - 2; ++n) if (vim_iswordc(keys[n]) != vim_iswordc(keys[len - 2])) { retval = 1; goto theend; } - // An abbreviation cannot contain white space. - for (n = 0; n < len; ++n) - if (VIM_ISWHITE(keys[n])) - { - retval = 1; - goto theend; - } + // An abbreviation cannot contain white space. + for (n = 0; n < len; ++n) + if (VIM_ISWHITE(keys[n])) + { + retval = 1; + goto theend; + } + } } - } - if (haskey && hasarg && abbrev) // if we will add an abbreviation - no_abbr = FALSE; // reset flag that indicates there are + if (haskey && hasarg && abbrev) // if we will add an abbreviation + no_abbr = FALSE; // reset flag that indicates there are // no abbreviations - if (!haskey || (maptype != 1 && !hasarg)) - msg_start(); + if (do_print) + msg_start(); - // Check if a new local mapping wasn't already defined globally. - if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) - { - // need to loop over all global hash lists - for (hash = 0; hash < 256 && !got_int; ++hash) + // Check if a new local mapping wasn't already defined globally. + if (map_table == curbuf->b_maphash && haskey && hasarg && maptype != 1) { - if (abbrev) + // need to loop over all global hash lists + for (hash = 0; hash < 256 && !got_int; ++hash) { - 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 - && unique - && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) + if (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) { - if (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; + // check entries with the same mode + if ((mp->m_mode & mode) != 0 + && mp->m_keylen == len + && unique + && STRNCMP(mp->m_keys, keys, (size_t)len) == 0) + { + if (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; + } } } } - } - // When listing global mappings, also list buffer-local ones here. - if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) - { - // need to loop over all global hash lists - for (hash = 0; hash < 256 && !got_int; ++hash) + // When listing global mappings, also list buffer-local ones here. + if (map_table != curbuf->b_maphash && !hasarg && maptype != 1) { - if (abbrev) + // need to loop over all global hash lists + for (hash = 0; hash < 256 && !got_int; ++hash) { - if (hash != 0) // there is only one abbreviation list - break; - mp = curbuf->b_first_abbr; - } - else - mp = curbuf->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 (abbrev) { - if (!haskey) // show all entries + if (hash != 0) // there is only one abbreviation list + break; + mp = curbuf->b_first_abbr; + } + else + mp = curbuf->b_maphash[hash]; + for ( ; mp != NULL && !got_int; mp = mp->m_next) + { + // check entries with the same mode + if ((mp->m_mode & mode) != 0) { - showmap(mp, TRUE); - did_local = TRUE; - } - else - { - n = mp->m_keylen; - if (STRNCMP(mp->m_keys, keys, - (size_t)(n < len ? n : len)) == 0) + if (!haskey) // show all entries { showmap(mp, TRUE); did_local = TRUE; } + else + { + n = mp->m_keylen; + if (STRNCMP(mp->m_keys, keys, + (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) + // 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) { - if (abbrev) + // need to loop over all hash lists + for (hash = 0; hash < 256 && !got_int; ++hash) { - 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 + if (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) { - mpp = &(mp->m_next); - continue; - } - if (!haskey) // 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 + + if (!(mp->m_mode & mode)) // skip entries with wrong mode { - n = (int)STRLEN(mp->m_str); - p = mp->m_str; + mpp = &(mp->m_next); + continue; + } + if (!haskey) // show all entries + { + showmap(mp, map_table != maphash); + did_it = TRUE; } - else - { - n = mp->m_keylen; - p = mp->m_keys; - } - if (STRNCMP(p, keys, (size_t)(n < len ? n : len)) == 0) + else // do we have a match? { - if (maptype == 1) // delete entry + 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, keys, (size_t)(n < len ? n : len)) == 0) { - // 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 && (!abbrev || round || n > len + 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 && (!abbrev || round || n > len || *skipwhite(keys + 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 (!hasarg) // 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 (!hasarg) // 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 (unique) - { - if (abbrev) - semsg(_("E226: abbreviation already exists for %s"), - p); + else if (unique) + { + if (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 { - newstr = vim_strsave(rhs); - if (newstr == NULL) + // new rhs for existing entry + mp->m_mode &= ~mode; // remove mode bits + if (mp->m_mode == 0 && !did_it) // reuse entry { - retval = 4; // no mem - goto theend; + char_u *newstr = vim_strsave(rhs); + + if (newstr == NULL) + { + retval = 4; // no mem + goto theend; + } + vim_free(mp->m_str); + mp->m_str = newstr; + vim_free(mp->m_orig_str); + mp->m_orig_str = vim_strsave(orig_rhs); + mp->m_noremap = noremap; + mp->m_nowait = nowait; + mp->m_silent = silent; + mp->m_mode = mode; + mp->m_simplified = + did_simplify && keyround == 1; +#ifdef FEAT_EVAL + mp->m_expr = expr; + mp->m_script_ctx = current_sctx; + mp->m_script_ctx.sc_lnum += sourcing_lnum; +#endif + did_it = TRUE; } - vim_free(mp->m_str); - mp->m_str = newstr; - vim_free(mp->m_orig_str); - mp->m_orig_str = vim_strsave(orig_rhs); - mp->m_noremap = noremap; - mp->m_nowait = nowait; - mp->m_silent = silent; - mp->m_mode = mode; -#ifdef FEAT_EVAL - mp->m_expr = expr; - mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; -#endif - did_it = TRUE; + } + if (mp->m_mode == 0) // entry can be deleted + { + map_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 (!abbrev && new_hash != hash) + { + *mpp = mp->m_next; + mp->m_next = map_table[new_hash]; + map_table[new_hash] = mp; + + continue; // continue with *mpp } } - if (mp->m_mode == 0) // entry can be deleted - { - map_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 (!abbrev && new_hash != hash) - { - *mpp = mp->m_next; - mp->m_next = map_table[new_hash]; - map_table[new_hash] = mp; - - 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 (*keys == Ctrl_C) + if (maptype == 1) { - // If CTRL-C has been unmapped, reuse it for Interrupting. - if (map_table == curbuf->b_maphash) - curbuf->b_mapped_ctrl_c &= ~mode; - else - mapped_ctrl_c &= ~mode; + // delete entry + if (!did_it) + retval = 2; // no match + else if (*keys == Ctrl_C) + { + // If CTRL-C has been unmapped, reuse it for Interrupting. + if (map_table == curbuf->b_maphash) + curbuf->b_mapped_ctrl_c &= ~mode; + else + mapped_ctrl_c &= ~mode; + } + continue; } - goto theend; - } - if (!haskey || !hasarg) // print entries - { - if (!did_it && !did_local) + if (!haskey || !hasarg) { - if (abbrev) - msg(_("No abbreviation found")); - else - msg(_("No mapping found")); + // print entries + if (!did_it && !did_local) + { + if (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 = ALLOC_ONE(mapblock_T); - if (mp == NULL) - { - retval = 4; // no mem - goto theend; - } + // Get here when adding a new entry to the maphash[] list or abbrlist. + mp = ALLOC_ONE(mapblock_T); + if (mp == NULL) + { + retval = 4; // no mem + goto theend; + } - // If CTRL-C has been mapped, don't always use it for Interrupting. - if (*keys == Ctrl_C) - { - if (map_table == curbuf->b_maphash) - curbuf->b_mapped_ctrl_c |= mode; - else - mapped_ctrl_c |= mode; - } + // If CTRL-C has been mapped, don't always use it for Interrupting. + if (*keys == Ctrl_C) + { + if (map_table == curbuf->b_maphash) + curbuf->b_mapped_ctrl_c |= mode; + else + mapped_ctrl_c |= mode; + } - mp->m_keys = vim_strsave(keys); - mp->m_str = vim_strsave(rhs); - mp->m_orig_str = vim_strsave(orig_rhs); - if (mp->m_keys == NULL || mp->m_str == NULL) - { - vim_free(mp->m_keys); - vim_free(mp->m_str); - vim_free(mp->m_orig_str); - vim_free(mp); - retval = 4; // no mem - goto theend; - } - mp->m_keylen = (int)STRLEN(mp->m_keys); - mp->m_noremap = noremap; - mp->m_nowait = nowait; - mp->m_silent = silent; - mp->m_mode = mode; + mp->m_keys = vim_strsave(keys); + mp->m_str = vim_strsave(rhs); + mp->m_orig_str = vim_strsave(orig_rhs); + if (mp->m_keys == NULL || mp->m_str == NULL) + { + vim_free(mp->m_keys); + vim_free(mp->m_str); + vim_free(mp->m_orig_str); + vim_free(mp); + retval = 4; // no mem + goto theend; + } + mp->m_keylen = (int)STRLEN(mp->m_keys); + mp->m_noremap = noremap; + mp->m_nowait = nowait; + mp->m_silent = silent; + mp->m_mode = mode; + mp->m_simplified = did_simplify && keyround == 1; #ifdef FEAT_EVAL - mp->m_expr = expr; - mp->m_script_ctx = current_sctx; - mp->m_script_ctx.sc_lnum += sourcing_lnum; + mp->m_expr = expr; + mp->m_script_ctx = current_sctx; + mp->m_script_ctx.sc_lnum += sourcing_lnum; #endif - // add the new entry in front of the abbrlist or maphash[] list - if (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; + // add the new entry in front of the abbrlist or maphash[] list + if (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: vim_free(keys_buf); + vim_free(alt_keys_buf); vim_free(arg_buf); return retval; } @@ -934,7 +987,7 @@ map_to_exists(char_u *str, char_u *modec char_u *buf; int retval; - rhs = replace_termcodes(str, &buf, FALSE, TRUE, FALSE); + rhs = replace_termcodes(str, &buf, REPTERM_DO_LT, NULL); retval = map_to_exists_mode(rhs, mode_str2flags(modechars), abbr); vim_free(buf); @@ -2036,7 +2089,8 @@ get_maparg(typval_T *argvars, typval_T * mode = get_map_mode(&which, 0); - keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, FALSE); + keys = replace_termcodes(keys, &keys_buf, + REPTERM_FROM_PART | REPTERM_DO_LT, NULL); rhs = check_map(keys, mode, exact, FALSE, abbr, &mp, &buffer_local); vim_free(keys_buf); diff --git a/src/menu.c b/src/menu.c --- a/src/menu.c +++ b/src/menu.c @@ -372,7 +372,8 @@ ex_menu( else if (modes & MENU_TIP_MODE) map_buf = NULL; /* Menu tips are plain text. */ else - map_to = replace_termcodes(map_to, &map_buf, FALSE, TRUE, special); + map_to = replace_termcodes(map_to, &map_buf, + REPTERM_DO_LT | (special ? REPTERM_SPECIAL : 0), NULL); menuarg.modes = modes; #ifdef FEAT_TOOLBAR menuarg.iconfile = icon; diff --git a/src/misc2.c b/src/misc2.c --- a/src/misc2.c +++ b/src/misc2.c @@ -2696,12 +2696,15 @@ trans_special( char_u **srcp, char_u *dst, int keycode, // prefer key code, e.g. K_DEL instead of DEL - int in_string) // TRUE when inside a double quoted string + int in_string, // TRUE when inside a double quoted string + int simplify, // simplify and + int *did_simplify) // found or { int modifiers = 0; int key; - key = find_special_key(srcp, &modifiers, keycode, FALSE, in_string); + key = find_special_key(srcp, &modifiers, keycode, FALSE, in_string, + simplify, did_simplify); if (key == 0) return 0; @@ -2753,9 +2756,11 @@ special_to_buf(int key, int modifiers, i find_special_key( char_u **srcp, int *modp, - int keycode, /* prefer key code, e.g. K_DEL instead of DEL */ - int keep_x_key, /* don't translate xHome to Home key */ - int in_string) /* TRUE in string, double quote is escaped */ + int keycode, // prefer key code, e.g. K_DEL instead of DEL + int keep_x_key, // don't translate xHome to Home key + int in_string, // TRUE in string, double quote is escaped + int simplify, // simplify and + int *did_simplify) // found or { char_u *last_dash; char_u *end_of_name; @@ -2835,7 +2840,8 @@ find_special_key( && VIM_ISDIGIT(last_dash[6])) { /* or or */ - vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, &n, 0, TRUE); + vim_str2nr(last_dash + 6, NULL, &l, STR2NR_ALL, NULL, + &n, 0, TRUE); if (l == 0) { emsg(_(e_invarg)); @@ -2885,11 +2891,10 @@ find_special_key( key = DEL; } - /* - * Normal Key with modifier: Try to make a single byte code. - */ + // Normal Key with modifier: Try to make a single byte code. if (!IS_SPECIAL(key)) - key = extract_modifiers(key, &modifiers); + key = extract_modifiers(key, &modifiers, + simplify, did_simplify); *modp = modifiers; *srcp = end_of_name; @@ -2903,26 +2908,37 @@ find_special_key( /* * Try to include modifiers in the key. * Changes "Shift-a" to 'A', "Alt-A" to 0xc0, etc. + * When "simplify" is FALSE don't do Ctrl and Alt. + * When "simplify" is TRUE and Ctrl or Alt is removed from modifiers set + * "did_simplify" when it's not NULL. */ int -extract_modifiers(int key, int *modp) +extract_modifiers(int key, int *modp, int simplify, int *did_simplify) { int modifiers = *modp; #ifdef MACOS_X - /* Command-key really special, no fancynest */ + // Command-key really special, no fancynest if (!(modifiers & MOD_MASK_CMD)) #endif if ((modifiers & MOD_MASK_SHIFT) && ASCII_ISALPHA(key)) { key = TOUPPER_ASC(key); - modifiers &= ~MOD_MASK_SHIFT; + // With and we keep the shift modifier. + // With and we don't keep the shift modifier. + if (simplify || modifiers == MOD_MASK_SHIFT) + modifiers &= ~MOD_MASK_SHIFT; } - if ((modifiers & MOD_MASK_CTRL) + + // and mean the same thing, always use "H" + if ((modifiers & MOD_MASK_CTRL) && ASCII_ISALPHA(key)) + key = TOUPPER_ASC(key); + + if (simplify && (modifiers & MOD_MASK_CTRL) #ifdef EBCDIC - /* * TODO: EBCDIC Better use: - * && (Ctrl_chr(key) || key == '?') - * ??? */ + // TODO: EBCDIC Better use: + // && (Ctrl_chr(key) || key == '?') + // ??? && strchr("?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_", key) != NULL #else @@ -2935,16 +2951,21 @@ extract_modifiers(int key, int *modp) /* is */ if (key == 0) key = K_ZERO; + if (did_simplify != NULL) + *did_simplify = TRUE; } + #ifdef MACOS_X /* Command-key really special, no fancynest */ if (!(modifiers & MOD_MASK_CMD)) #endif - if ((modifiers & MOD_MASK_ALT) && key < 0x80 + if (simplify && (modifiers & MOD_MASK_ALT) && key < 0x80 && !enc_dbcs) // avoid creating a lead byte { key |= 0x80; modifiers &= ~MOD_MASK_ALT; /* remove the META modifier */ + if (did_simplify != NULL) + *did_simplify = TRUE; } *modp = modifiers; diff --git a/src/option.c b/src/option.c --- a/src/option.c +++ b/src/option.c @@ -4495,7 +4495,7 @@ find_key_option(char_u *arg_arg, int has { --arg; /* put arg at the '<' */ modifiers = 0; - key = find_special_key(&arg, &modifiers, TRUE, TRUE, FALSE); + key = find_special_key(&arg, &modifiers, TRUE, TRUE, FALSE, TRUE, NULL); if (modifiers) /* can't handle modifiers here */ key = 0; } diff --git a/src/proto/misc2.pro b/src/proto/misc2.pro --- a/src/proto/misc2.pro +++ b/src/proto/misc2.pro @@ -67,10 +67,10 @@ void append_ga_line(garray_T *gap); int simplify_key(int key, int *modifiers); int handle_x_keys(int key); char_u *get_special_key_name(int c, int modifiers); -int trans_special(char_u **srcp, char_u *dst, int keycode, int in_string); +int trans_special(char_u **srcp, char_u *dst, int keycode, int in_string, int simplify, int *did_simplify); int special_to_buf(int key, int modifiers, int keycode, char_u *dst); -int find_special_key(char_u **srcp, int *modp, int keycode, int keep_x_key, int in_string); -int extract_modifiers(int key, int *modp); +int find_special_key(char_u **srcp, int *modp, int keycode, int keep_x_key, int in_string, int simplify, int *did_simplify); +int extract_modifiers(int key, int *modp, int simplify, int *did_simplify); int find_special_key_in_table(int c); int get_special_key_code(char_u *name); char_u *get_key_name(int i); diff --git a/src/proto/term.pro b/src/proto/term.pro --- a/src/proto/term.pro +++ b/src/proto/term.pro @@ -67,7 +67,7 @@ int is_mouse_topline(win_T *wp); int check_termcode(int max_offset, char_u *buf, int bufsize, int *buflen); void term_get_fg_color(char_u *r, char_u *g, char_u *b); void term_get_bg_color(char_u *r, char_u *g, char_u *b); -char_u *replace_termcodes(char_u *from, char_u **bufp, int from_part, int do_lt, int special); +char_u *replace_termcodes(char_u *from, char_u **bufp, int flags, int *did_simplify); void show_termcodes(void); int show_one_termcode(char_u *name, char_u *code, int printit); char_u *translate_mapping(char_u *str); diff --git a/src/structs.h b/src/structs.h --- a/src/structs.h +++ b/src/structs.h @@ -1172,6 +1172,8 @@ struct mapblock char_u *m_orig_str; // rhs as entered by the user int m_keylen; // strlen(m_keys) int m_mode; // valid mode + int m_simplified; // m_keys was simplified, do not use this map + // if seenModifyOtherKeys is TRUE int m_noremap; // if non-zero no re-mapping for m_str char m_silent; // used, don't echo commands char m_nowait; // used diff --git a/src/term.c b/src/term.c --- a/src/term.c +++ b/src/term.c @@ -4845,6 +4845,7 @@ not_enough: else if ((arg[0] == 27 && argc == 3 && trail == '~') || (argc == 2 && trail == 'u')) { + seenModifyOtherKeys = TRUE; if (trail == 'u') key = arg[0]; else @@ -4853,13 +4854,20 @@ not_enough: modifiers = decode_modifiers(arg[1]); // Some keys already have Shift included, pass them as - // normal keys. + // normal keys. Not when Ctrl is also used, because + // and are different. if (modifiers == MOD_MASK_SHIFT && ((key >= '@' && key <= 'Z') || key == '^' || key == '_' || (key >= '{' && key <= '~'))) modifiers = 0; + // When used with Ctrl we always make a letter upper case, + // so that mapping and are the same. Typing + // also uses "H" but modifier is different. + if ((modifiers & MOD_MASK_CTRL) && ASCII_ISALPHA(key)) + key = TOUPPER_ASC(key); + // insert modifiers with KS_MODIFIER new_slen = modifiers2keycode(modifiers, &key, string); slen = csi_len; @@ -5340,18 +5348,26 @@ term_get_bg_color(char_u *r, char_u *g, * pointer to it is returned. If something fails *bufp is set to NULL and from * is returned. * - * CTRL-V characters are removed. When "from_part" is TRUE, a trailing CTRL-V - * is included, otherwise it is removed (for ":map xx ^V", maps xx to - * nothing). When 'cpoptions' does not contain 'B', a backslash can be used - * instead of a CTRL-V. + * CTRL-V characters are removed. When "flags" has REPTERM_FROM_PART, a + * trailing CTRL-V is included, otherwise it is removed (for ":map xx ^V", maps + * xx to nothing). When 'cpoptions' does not contain 'B', a backslash can be + * used instead of a CTRL-V. + * + * Flags: + * REPTERM_FROM_PART see above + * REPTERM_DO_LT also translate + * REPTERM_SPECIAL always accept notation + * REPTERM_NO_SIMPLIFY do not simplify to 0x08 and set 8th bit for + * + * "did_simplify" is set when some or code was simplified, unless + * it is NULL. */ char_u * replace_termcodes( char_u *from, char_u **bufp, - int from_part, - int do_lt, /* also translate */ - int special) /* always accept notation */ + int flags, + int *did_simplify) { int i; int slen; @@ -5364,7 +5380,8 @@ replace_termcodes( char_u *result; /* buffer for resulting string */ do_backslash = (vim_strchr(p_cpo, CPO_BSLASH) == NULL); - do_special = (vim_strchr(p_cpo, CPO_SPECI) == NULL) || special; + do_special = (vim_strchr(p_cpo, CPO_SPECI) == NULL) + || (flags & REPTERM_SPECIAL); do_key_code = (vim_strchr(p_cpo, CPO_KEYCODE) == NULL); /* @@ -5383,7 +5400,7 @@ replace_termcodes( /* * Check for #n at start only: function key n */ - if (from_part && src[0] == '#' && VIM_ISDIGIT(src[1])) /* function key */ + if ((flags & REPTERM_FROM_PART) && src[0] == '#' && VIM_ISDIGIT(src[1])) { result[dlen++] = K_SPECIAL; result[dlen++] = 'k'; @@ -5403,7 +5420,8 @@ replace_termcodes( * If 'cpoptions' does not contain '<', check for special key codes, * like "" */ - if (do_special && (do_lt || STRNCMP(src, "", 4) != 0)) + if (do_special && ((flags & REPTERM_DO_LT) + || STRNCMP(src, "", 4) != 0)) { #ifdef FEAT_EVAL /* @@ -5429,7 +5447,8 @@ replace_termcodes( } #endif - slen = trans_special(&src, result + dlen, TRUE, FALSE); + slen = trans_special(&src, result + dlen, TRUE, FALSE, + (flags & REPTERM_NO_SIMPLIFY) == 0, did_simplify); if (slen) { dlen += slen; @@ -5509,7 +5528,7 @@ replace_termcodes( ++src; /* skip CTRL-V or backslash */ if (*src == NUL) { - if (from_part) + if (flags & REPTERM_FROM_PART) result[dlen++] = key; break; } diff --git a/src/terminal.c b/src/terminal.c --- a/src/terminal.c +++ b/src/terminal.c @@ -772,7 +772,8 @@ ex_terminal(exarg_T *eap) p = skiptowhite(cmd); *p = NUL; - keys = replace_termcodes(ep + 1, &buf, TRUE, TRUE, TRUE); + keys = replace_termcodes(ep + 1, &buf, + REPTERM_FROM_PART | REPTERM_DO_LT | REPTERM_SPECIAL, NULL); opt.jo_set2 |= JO2_EOF_CHARS; opt.jo_eof_chars = vim_strsave(keys); vim_free(buf); @@ -1372,7 +1373,12 @@ term_convert_key(term_T *term, int c, ch } // add modifiers for the typed key - mod |= mod_mask; + if (mod_mask & MOD_MASK_SHIFT) + mod |= VTERM_MOD_SHIFT; + if (mod_mask & MOD_MASK_CTRL) + mod |= VTERM_MOD_CTRL; + if (mod_mask & (MOD_MASK_ALT | MOD_MASK_META)) + mod |= VTERM_MOD_ALT; /* * Convert special keys to vterm keys: diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim --- a/src/testdir/test_termcodes.vim +++ b/src/testdir/test_termcodes.vim @@ -862,7 +862,7 @@ endfunc " The mode doesn't need to be enabled, the codes are always detected. func RunTest_modifyOtherKeys(func) new - set timeoutlen=20 + set timeoutlen=10 " Shift-X is send as 'X' with the shift modifier call feedkeys('a' .. a:func('X', 2) .. "\", 'Lx!') @@ -902,11 +902,8 @@ func RunTest_modifyOtherKeys(func) set timeoutlen& endfunc -func Test_modifyOtherKeys_CSI27() +func Test_modifyOtherKeys_basic() call RunTest_modifyOtherKeys(function('GetEscCodeCSI27')) -endfunc - -func Test_modifyOtherKeys_CSIu() call RunTest_modifyOtherKeys(function('GetEscCodeCSIu')) endfunc @@ -928,7 +925,7 @@ endfunc func RunTest_mapping_works_with_shift(func) new - set timeoutlen=20 + set timeoutlen=10 call RunTest_mapping_shift('@', a:func) call RunTest_mapping_shift('A', a:func) @@ -944,7 +941,88 @@ func RunTest_mapping_works_with_shift(fu set timeoutlen& endfunc -func Test_mapping_works_with_shift() +func Test_mapping_works_with_shift_plain() call RunTest_mapping_works_with_shift(function('GetEscCodeCSI27')) call RunTest_mapping_works_with_shift(function('GetEscCodeCSIu')) endfunc + +func RunTest_mapping_mods(map, key, func, code) + call setline(1, '') + exe 'inoremap ' .. a:map .. ' xyz' + call feedkeys('a' .. a:func(a:key, a:code) .. "\", 'Lx!') + call assert_equal("xyz", getline(1)) + exe 'iunmap ' .. a:map +endfunc + +func RunTest_mapping_works_with_mods(func, mods, code) + new + set timeoutlen=10 + + if a:mods !~ 'S' + " Shift by itself has no effect + call RunTest_mapping_mods('<' .. a:mods .. '-@>', '@', a:func, a:code) + endif + call RunTest_mapping_mods('<' .. a:mods .. '-A>', 'A', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-Z>', 'Z', a:func, a:code) + if a:mods !~ 'S' + " with Shift code is always upper case + call RunTest_mapping_mods('<' .. a:mods .. '-a>', 'a', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-z>', 'z', a:func, a:code) + endif + if a:mods != 'A' + " with Alt code is not in upper case + call RunTest_mapping_mods('<' .. a:mods .. '-a>', 'A', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-z>', 'Z', a:func, a:code) + endif + call RunTest_mapping_mods('<' .. a:mods .. '-á>', 'á', a:func, a:code) + if a:mods !~ 'S' + " Shift by itself has no effect + call RunTest_mapping_mods('<' .. a:mods .. '-^>', '^', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-_>', '_', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-{>', '{', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-\|>', '|', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-}>', '}', a:func, a:code) + call RunTest_mapping_mods('<' .. a:mods .. '-~>', '~', a:func, a:code) + endif + + bwipe! + set timeoutlen& +endfunc + +func Test_mapping_works_with_shift() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'S', 2) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'S', 2) +endfunc + +func Test_mapping_works_with_ctrl() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C', 5) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C', 5) +endfunc + +func Test_mapping_works_with_shift_ctrl() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-S', 6) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-S', 6) +endfunc + +" Below we also test the "u" code with Alt, This works, but libvterm would not +" send the Alt key like this but by prefixing an Esc. + +func Test_mapping_works_with_alt() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'A', 3) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'A', 3) +endfunc + +func Test_mapping_works_with_shift_alt() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'S-A', 4) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'S-A', 4) +endfunc + +func Test_mapping_works_with_ctrl_alt() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-A', 7) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-A', 7) +endfunc + +func Test_mapping_works_with_shift_ctrl_alt() + call RunTest_mapping_works_with_mods(function('GetEscCodeCSI27'), 'C-S-A', 8) + call RunTest_mapping_works_with_mods(function('GetEscCodeCSIu'), 'C-S-A', 8) +endfunc diff --git a/src/usercmd.c b/src/usercmd.c --- a/src/usercmd.c +++ b/src/usercmd.c @@ -868,7 +868,7 @@ uc_add_command( char_u *rep_buf = NULL; garray_T *gap; - replace_termcodes(rep, &rep_buf, FALSE, FALSE, FALSE); + replace_termcodes(rep, &rep_buf, 0, NULL); if (rep_buf == NULL) { // Can't replace termcodes - try using the string as is diff --git a/src/version.c b/src/version.c --- a/src/version.c +++ b/src/version.c @@ -754,6 +754,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ /**/ + 2145, +/**/ 2144, /**/ 2143, diff --git a/src/vim.h b/src/vim.h --- a/src/vim.h +++ b/src/vim.h @@ -2633,4 +2633,10 @@ long elapsed(DWORD start_tick); #define CLIP_ZINDEX 32000 +// Flags for replace_termcodes() +#define REPTERM_FROM_PART 1 +#define REPTERM_DO_LT 2 +#define REPTERM_SPECIAL 4 +#define REPTERM_NO_SIMPLIFY 8 + #endif // VIM__H