To: vim_dev@googlegroups.com Subject: Patch 9.0.0985 Fcc: outbox From: Bram Moolenaar Mime-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ------------ Patch 9.0.0985 Problem: When using kitty keyboard protocol function keys may not work. (Kovid Goyal) Solution: Recognize CSI ending in [ABCDEFHPQRS] also when the termcap entries are not specified. (closes #11648) Files: src/term.c, src/testdir/test_termcodes.vim, src/testdir/view_util.vim *** ../vim-9.0.0984/src/term.c 2022-12-01 12:29:39.972957384 +0000 --- src/term.c 2022-12-02 12:27:14.587092206 +0000 *************** *** 2048,2056 **** // things for this terminal if (!gui.in_use) return FAIL; ! #ifdef HAVE_TGETENT break; // don't try using external termcap ! #endif } #endif // FEAT_GUI } --- 2048,2056 ---- // things for this terminal if (!gui.in_use) return FAIL; ! # ifdef HAVE_TGETENT break; // don't try using external termcap ! # endif } #endif // FEAT_GUI } *************** *** 5088,5093 **** --- 5088,5129 ---- } /* + * Shared between handle_key_with_modifier() and handle_csi_function_key(). + */ + static int + put_key_modifiers_in_typebuf( + int key_arg, + int modifiers_arg, + int csi_len, + int offset, + char_u *buf, + int bufsize, + int *buflen) + { + int key = key_arg; + int modifiers = modifiers_arg; + + // Some keys need adjustment when the Ctrl modifier is used. + key = may_adjust_key_for_ctrl(modifiers, key); + + // May remove the shift modifier if it's already included in the key. + modifiers = may_remove_shift_modifier(modifiers, key); + + // Produce modifiers with K_SPECIAL KS_MODIFIER {mod} + char_u string[MAX_KEY_CODE_LEN + 1]; + int new_slen = modifiers2keycode(modifiers, &key, string); + + // Add the bytes for the key. + new_slen += add_key_to_buf(key, string + new_slen); + + string[new_slen] = NUL; + if (put_string_in_typebuf(offset, csi_len, string, new_slen, + buf, bufsize, buflen) == FAIL) + return -1; + return new_slen - csi_len + offset; + } + + /* * Handle a sequence with key and modifier, one of: * {lead}27;{modifier};{key}~ * {lead}{key};{modifier}u *************** *** 5103,5112 **** int bufsize, int *buflen) { - int key; - int modifiers; - char_u string[MAX_KEY_CODE_LEN + 1]; - // Only set seenModifyOtherKeys for the "{lead}27;" code to avoid setting // it for terminals using the kitty keyboard protocol. Xterm sends // the form ending in "u" when the formatOtherKeys resource is set. We do --- 5139,5144 ---- *************** *** 5123,5153 **** || kitty_protocol_state == KKPS_OFF || kitty_protocol_state == KKPS_AFTER_T_KE) && term_props[TPR_KITTY].tpr_status != TPR_YES) seenModifyOtherKeys = TRUE; ! if (trail == 'u') ! key = arg[0]; ! else ! key = arg[2]; ! ! modifiers = decode_modifiers(arg[1]); ! ! // Some keys need adjustment when the Ctrl modifier is used. ! key = may_adjust_key_for_ctrl(modifiers, key); ! ! // May remove the shift modifier if it's already included in the key. ! modifiers = may_remove_shift_modifier(modifiers, key); ! ! // insert modifiers with KS_MODIFIER ! int new_slen = modifiers2keycode(modifiers, &key, string); ! ! // add the bytes for the key ! new_slen += add_key_to_buf(key, string + new_slen); ! ! if (put_string_in_typebuf(offset, csi_len, string, new_slen, ! buf, bufsize, buflen) == FAIL) ! return -1; ! return new_slen - csi_len + offset; } /* --- 5155,5169 ---- || kitty_protocol_state == KKPS_OFF || kitty_protocol_state == KKPS_AFTER_T_KE) && term_props[TPR_KITTY].tpr_status != TPR_YES) + { + ch_log(NULL, "setting seenModifyOtherKeys to TRUE"); seenModifyOtherKeys = TRUE; + } ! int key = trail == 'u' ? arg[0] : arg[2]; ! int modifiers = decode_modifiers(arg[1]); ! return put_key_modifiers_in_typebuf(key, modifiers, ! csi_len, offset, buf, bufsize, buflen); } /* *************** *** 5186,5191 **** --- 5202,5252 ---- } /* + * CSI function key without or with modifiers: + * {lead}[ABCDEFHPQRS] + * {lead}1;{modifier}[ABCDEFHPQRS] + * Returns zero when nog recognized, a positive number when recognized. + */ + static int + handle_csi_function_key( + int argc, + int *arg, + int trail, + int csi_len, + char_u *key_name, + int offset, + char_u *buf, + int bufsize, + int *buflen) + { + key_name[0] = 'k'; + switch (trail) + { + case 'A': key_name[1] = 'u'; break; // K_UP + case 'B': key_name[1] = 'd'; break; // K_DOWN + case 'C': key_name[1] = 'r'; break; // K_RIGHT + case 'D': key_name[1] = 'l'; break; // K_LEFT + + // case 'E': keypad BEGIN - not supported + case 'F': key_name[0] = '@'; key_name[1] = '7'; break; // K_END + case 'H': key_name[1] = 'h'; break; // K_HOME + + case 'P': key_name[1] = '1'; break; // K_F1 + case 'Q': key_name[1] = '2'; break; // K_F2 + case 'R': key_name[1] = '3'; break; // K_F3 + case 'S': key_name[1] = '4'; break; // K_F4 + + default: return 0; // not recognized + } + + int key = TERMCAP2KEY(key_name[0], key_name[1]); + int modifiers = argc == 2 ? decode_modifiers(arg[1]) : 0; + put_key_modifiers_in_typebuf(key, modifiers, + csi_len, offset, buf, bufsize, buflen); + return csi_len; + } + + /* * Handle a CSI escape sequence. * - Xterm version string. * *************** *** 5197,5206 **** * * - window position reply: {lead}3;{x};{y}t * ! * - key with modifiers when modifyOtherKeys is enabled: * {lead}27;{modifier};{key}~ * {lead}{key};{modifier}u * * Return 0 for no match, -1 for partial match, > 0 for full match. */ static int --- 5258,5272 ---- * * - window position reply: {lead}3;{x};{y}t * ! * - key with modifiers when modifyOtherKeys is enabled or the Kitty keyboard ! * protocol is used: * {lead}27;{modifier};{key}~ * {lead}{key};{modifier}u * + * - function key with or without modifiers: + * {lead}[ABCDEFHPQRS] + * {lead}1;{modifier}[ABCDEFHPQRS] + * * Return 0 for no match, -1 for partial match, > 0 for full match. */ static int *************** *** 5218,5224 **** int first = -1; // optional char right after {lead} int trail; // char that ends CSI sequence int arg[3] = {-1, -1, -1}; // argument numbers ! int argc; // number of arguments char_u *ap = argp; int csi_len; --- 5284,5290 ---- int first = -1; // optional char right after {lead} int trail; // char that ends CSI sequence int arg[3] = {-1, -1, -1}; // argument numbers ! int argc = 0; // number of arguments char_u *ap = argp; int csi_len; *************** *** 5226,5267 **** if (!VIM_ISDIGIT(*ap)) first = *ap++; ! // Find up to three argument numbers. ! for (argc = 0; argc < 3; ) { ! if (ap >= tp + len) ! return -1; ! if (*ap == ';') ! arg[argc++] = -1; // omitted number ! else if (VIM_ISDIGIT(*ap)) { ! arg[argc] = 0; ! for (;;) { ! if (ap >= tp + len) ! return -1; ! if (!VIM_ISDIGIT(*ap)) ! break; ! arg[argc] = arg[argc] * 10 + (*ap - '0'); ! ++ap; } ! ++argc; } ! if (*ap == ';') ++ap; ! else ! break; } - // mrxvt has been reported to have "+" in the version. Assume - // the escape sequence ends with a letter or one of "{|}~". - while (ap < tp + len - && !(*ap >= '{' && *ap <= '~') - && !ASCII_ISALPHA(*ap)) - ++ap; - if (ap >= tp + len) - return -1; - trail = *ap; csi_len = (int)(ap - tp) + 1; // Response to XTQMODKEYS: "CSI > 4 ; Pv m" where Pv indicates the --- 5292,5345 ---- if (!VIM_ISDIGIT(*ap)) first = *ap++; ! if (ASCII_ISUPPER(first)) { ! // If "first" is in [ABCDEFHPQRS] then it is actually the "trail" and ! // no argument follows. ! trail = first; ! first = -1; ! --ap; ! } ! else ! { ! // Find up to three argument numbers. ! for (argc = 0; argc < 3; ) { ! if (ap >= tp + len) ! return -1; ! if (*ap == ';') ! arg[argc++] = -1; // omitted number ! else if (VIM_ISDIGIT(*ap)) { ! arg[argc] = 0; ! for (;;) ! { ! if (ap >= tp + len) ! return -1; ! if (!VIM_ISDIGIT(*ap)) ! break; ! arg[argc] = arg[argc] * 10 + (*ap - '0'); ! ++ap; ! } ! ++argc; } ! if (*ap == ';') ! ++ap; ! else ! break; } ! ! // mrxvt has been reported to have "+" in the version. Assume ! // the escape sequence ends with a letter or one of "{|}~". ! while (ap < tp + len ! && !(*ap >= '{' && *ap <= '~') ! && !ASCII_ISALPHA(*ap)) ++ap; ! if (ap >= tp + len) ! return -1; ! trail = *ap; } csi_len = (int)(ap - tp) + 1; // Response to XTQMODKEYS: "CSI > 4 ; Pv m" where Pv indicates the *************** *** 5276,5286 **** *slen = csi_len; } ! // Cursor position report: Eat it when there are 2 arguments ! // and it ends in 'R'. Also when u7_status is not "sent", it ! // may be from a previous Vim that just exited. But not for ! // , it sends something similar, check for row and column ! // to make sense. else if (first == -1 && argc == 2 && trail == 'R') { handle_u7_response(arg, tp, csi_len); --- 5354,5375 ---- *slen = csi_len; } ! // Function key starting with CSI: ! // {lead}[ABCDEFHPQRS] ! // {lead}1;{modifier}[ABCDEFHPQRS] ! else if (first == -1 && ASCII_ISUPPER(trail) ! && (argc == 0 || (argc == 2 && arg[0] == 1))) ! { ! int res = handle_csi_function_key(argc, arg, trail, ! csi_len, key_name, offset, buf, bufsize, buflen); ! return res <= 0 ? res : len + res; ! } ! ! // Cursor position report: {lead}{row};{col}R ! // Eat it when there are 2 arguments and it ends in 'R'. ! // Also when u7_status is not "sent", it may be from a previous Vim that ! // just exited. But not for , it sends something similar, check for ! // row and column to make sense. else if (first == -1 && argc == 2 && trail == 'R') { handle_u7_response(arg, tp, csi_len); *************** *** 5346,5351 **** --- 5435,5441 ---- // Reset seenModifyOtherKeys just in case some key combination has // been seen that set it before we get the status response. + ch_log(NULL, "setting seenModifyOtherKeys to FALSE"); seenModifyOtherKeys = FALSE; } *************** *** 5916,5922 **** /* * Check for responses from the terminal starting with {lead}: ! * "[" or CSI followed by [0-9>?] * * - Xterm version string: {lead}>{x};{vers};{y}c * Also eat other possible responses to t_RV, rxvt returns --- 6006,6014 ---- /* * Check for responses from the terminal starting with {lead}: ! * "[" or CSI followed by [0-9>?]. ! * Also for function keys without a modifier: ! * "[" or CSI followed by [ABCDEFHPQRS]. * * - Xterm version string: {lead}>{x};{vers};{y}c * Also eat other possible responses to t_RV, rxvt returns *************** *** 5935,5942 **** * {lead}{key};{modifier}u */ if (((tp[0] == ESC && len >= 3 && tp[1] == '[') ! || (tp[0] == CSI && len >= 2)) ! && (VIM_ISDIGIT(*argp) || *argp == '>' || *argp == '?')) { int resp = handle_csi(tp, len, argp, offset, buf, bufsize, buflen, key_name, &slen); --- 6027,6035 ---- * {lead}{key};{modifier}u */ if (((tp[0] == ESC && len >= 3 && tp[1] == '[') ! || (tp[0] == CSI && len >= 2)) ! && vim_strchr((char_u *)"0123456789>?ABCDEFHPQRS", ! *argp) != NULL) { int resp = handle_csi(tp, len, argp, offset, buf, bufsize, buflen, key_name, &slen); *************** *** 6424,6430 **** slen = trans_special(&src, result + dlen, FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY), TRUE, did_simplify); ! if (slen) { dlen += slen; continue; --- 6517,6523 ---- slen = trans_special(&src, result + dlen, FSK_KEYCODE | ((flags & REPTERM_NO_SIMPLIFY) ? 0 : FSK_SIMPLIFY), TRUE, did_simplify); ! if (slen > 0) { dlen += slen; continue; *** ../vim-9.0.0984/src/testdir/test_termcodes.vim 2022-11-29 18:32:29.309191253 +0000 --- src/testdir/test_termcodes.vim 2022-12-02 12:24:24.743116236 +0000 *************** *** 2483,2488 **** --- 2483,2562 ---- set timeoutlen& endfunc + func RunTest_mapping_funckey(map, func, key, 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), 'mapping ' .. a:map) + exe 'iunmap ' .. a:map + endfunc + + func Test_mapping_kitty_function_keys() + new + set timeoutlen=10 + + " Function keys made with CSI and ending in [ABCDEFHPQRS]. + " 'E' is keypad BEGIN, not supported + let maps = [ + \ ['', 'A', 0], + \ ['', 'A', 2], + \ ['', 'A', 5], + \ ['', 'A', 6], + \ + \ ['', 'B', 0], + \ ['', 'B', 2], + \ ['', 'B', 5], + \ ['', 'B', 6], + \ + \ ['', 'C', 0], + \ ['', 'C', 2], + \ ['', 'C', 5], + \ ['', 'C', 6], + \ + \ ['', 'D', 0], + \ ['', 'D', 2], + \ ['', 'D', 5], + \ ['', 'D', 6], + \ + \ ['', 'F', 0], + \ ['', 'F', 2], + \ ['', 'F', 5], + \ ['', 'F', 6], + \ + \ ['', 'H', 0], + \ ['', 'H', 2], + \ ['', 'H', 5], + \ ['', 'H', 6], + \ + \ ['', 'P', 0], + \ ['', 'P', 2], + \ ['', 'P', 5], + \ ['', 'P', 6], + \ + \ ['', 'Q', 0], + \ ['', 'Q', 2], + \ ['', 'Q', 5], + \ ['', 'Q', 6], + \ + \ ['', 'R', 0], + \ ['', 'R', 2], + \ ['', 'R', 5], + \ ['', 'R', 6], + \ + \ ['', 'S', 0], + \ ['', 'S', 2], + \ ['', 'S', 5], + \ ['', 'S', 6], + \ ] + + for map in maps + call RunTest_mapping_funckey(map[0], function('GetEscCodeFunckey'), map[1], map[2]) + endfor + + bwipe! + set timeoutlen& + endfunc + func Test_insert_literal() set timeoutlen=10 *** ../vim-9.0.0984/src/testdir/view_util.vim 2022-11-27 13:51:18.850338772 +0000 --- src/testdir/view_util.vim 2022-12-02 10:20:06.924138045 +0000 *************** *** 95,100 **** --- 95,112 ---- return "\[" .. key .. ';' .. mod .. 'u' endfunc + " Return the kitty keyboard protocol encoding for a function key: + " CSI {key} + " CSS 1;{modifier} {key} + func GetEscCodeFunckey(key, modifier) + if a:modifier == 0 + return "\[" .. a:key + endif + + let mod = printf("%d", a:modifier) + return "\[1;".. mod .. a:key + endfunc + " Return the kitty keyboard protocol encoding for "key" without a modifier. " Used for the Escape key. func GetEscCodeCSIuWithoutModifier(key) *** ../vim-9.0.0984/src/version.c 2022-12-01 19:40:51.796701665 +0000 --- src/version.c 2022-12-01 20:30:14.235518453 +0000 *************** *** 697,698 **** --- 697,700 ---- { /* Add new patch number below this line */ + /**/ + 985, /**/ -- hundred-and-one symptoms of being an internet addict: 200. You really believe in the concept of a "paperless" office. /// Bram Moolenaar -- Bram@Moolenaar.net -- http://www.Moolenaar.net \\\ /// \\\ \\\ sponsor Vim, vote for features -- http://www.Vim.org/sponsor/ /// \\\ help me help AIDS victims -- http://ICCF-Holland.org ///