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
! // <S-F3>, 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 <S-F3>, 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}:
! * "<Esc>[" 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}:
! * "<Esc>[" or CSI followed by [0-9>?].
! * Also for function keys without a modifier:
! * "<Esc>[" 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) .. "\<Esc>", '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 = [
+ \ ['<Up>', 'A', 0],
+ \ ['<S-Up>', 'A', 2],
+ \ ['<C-Up>', 'A', 5],
+ \ ['<C-S-Up>', 'A', 6],
+ \
+ \ ['<Down>', 'B', 0],
+ \ ['<S-Down>', 'B', 2],
+ \ ['<C-Down>', 'B', 5],
+ \ ['<C-S-Down>', 'B', 6],
+ \
+ \ ['<Right>', 'C', 0],
+ \ ['<S-Right>', 'C', 2],
+ \ ['<C-Right>', 'C', 5],
+ \ ['<C-S-Right>', 'C', 6],
+ \
+ \ ['<Left>', 'D', 0],
+ \ ['<S-Left>', 'D', 2],
+ \ ['<C-Left>', 'D', 5],
+ \ ['<C-S-Left>', 'D', 6],
+ \
+ \ ['<End>', 'F', 0],
+ \ ['<S-End>', 'F', 2],
+ \ ['<C-End>', 'F', 5],
+ \ ['<C-S-End>', 'F', 6],
+ \
+ \ ['<Home>', 'H', 0],
+ \ ['<S-Home>', 'H', 2],
+ \ ['<C-Home>', 'H', 5],
+ \ ['<C-S-Home>', 'H', 6],
+ \
+ \ ['<F1>', 'P', 0],
+ \ ['<S-F1>', 'P', 2],
+ \ ['<C-F1>', 'P', 5],
+ \ ['<C-S-F1>', 'P', 6],
+ \
+ \ ['<F2>', 'Q', 0],
+ \ ['<S-F2>', 'Q', 2],
+ \ ['<C-F2>', 'Q', 5],
+ \ ['<C-S-F2>', 'Q', 6],
+ \
+ \ ['<F3>', 'R', 0],
+ \ ['<S-F3>', 'R', 2],
+ \ ['<C-F3>', 'R', 5],
+ \ ['<C-S-F3>', 'R', 6],
+ \
+ \ ['<F4>', 'S', 0],
+ \ ['<S-F4>', 'S', 2],
+ \ ['<C-F4>', 'S', 5],
+ \ ['<C-S-F4>', '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 "\<Esc>[" .. 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 "\<Esc>[" .. a:key
+ endif
+
+ let mod = printf("%d", a:modifier)
+ return "\<Esc>[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 -- Br...@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 ///