patch 9.1.1736: Cannot detect <F3> using kitty protocol
Commit:
https://github.com/vim/vim/commit/58c5a778707398fe4662530e0a06f5402bdedbf6
Author: Christian Brabandt <[email protected]>
Date: Sat Sep 6 10:11:39 2025 +0200
patch 9.1.1736: Cannot detect <F3> using kitty protocol
Problem: Cannot detect <F3> using kitty protocol
Solution: Handle and detect Kitty keys when using the trailing "~" byte
fixes: #18100
closes: #18126
Signed-off-by: Christian Brabandt <[email protected]>
diff --git a/src/term.c b/src/term.c
index d1917203d..2de1508f1 100644
--- a/src/term.c
+++ b/src/term.c
@@ -5293,6 +5293,37 @@ put_key_modifiers_in_typebuf(
return new_slen - csi_len + offset;
}
+/*
+ * Parse the number from a CSI numbered sequence for an F1-F12 key:
+ * ESC [ {number} ~
+ * Returns the key
+ */
+ static int
+parse_csi_f_keys(int arg)
+{
+ char_u key_name[2] = "";
+
+ switch (arg)
+ {
+ case 11: key_name[0] = 'k'; key_name[1] = '1'; break; // K_F1
+ case 12: key_name[0] = 'k'; key_name[1] = '2'; break; // K_F2
+ case 13: key_name[0] = 'k'; key_name[1] = '3'; break; // K_F3
+ case 14: key_name[0] = 'k'; key_name[1] = '4'; break; // K_F4
+ case 15: key_name[0] = 'k'; key_name[1] = '5'; break; // K_F5
+ case 17: key_name[0] = 'k'; key_name[1] = '6'; break; // K_F6
+ case 18: key_name[0] = 'k'; key_name[1] = '7'; break; // K_F7
+ case 19: key_name[0] = 'k'; key_name[1] = '8'; break; // K_F8
+ case 20: key_name[0] = 'k'; key_name[1] = '9'; break; // K_F9
+ case 21: key_name[0] = 'F'; key_name[1] = ';'; break; // K_F10
+ case 23: key_name[0] = 'F'; key_name[1] = '1'; break; // K_F11
+ case 24: key_name[0] = 'F'; key_name[1] = '2'; break; // K_F12
+ }
+ if (key_name[0])
+ return TERMCAP2KEY(key_name[0], key_name[1]);
+ // shouldn't happen
+ return arg;
+}
+
/*
* Handle a sequence with key and modifier, one of:
* {lead}27;{modifier};{key}~
@@ -5302,12 +5333,12 @@ put_key_modifiers_in_typebuf(
static int
handle_key_with_modifier(
int *arg,
- int trail,
int csi_len,
int offset,
char_u *buf,
int bufsize,
- int *buflen)
+ int *buflen,
+ int iskitty)
{
// Only set seenModifyOtherKeys for the "{lead}27;" code to avoid setting
// it for terminals using the kitty keyboard protocol. Xterm sends
@@ -5320,7 +5351,7 @@ handle_key_with_modifier(
//
// Do not set seenModifyOtherKeys for kitty, it does send some sequences
// like this but does not have the modifyOtherKeys feature.
- if (trail != 'u'
+ if (!iskitty
&& (kitty_protocol_state == KKPS_INITIAL
|| kitty_protocol_state == KKPS_OFF
|| kitty_protocol_state == KKPS_AFTER_T_TE)
@@ -5332,7 +5363,7 @@ handle_key_with_modifier(
seenModifyOtherKeys = TRUE;
}
- int key = trail == 'u' ? arg[0] : arg[2];
+ int key = iskitty ? arg[0] : arg[2];
int modifiers = decode_modifiers(arg[1]);
// Some terminals do not apply the Shift modifier to the key. To make
@@ -5345,6 +5376,9 @@ handle_key_with_modifier(
if (key == ESC)
key = K_ESC;
+ else if (arg[0] >= 11 && arg[0] <= 24)
+ key = parse_csi_f_keys(arg[0]);
+
return put_key_modifiers_in_typebuf(key, modifiers,
csi_len, offset, buf, bufsize, buflen);
}
@@ -5352,6 +5386,7 @@ handle_key_with_modifier(
/*
* Handle a sequence with key without a modifier:
* {lead}{key}u
+ * {lead}{key}~
* Returns the difference in length.
*/
static int
@@ -5375,6 +5410,14 @@ handle_key_without_modifier(
string[2] = KE_ESC;
new_slen = 3;
}
+ else if (arg[0] >= 11 && arg[0] <= 24)
+ {
+ int key = parse_csi_f_keys(arg[0]);
+ string[0] = K_SPECIAL;
+ string[1] = KEY2TERMCAP0(key);
+ string[2] = KEY2TERMCAP1(key);
+ new_slen = 3;
+ }
else
new_slen = add_key_to_buf(arg[0], string);
@@ -5672,19 +5715,22 @@ handle_csi(
// Key with modifier:
// {lead}27;{modifier};{key}~
// {lead}{key};{modifier}u
+ // {lead}{key};{modifier}~
// Even though we only handle four modifiers and the {modifier} value
// should be 16 or lower, we accept all modifier values to avoid the raw
// sequence to be passed through.
else if ((arg[0] == 27 && argc == 3 && trail == '~')
- || (argc == 2 && trail == 'u'))
+ || (argc == 2 && (trail == 'u' || trail == '~')))
{
- return len + handle_key_with_modifier(arg, trail,
- csi_len, offset, buf, bufsize, buflen);
+ int iskitty = argc == 2 && (trail == 'u' || trail == '~');
+ return len + handle_key_with_modifier(arg, csi_len, offset, buf,
+ bufsize, buflen, iskitty);
}
- // Key without modifier (Kitty sends this for Esc):
+ // Key without modifier (Kitty sends this for Esc or F3):
// {lead}{key}u
- else if (argc == 1 && trail == 'u')
+ // {lead}{key}~
+ else if (argc == 1 && (trail == 'u' || trail == '~'))
{
return len + handle_key_without_modifier(arg,
csi_len, offset, buf, bufsize, buflen);
diff --git a/src/testdir/keycode_check.json b/src/testdir/keycode_check.json
index b06e3c52b..6e3ce5263 100644
--- a/src/testdir/keycode_check.json
+++ b/src/testdir/keycode_check.json
@@ -1 +1,153 @@
-{"31kitty":{"Space":"20","modkeys":"","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b5b32373b313175","C-Space":"1b5b33323b3575","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b313175","S-Space":"20","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3175","protocol":"kitty","A-Space":"1b5b33323b313175","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"32libvterm":{"Space":"20","modkeys":"","version":"1b5b3e303b3130303b3063","C-Tab":"","A-Esc":"1b5b32373b3375","C-Space":"1b5b33323b3575","S-C-I":"1b5b3130353b3675","C-I":"1b5b3130353b3575","S-Tab":"1b5b393b3275","Tab":"09","resource":"","A-Tab":"1b5b393b3375","S-Space":"20","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3175","protocol":"kitty","A-Space":"1b5b33323b3375","S-Esc":"1b5b32373b3275","Esc":"1b5b323775"},"22libvterm":{"Space":"20","modkeys":"\u001b[>4;2m","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","kitty":"","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b5b32373b323b32377e","Esc":"1b"},"13kitty":{"Space":"20","modkeys":"","version":"1b5b3e313b343030303b323163","C-Tab":"","A-Esc":"1b1b","S-C-I":"1b5b3130353b3675","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"1b09","resource":"","C-Esc":"1b","kitty":"1b5b3f3075","protocol":"none","A-Space":"1b5b33323b313175","S-Esc":"1b","Esc":"1b"},"21xterm":{"Space":"20","modkeys":"\u001b[>4;2m","version":"1b5b3e34313b3337373b3063","C-Tab":"1b5b32373b353b397e","A-Esc":"1b5b32373b333b32377e","C-Space":"1b5b32373b353b33327e","S-C-I":"1b5b32373b363b37337e","C-I":"1b5b32373b353b3130357e","S-Tab":"1b5b5a","Tab":"09","resource":"=30","A-Tab":"1b5b32373b333b397e","S-Space":"1b5b32373b323b33327e","C-Esc":"1b5b32373b353b32377e","kitty":"","protocol":"mok2","A-Space":"1b5b32373b333b33327e","S-Esc":"1b5b32373b323b32377e","Esc":"1b"},"12libvterm":{"Space":"20","modkeys":"\u001b[>4;0m","version":"1b5b3e303b3130303b3063","C-Tab":"1b5b393b3575","A-Esc":"9b00","S-C-I":"1b5b5a","C-I":"09","S-Tab":"1b5b5a","Tab":"09","resource":"","A-Tab":"8900","S-Space":"1b5b33323b3275","C-Esc":"1b5b32373b3575","kitty":"1b5b3f3075","protocol":"none","A-Space":"a000","S-Esc":"1b5b32373b3275","Esc":"1b"},"11xterm":{"Space":"20","modkeys":"\u001b[>4;0m","version":"1b5b3e34313b3337373b3063","C-Tab":"09","A-Esc":"9b00","S-C-I":"09","C-I":"09","S-Tab":"1b5b5a","Tab":"09","S-Space":"20","A-Tab":"8900","resource":"","C-Esc":"1b","kitty":"","protocol":"none","A-Space":"a000","S-Esc":"1b","Esc":"1b"}}
+{
+ "31kitty": {
+ "Space": "20",
+ "modkeys": "",
+ "version": "1b5b3e313b343030303b323163",
+ "C-Tab": "",
+ "A-Esc": "1b5b32373b313175",
+ "C-Space": "1b5b33323b3575",
+ "S-C-I": "1b5b3130353b3675",
+ "C-I": "1b5b3130353b3575",
+ "S-Tab": "1b5b393b3275",
+ "Tab": "09",
+ "resource": "",
+ "A-Tab": "1b5b393b313175",
+ "S-Space": "20",
+ "C-Esc": "1b5b32373b3575",
+ "kitty": "1b5b3f3175",
+ "protocol": "kitty",
+ "A-Space": "1b5b33323b313175",
+ "S-Esc": "1b5b32373b3275",
+ "Esc": "1b5b323775",
+ "F3": "1b5b31337e"
+ },
+ "32libvterm": {
+ "Space": "20",
+ "modkeys": "",
+ "version": "1b5b3e303b3130303b3063",
+ "C-Tab": "",
+ "A-Esc": "1b5b32373b3375",
+ "C-Space": "1b5b33323b3575",
+ "S-C-I": "1b5b3130353b3675",
+ "C-I": "1b5b3130353b3575",
+ "S-Tab": "1b5b393b3275",
+ "Tab": "09",
+ "resource": "",
+ "A-Tab": "1b5b393b3375",
+ "S-Space": "20",
+ "C-Esc": "1b5b32373b3575",
+ "kitty": "1b5b3f3175",
+ "protocol": "kitty",
+ "A-Space": "1b5b33323b3375",
+ "S-Esc": "1b5b32373b3275",
+ "Esc": "1b5b323775",
+ "F3": "1b4f52"
+ },
+ "22libvterm": {
+ "Space": "20",
+ "modkeys": "\u001b[>4;2m",
+ "version": "1b5b3e303b3130303b3063",
+ "C-Tab": "1b5b32373b353b397e",
+ "A-Esc": "1b5b32373b333b32377e",
+ "C-Space": "1b5b32373b353b33327e",
+ "S-C-I": "1b5b32373b363b37337e",
+ "C-I": "1b5b32373b353b3130357e",
+ "S-Tab": "1b5b5a",
+ "Tab": "09",
+ "resource": "",
+ "A-Tab": "1b5b32373b333b397e",
+ "S-Space": "1b5b32373b323b33327e",
+ "C-Esc": "1b5b32373b353b32377e",
+ "kitty": "",
+ "protocol": "mok2",
+ "A-Space": "1b5b32373b333b33327e",
+ "S-Esc": "1b5b32373b323b32377e",
+ "Esc": "1b",
+ "F3": "1b4f52"
+ },
+ "13kitty": {
+ "Space": "20",
+ "modkeys": "",
+ "version": "1b5b3e313b343030303b323163",
+ "C-Tab": "",
+ "A-Esc": "1b1b",
+ "S-C-I": "1b5b3130353b3675",
+ "C-I": "09",
+ "S-Tab": "1b5b5a",
+ "Tab": "09",
+ "S-Space": "20",
+ "A-Tab": "1b09",
+ "resource": "",
+ "C-Esc": "1b",
+ "kitty": "1b5b3f3075",
+ "protocol": "none",
+ "A-Space": "1b5b33323b313175",
+ "S-Esc": "1b",
+ "Esc": "1b",
+ "F3": "1b4f52"
+ },
+ "21xterm": {
+ "Space": "20",
+ "modkeys": "\u001b[>4;2m",
+ "version": "1b5b3e34313b3337373b3063",
+ "C-Tab": "1b5b32373b353b397e",
+ "A-Esc": "1b5b32373b333b32377e",
+ "C-Space": "1b5b32373b353b33327e",
+ "S-C-I": "1b5b32373b363b37337e",
+ "C-I": "1b5b32373b353b3130357e",
+ "S-Tab": "1b5b5a",
+ "Tab": "09",
+ "resource": "=30",
+ "A-Tab": "1b5b32373b333b397e",
+ "S-Space": "1b5b32373b323b33327e",
+ "C-Esc": "1b5b32373b353b32377e",
+ "kitty": "",
+ "protocol": "mok2",
+ "A-Space": "1b5b32373b333b33327e",
+ "S-Esc": "1b5b32373b323b32377e",
+ "Esc": "1b",
+ "F3": "1b4f52"
+ },
+ "12libvterm": {
+ "Space": "20",
+ "modkeys": "\u001b[>4;0m",
+ "version": "1b5b3e303b3130303b3063",
+ "C-Tab": "1b5b393b3575",
+ "A-Esc": "9b00",
+ "S-C-I": "1b5b5a",
+ "C-I": "09",
+ "S-Tab": "1b5b5a",
+ "Tab": "09",
+ "resource": "",
+ "A-Tab": "8900",
+ "S-Space": "1b5b33323b3275",
+ "C-Esc": "1b5b32373b3575",
+ "kitty": "1b5b3f3075",
+ "protocol": "none",
+ "A-Space": "a000",
+ "S-Esc": "1b5b32373b3275",
+ "Esc": "1b",
+ "F3": "1b4f52"
+ },
+ "11xterm": {
+ "Space": "20",
+ "modkeys": "\u001b[>4;0m",
+ "version": "1b5b3e34313b3337373b3063",
+ "C-Tab": "09",
+ "A-Esc": "9b00",
+ "S-C-I": "09",
+ "C-I": "09",
+ "S-Tab": "1b5b5a",
+ "Tab": "09",
+ "S-Space": "20",
+ "A-Tab": "8900",
+ "resource": "",
+ "C-Esc": "1b",
+ "kitty": "",
+ "protocol": "none",
+ "A-Space": "a000",
+ "S-Esc": "1b",
+ "Esc": "1b",
+ "F3": "1b4f52"
+ }
+}
diff --git a/src/testdir/keycode_check.vim b/src/testdir/keycode_check.vim
index 8320341d2..33eb6ceeb 100644
--- a/src/testdir/keycode_check.vim
+++ b/src/testdir/keycode_check.vim
@@ -74,6 +74,7 @@ var key_entries = [
['Shift-Space', 'S-Space'],
['Ctrl-Space', 'C-Space'],
['Alt-Space', 'A-Space'],
+ ['F3', 'F3'],
]
# Given a terminal name and a item name, return the text to display.
@@ -464,7 +465,7 @@ while true
ActionReplace()
elseif action == 4
ActionClear()
- elseif action == 5
+ elseif action == 5 || action == 0
ActionQuit()
endif
endwhile
diff --git a/src/testdir/test_termcodes.vim b/src/testdir/test_termcodes.vim
index 99710fada..f37f618c8 100644
--- a/src/testdir/test_termcodes.vim
+++ b/src/testdir/test_termcodes.vim
@@ -2517,6 +2517,31 @@ func Test_mapping_kitty_function_keys()
set timeoutlen&
endfunc
+func Test_mapping_kitty_function_keys2()
+ " uses the CSI {number}; {modifiers} ~ form
+ new
+ set timeoutlen=10
+
+ let maps = [
+ \ ['<F3>', '13', 0],
+ \ ['<S-F3>', '13', 2],
+ \ ['<C-F3>', '13', 5],
+ \ ['<C-S-F3>', '13', 6],
+ \
+ \ ['<F5>', '15', 0],
+ \ ['<S-F5>', '15', 2],
+ \ ['<C-F5>', '15', 5],
+ \ ['<C-S-F5>', '15', 6],
+ \ ]
+
+ for map in maps
+ call RunTest_mapping_funckey(map[0], function('GetEscCodeFunckey2'),
map[1], map[2])
+ endfor
+
+ bwipe!
+ set timeoutlen&
+endfunc
+
func Test_insert_literal()
set timeoutlen=10
diff --git a/src/testdir/util/view_util.vim b/src/testdir/util/view_util.vim
index 161c8b20c..5c5bfa81d 100644
--- a/src/testdir/util/view_util.vim
+++ b/src/testdir/util/view_util.vim
@@ -108,6 +108,18 @@ func GetEscCodeFunckey(key, modifier)
return "\<Esc>[1;".. mod .. a:key
endfunc
+" Return the kitty keyboard protocol encoding for a function key:
+" CSI {number}; {modifiier} ~
+func GetEscCodeFunckey2(key, modifier)
+ let key = "\<Esc>[" .. a:key
+ if a:modifier == 0
+ return key .. "~"
+ endif
+
+ let mod = printf("%d", a:modifier)
+ return key .. ';' .. mod .. '~'
+endfunc
+
" Return the kitty keyboard protocol encoding for "key" without a modifier.
" Used for the Escape key.
func GetEscCodeCSIuWithoutModifier(key)
diff --git a/src/version.c b/src/version.c
index e6b9f9dfd..cf2f99101 100644
--- a/src/version.c
+++ b/src/version.c
@@ -724,6 +724,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 1736,
/**/
1735,
/**/
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
To view this discussion visit
https://groups.google.com/d/msgid/vim_dev/E1uuo4h-0079VT-Al%40256bit.org.