Merge pull request #32038 from gpanders/push-nsrttwwnsqvm

feat(terminal): add support for kitty keyboard protocol
This commit is contained in:
Gregory Anders
2025-01-16 18:33:22 -06:00
committed by GitHub
8 changed files with 497 additions and 100 deletions

View File

@@ -341,6 +341,9 @@ TERMINAL
• |jobstart()| gained the "term" flag.
• The |terminal| will send theme update notifications when 'background' is
changed and DEC mode 2031 is enabled.
• The |terminal| has experimental support for the Kitty keyboard protocol
(sometimes called "CSI u" key encoding). Only the "Disambiguate escape
codes" mode is currently supported.
TREESITTER

View File

@@ -1517,12 +1517,10 @@ int merge_modifiers(int c_arg, int *modifiers)
int c = c_arg;
if (*modifiers & MOD_MASK_CTRL) {
if ((c >= '`' && c <= 0x7f) || (c >= '@' && c <= '_')) {
if (!(State & MODE_TERMINAL) || !(c == 'I' || c == 'J' || c == 'M' || c == '[')) {
c &= 0x1f;
if (c == NUL) {
c = K_ZERO;
}
if (c >= '@' && c <= 0x7f) {
c &= 0x1f;
if (c == NUL) {
c = K_ZERO;
}
} else if (c == '6') {
// CTRL-6 is equivalent to CTRL-^
@@ -2058,6 +2056,12 @@ static bool at_ins_compl_key(void)
/// @return the length of the replaced bytes, 0 if nothing changed, -1 for error.
static int check_simplify_modifier(int max_offset)
{
// We want full modifiers in Terminal mode so that the key can be correctly
// encoded
if (State & MODE_TERMINAL) {
return 0;
}
for (int offset = 0; offset < max_offset; offset++) {
if (offset + 3 >= typebuf.tb_len) {
break;

View File

@@ -783,7 +783,12 @@ static int terminal_execute(VimState *state, int key)
{
TerminalState *s = (TerminalState *)state;
switch (key) {
// Check for certain control keys like Ctrl-C and Ctrl-\. We still send the
// unmerged key and modifiers to the terminal.
int tmp_mod_mask = mod_mask;
int mod_key = merge_modifiers(key, &tmp_mod_mask);
switch (mod_key) {
case K_LEFTMOUSE:
case K_LEFTDRAG:
case K_LEFTRELEASE:
@@ -841,13 +846,13 @@ static int terminal_execute(VimState *state, int key)
FALLTHROUGH;
default:
if (key == Ctrl_C) {
if (mod_key == Ctrl_C) {
// terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4
// But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand,
// so ensure that it is cleared.
got_int = false;
}
if (key == Ctrl_BSL && !s->got_bsl) {
if (mod_key == Ctrl_BSL && !s->got_bsl) {
s->got_bsl = true;
break;
}
@@ -1016,7 +1021,7 @@ static void terminal_send_key(Terminal *term, int c)
VTermKey key = convert_key(&c, &mod);
if (key) {
if (key != VTERM_KEY_NONE) {
vterm_keyboard_key(term->vt, key, mod);
} else if (!IS_SPECIAL(c)) {
vterm_keyboard_unichar(term->vt, (uint32_t)c, mod);

View File

@@ -10,54 +10,77 @@
# include "vterm/keyboard.c.generated.h"
#endif
static VTermKeyEncodingFlags vterm_state_get_key_encoding_flags(const VTermState *state)
{
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
const struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
assert(stack->size > 0);
return stack->items[stack->size - 1];
}
void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
{
// The shift modifier is never important for Unicode characters apart from Space
if (c != ' ') {
mod &= (unsigned)~VTERM_MOD_SHIFT;
bool passthru = false;
if (c == ' ') {
// Space is passed through only when there are no modifiers (including shift)
passthru = mod == VTERM_MOD_NONE;
} else {
// Otherwise pass through when there are no modifiers (ignoring shift)
passthru = (mod & (unsigned)~VTERM_MOD_SHIFT) == 0;
}
if (mod == 0) {
// Normal text - ignore just shift
if (passthru) {
char str[6];
int seqlen = fill_utf8((int)c, str);
vterm_push_output_bytes(vt, str, (size_t)seqlen);
return;
}
int needs_CSIu;
switch (c) {
// Special Ctrl- letters that can't be represented elsewise
case 'i':
case 'j':
case 'm':
case '[':
needs_CSIu = 1;
break;
// Ctrl-\ ] ^ _ don't need CSUu
case '\\':
case ']':
case '^':
case '_':
needs_CSIu = 0;
break;
// Shift-space needs CSIu
case ' ':
needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
break;
// All other characters needs CSIu except for letters a-z
default:
needs_CSIu = (c < 'a' || c > 'z');
}
VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state);
if (flags.disambiguate) {
// Always use unshifted codepoint
if (c >= 'A' && c <= 'Z') {
c += 'a' - 'A';
mod |= VTERM_MOD_SHIFT;
}
// ALT we can just prefix with ESC; anything else requires CSI u
if (needs_CSIu && (mod & (unsigned)~VTERM_MOD_ALT)) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1);
return;
}
if (mod & VTERM_MOD_CTRL) {
c &= 0x1f;
// Handle special cases. These are taken from kitty, but seem mostly
// consistent across terminals.
switch (c) {
case '2':
case ' ':
// Ctrl+2 is NUL to match Ctrl+@ (which is Shift+2 on US keyboards)
// Ctrl+Space is also NUL for some reason
c = 0x00;
break;
case '3':
case '4':
case '5':
case '6':
case '7':
// Ctrl+3 through Ctrl+7 are sequential starting from 0x1b. Importantly,
// this means that Ctrl+6 emits 0x1e (the same as Ctrl+^ on US keyboards)
c = 0x1b + c - '3';
break;
case '8':
// Ctrl+8 is DEL
c = 0x7f;
break;
case '/':
// Ctrl+/ is equivalent to Ctrl+_ for historic reasons
c = 0x1f;
break;
default:
if (c >= '@' && c <= 0x7f) {
c &= 0x1f;
}
break;
}
}
vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
@@ -75,7 +98,7 @@ typedef struct {
KEYCODE_CSINUM,
KEYCODE_KEYPAD,
} type;
char literal;
int literal;
int csinum;
} keycodes_s;
@@ -137,12 +160,35 @@ static keycodes_s keycodes_kp[] = {
{ KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
};
static keycodes_s keycodes_kp_csiu[] = {
{ KEYCODE_KEYPAD, 57399, 'p' }, // KP_0
{ KEYCODE_KEYPAD, 57400, 'q' }, // KP_1
{ KEYCODE_KEYPAD, 57401, 'r' }, // KP_2
{ KEYCODE_KEYPAD, 57402, 's' }, // KP_3
{ KEYCODE_KEYPAD, 57403, 't' }, // KP_4
{ KEYCODE_KEYPAD, 57404, 'u' }, // KP_5
{ KEYCODE_KEYPAD, 57405, 'v' }, // KP_6
{ KEYCODE_KEYPAD, 57406, 'w' }, // KP_7
{ KEYCODE_KEYPAD, 57407, 'x' }, // KP_8
{ KEYCODE_KEYPAD, 57408, 'y' }, // KP_9
{ KEYCODE_KEYPAD, 57411, 'j' }, // KP_MULT
{ KEYCODE_KEYPAD, 57413, 'k' }, // KP_PLUS
{ KEYCODE_KEYPAD, 57416, 'l' }, // KP_COMMA
{ KEYCODE_KEYPAD, 57412, 'm' }, // KP_MINUS
{ KEYCODE_KEYPAD, 57409, 'n' }, // KP_PERIOD
{ KEYCODE_KEYPAD, 57410, 'o' }, // KP_DIVIDE
{ KEYCODE_KEYPAD, 57414, 'M' }, // KP_ENTER
{ KEYCODE_KEYPAD, 57415, 'X' }, // KP_EQUAL
};
void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
{
if (key == VTERM_KEY_NONE) {
return;
}
VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state);
keycodes_s k;
if (key < VTERM_KEY_FUNCTION_0) {
if (key >= sizeof(keycodes)/sizeof(keycodes[0])) {
@@ -158,7 +204,12 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) {
return;
}
k = keycodes_kp[key - VTERM_KEY_KP_0];
if (flags.disambiguate) {
k = keycodes_kp_csiu[key - VTERM_KEY_KP_0];
} else {
k = keycodes_kp[key - VTERM_KEY_KP_0];
}
}
switch (k.type) {
@@ -167,7 +218,9 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
case KEYCODE_TAB:
// Shift-Tab is CSI Z but plain Tab is 0x09
if (mod == VTERM_MOD_SHIFT) {
if (flags.disambiguate) {
goto case_LITERAL;
} else if (mod == VTERM_MOD_SHIFT) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
} else if (mod & VTERM_MOD_SHIFT) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1);
@@ -187,7 +240,20 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
case KEYCODE_LITERAL:
case_LITERAL:
if (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) {
if (flags.disambiguate) {
switch (key) {
case VTERM_KEY_TAB:
case VTERM_KEY_ENTER:
case VTERM_KEY_BACKSPACE:
// If there are no mods then leave these as-is
flags.disambiguate = mod != VTERM_MOD_NONE;
break;
default:
break;
}
}
if (flags.disambiguate) {
vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1);
} else {
vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
@@ -229,7 +295,7 @@ void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
case KEYCODE_KEYPAD:
if (vt->state->mode.keypad) {
k.literal = (char)k.csinum;
k.literal = k.csinum;
goto case_SS3;
} else {
goto case_LITERAL;

View File

@@ -1,3 +1,4 @@
#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -116,6 +117,15 @@ static VTermState *vterm_state_new(VTerm *vt)
(*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data);
}
for (size_t i = 0; i < ARRAY_SIZE(state->key_encoding_stacks); i++) {
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[i];
for (size_t j = 0; j < ARRAY_SIZE(stack->items); j++) {
memset(&stack->items[j], 0, sizeof(stack->items[j]));
}
stack->size = 1;
}
return state;
}
@@ -916,6 +926,115 @@ static void request_version_string(VTermState *state)
VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
}
static void request_key_encoding_flags(VTermState *state)
{
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
int reply = 0;
assert(stack->size > 0);
VTermKeyEncodingFlags flags = stack->items[stack->size - 1];
if (flags.disambiguate) {
reply |= KEY_ENCODING_DISAMBIGUATE;
}
if (flags.report_events) {
reply |= KEY_ENCODING_REPORT_EVENTS;
}
if (flags.report_alternate) {
reply |= KEY_ENCODING_REPORT_ALTERNATE;
}
if (flags.report_all_keys) {
reply |= KEY_ENCODING_REPORT_ALL_KEYS;
}
if (flags.report_associated) {
reply |= KEY_ENCODING_REPORT_ASSOCIATED;
}
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%du", reply);
}
static void set_key_encoding_flags(VTermState *state, int arg, int mode)
{
// When mode is 3, bits set in arg reset the corresponding mode
bool set = mode != 3;
// When mode is 1, unset bits are reset
bool reset_unset = mode == 1;
struct VTermKeyEncodingFlags flags = { 0 };
if (arg & KEY_ENCODING_DISAMBIGUATE) {
flags.disambiguate = set;
} else if (reset_unset) {
flags.disambiguate = false;
}
if (arg & KEY_ENCODING_REPORT_EVENTS) {
flags.report_events = set;
} else if (reset_unset) {
flags.report_events = false;
}
if (arg & KEY_ENCODING_REPORT_ALTERNATE) {
flags.report_alternate = set;
} else if (reset_unset) {
flags.report_alternate = false;
}
if (arg & KEY_ENCODING_REPORT_ALL_KEYS) {
flags.report_all_keys = set;
} else if (reset_unset) {
flags.report_all_keys = false;
}
if (arg & KEY_ENCODING_REPORT_ASSOCIATED) {
flags.report_associated = set;
} else if (reset_unset) {
flags.report_associated = false;
}
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
assert(stack->size > 0);
stack->items[stack->size - 1] = flags;
}
static void push_key_encoding_flags(VTermState *state, int arg)
{
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
assert(stack->size <= ARRAY_SIZE(stack->items));
if (stack->size == ARRAY_SIZE(stack->items)) {
// Evict oldest entry when stack is full
for (size_t i = 0; i < ARRAY_SIZE(stack->items) - 1; i++) {
stack->items[i] = stack->items[i + 1];
}
} else {
stack->size++;
}
set_key_encoding_flags(state, arg, 1);
}
static void pop_key_encoding_flags(VTermState *state, int arg)
{
int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY;
struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen];
if (arg >= stack->size) {
stack->size = 1;
// If a pop request is received that empties the stack, all flags are reset.
memset(&stack->items[0], 0, sizeof(stack->items[0]));
} else if (arg > 0) {
stack->size -= arg;
}
}
static int on_csi(const char *leader, const long args[], int argcount, const char *intermed,
char command, void *user)
{
@@ -932,6 +1051,8 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha
switch (leader[0]) {
case '?':
case '>':
case '<':
case '=':
leader_byte = (int)leader[0];
break;
default:
@@ -1542,6 +1663,23 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha
break;
case LEADER('?', 0x75): // Kitty query
request_key_encoding_flags(state);
break;
case LEADER('>', 0x75): // Kitty push flags
push_key_encoding_flags(state, CSI_ARG_OR(args[0], 0));
break;
case LEADER('<', 0x75): // Kitty pop flags
pop_key_encoding_flags(state, CSI_ARG_OR(args[0], 1));
break;
case LEADER('=', 0x75): // Kitty set flags
val = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]);
set_key_encoding_flags(state, CSI_ARG_OR(args[0], 0), val);
break;
case INTERMED('\'', 0x7D): // DECIC
count = CSI_ARG_COUNT(args[0]);

View File

@@ -21,7 +21,14 @@
#define BUFIDX_PRIMARY 0
#define BUFIDX_ALTSCREEN 1
#define KEY_ENCODING_DISAMBIGUATE 0x1
#define KEY_ENCODING_REPORT_EVENTS 0x2
#define KEY_ENCODING_REPORT_ALTERNATE 0x4
#define KEY_ENCODING_REPORT_ALL_KEYS 0x8
#define KEY_ENCODING_REPORT_ASSOCIATED 0x10
typedef struct VTermEncoding VTermEncoding;
typedef struct VTermKeyEncodingFlags VTermKeyEncodingFlags;
typedef struct {
VTermEncoding *enc;
@@ -46,6 +53,21 @@ struct VTermPen {
unsigned baseline:2;
};
// https://sw.kovidgoyal.net/kitty/keyboard-protocol/#progressive-enhancement
struct VTermKeyEncodingFlags {
bool disambiguate:1;
bool report_events:1;
bool report_alternate:1;
bool report_all_keys:1;
bool report_associated:1;
};
struct VTermKeyEncodingStack {
VTermKeyEncodingFlags items[16];
uint8_t size; ///< Number of items in the stack. This is at least 1 and at
///< most the length of the "items" array.
};
struct VTermState {
VTerm *vt;
@@ -171,6 +193,9 @@ struct VTermState {
char *buffer;
size_t buflen;
} selection;
// Maintain two stacks, one for primary screen and one for altscreen
struct VTermKeyEncodingStack key_encoding_stacks[2];
};
struct VTerm {

View File

@@ -629,6 +629,14 @@ describe('terminal input', function()
-- TODO(bfredl): getcharstr() erases the distinction between <C-I> and <Tab>.
-- If it was enhanced or replaced this could get folded into the test above.
it('can send TAB/C-I and ESC/C-[ separately', function()
if
skip(
is_os('win'),
"The escape sequence to enable kitty keyboard mode doesn't work on Windows"
)
then
return
end
clear()
local screen = tt.setup_child_nvim({
'-u',

View File

@@ -17,6 +17,9 @@ local bit = require('bit')
--- @field VTERM_KEY_NONE integer
--- @field VTERM_KEY_TAB integer
--- @field VTERM_KEY_UP integer
--- @field VTERM_KEY_BACKSPACE integer
--- @field VTERM_KEY_ESCAPE integer
--- @field VTERM_KEY_DEL integer
--- @field VTERM_MOD_ALT integer
--- @field VTERM_MOD_CTRL integer
--- @field VTERM_MOD_SHIFT integer
@@ -505,6 +508,18 @@ local function strp_key(input_key)
return vterm.VTERM_KEY_ENTER
end
if input_key == 'bs' then
return vterm.VTERM_KEY_BACKSPACE
end
if input_key == 'del' then
return vterm.VTERM_KEY_DEL
end
if input_key == 'esc' then
return vterm.VTERM_KEY_ESCAPE
end
if input_key == 'f1' then
return vterm.VTERM_KEY_FUNCTION_0 + 1
end
@@ -2324,65 +2339,83 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
local vt = init()
local state = wantstate(vt)
-- Disambiguate escape codes enabled
push('\x1b[>1u', vt)
-- Unmodified ASCII
inchar(41, vt)
expect('output 29')
inchar(61, vt)
expect('output 3d')
inchar(0x41, vt)
expect_output('A')
inchar(0x61, vt)
expect_output('a')
-- Ctrl modifier on ASCII letters
inchar(41, vt, { C = true })
expect('output 1b,5b,34,31,3b,35,75')
inchar(61, vt, { C = true })
expect('output 1b,5b,36,31,3b,35,75')
inchar(0x41, vt, { C = true })
expect_output('\x1b[97;6u')
inchar(0x61, vt, { C = true })
expect_output('\x1b[97;5u')
-- Alt modifier on ASCII letters
inchar(41, vt, { A = true })
expect('output 1b,29')
inchar(61, vt, { A = true })
expect('output 1b,3d')
inchar(0x41, vt, { A = true })
expect_output('\x1b[97;4u')
inchar(0x61, vt, { A = true })
expect_output('\x1b[97;3u')
-- Ctrl-Alt modifier on ASCII letters
inchar(41, vt, { C = true, A = true })
expect('output 1b,5b,34,31,3b,37,75')
inchar(61, vt, { C = true, A = true })
expect('output 1b,5b,36,31,3b,37,75')
inchar(0x41, vt, { C = true, A = true })
expect_output('\x1b[97;8u')
inchar(0x61, vt, { C = true, A = true })
expect_output('\x1b[97;7u')
-- Special handling of Ctrl-I
inchar(49, vt)
expect('output 31')
inchar(69, vt)
expect('output 45')
inchar(49, vt, { C = true })
expect('output 1b,5b,34,39,3b,35,75')
inchar(69, vt, { C = true })
expect('output 1b,5b,36,39,3b,35,75')
inchar(49, vt, { A = true })
expect('output 1b,31')
inchar(69, vt, { A = true })
expect('output 1b,45')
inchar(49, vt, { A = true, C = true })
expect('output 1b,5b,34,39,3b,37,75')
inchar(69, vt, { A = true, C = true })
expect('output 1b,5b,36,39,3b,37,75')
-- Ctrl-I is disambiguated
inchar(0x49, vt)
expect_output('I')
inchar(0x69, vt)
expect_output('i')
inchar(0x49, vt, { C = true })
expect_output('\x1b[105;6u')
inchar(0x69, vt, { C = true })
expect_output('\x1b[105;5u')
inchar(0x49, vt, { A = true })
expect_output('\x1b[105;4u')
inchar(0x69, vt, { A = true })
expect_output('\x1b[105;3u')
inchar(0x49, vt, { A = true, C = true })
expect_output('\x1b[105;8u')
inchar(0x69, vt, { A = true, C = true })
expect_output('\x1b[105;7u')
-- Ctrl+Digits
for i = 0, 9 do
local c = 0x30 + i
inchar(c, vt)
expect_output(tostring(i))
inchar(c, vt, { C = true })
expect_output(string.format('\x1b[%d;5u', c))
inchar(c, vt, { C = true, S = true })
expect_output(string.format('\x1b[%d;6u', c))
inchar(c, vt, { C = true, A = true })
expect_output(string.format('\x1b[%d;7u', c))
inchar(c, vt, { C = true, A = true, S = true })
expect_output(string.format('\x1b[%d;8u', c))
end
-- Special handling of Space
inchar(20, vt)
expect('output 14')
inchar(20, vt, { S = true })
expect('output 14')
inchar(20, vt, { C = true })
expect('output 1b,5b,32,30,3b,35,75')
inchar(20, vt, { C = true, S = true })
expect('output 1b,5b,32,30,3b,35,75')
inchar(20, vt, { A = true })
expect('output 1b,14')
inchar(20, vt, { S = true, A = true })
expect('output 1b,14')
inchar(20, vt, { C = true, A = true })
expect('output 1b,5b,32,30,3b,37,75')
inchar(20, vt, { S = true, C = true, A = true })
expect('output 1b,5b,32,30,3b,37,75')
inchar(0x20, vt)
expect_output(' ')
inchar(0x20, vt, { S = true })
expect_output('\x1b[32;2u')
inchar(0x20, vt, { C = true })
expect_output('\x1b[32;5u')
inchar(0x20, vt, { C = true, S = true })
expect_output('\x1b[32;6u')
inchar(0x20, vt, { A = true })
expect_output('\x1b[32;3u')
inchar(0x20, vt, { S = true, A = true })
expect_output('\x1b[32;4u')
inchar(0x20, vt, { C = true, A = true })
expect_output('\x1b[32;7u')
inchar(0x20, vt, { S = true, C = true, A = true })
expect_output('\x1b[32;8u')
-- Cursor keys in reset (cursor) mode
inkey('up', vt)
@@ -2413,21 +2446,65 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
inkey('up', vt, { C = true })
expect_output('\x1b[1;5A')
-- Shift-Tab should be different
-- Tab
inkey('tab', vt)
expect_output('\x09')
inkey('tab', vt, { S = true })
expect_output('\x1b[Z')
expect_output('\x1b[9;2u')
inkey('tab', vt, { C = true })
expect_output('\x1b[9;5u')
inkey('tab', vt, { A = true })
expect_output('\x1b\x09')
expect_output('\x1b[9;3u')
inkey('tab', vt, { C = true, A = true })
expect_output('\x1b[9;7u')
-- Backspace
inkey('bs', vt)
expect_output('\x7f')
inkey('bs', vt, { S = true })
expect_output('\x1b[127;2u')
inkey('bs', vt, { C = true })
expect_output('\x1b[127;5u')
inkey('bs', vt, { A = true })
expect_output('\x1b[127;3u')
inkey('bs', vt, { C = true, A = true })
expect_output('\x1b[127;7u')
-- DEL
inkey('del', vt)
expect_output('\x1b[3~')
inkey('del', vt, { S = true })
expect_output('\x1b[3;2~')
inkey('del', vt, { C = true })
expect_output('\x1b[3;5~')
inkey('del', vt, { A = true })
expect_output('\x1b[3;3~')
inkey('del', vt, { C = true, A = true })
expect_output('\x1b[3;7~')
-- ESC
inkey('esc', vt)
expect_output('\x1b[27;1u')
inkey('esc', vt, { S = true })
expect_output('\x1b[27;2u')
inkey('esc', vt, { C = true })
expect_output('\x1b[27;5u')
inkey('esc', vt, { A = true })
expect_output('\x1b[27;3u')
inkey('esc', vt, { C = true, A = true })
expect_output('\x1b[27;7u')
-- Enter in linefeed mode
inkey('enter', vt)
expect_output('\x0d')
inkey('enter', vt, { S = true })
expect_output('\x1b[13;2u')
inkey('enter', vt, { C = true })
expect_output('\x1b[13;5u')
inkey('enter', vt, { A = true })
expect_output('\x1b[13;3u')
inkey('enter', vt, { C = true, A = true })
expect_output('\x1b[13;7u')
-- Enter in newline mode
push('\x1b[20h', vt)
@@ -2448,7 +2525,7 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
-- Keypad in DECKPNM
inkey('kp0', vt)
expect_output('0')
expect_output('\x1b[57399;1u')
-- Keypad in DECKPAM
push('\x1b=', vt)
@@ -2478,6 +2555,77 @@ putglyph 1f3f4,200d,2620,fe0f 2 0,4]])
expect_output('\x1b[I')
vterm.vterm_state_focus_out(state)
expect_output('\x1b[O')
-- Disambiguate escape codes disabled
push('\x1b[<u', vt)
-- Unmodified ASCII
inchar(0x41, vt)
expect_output('A')
inchar(0x61, vt)
expect_output('a')
-- Ctrl modifier on ASCII letters
inchar(0x41, vt, { C = true })
expect_output('\x01')
inchar(0x61, vt, { C = true })
expect_output('\x01')
-- Alt modifier on ASCII letters
inchar(0x41, vt, { A = true })
expect_output('\x1bA')
inchar(0x61, vt, { A = true })
expect_output('\x1ba')
-- Ctrl-Alt modifier on ASCII letters
inchar(0x41, vt, { C = true, A = true })
expect_output('\x1b\x01')
inchar(0x61, vt, { C = true, A = true })
expect_output('\x1b\x01')
-- Ctrl-I is ambiguous
inchar(0x49, vt)
expect_output('I')
inchar(0x69, vt)
expect_output('i')
inchar(0x49, vt, { C = true })
expect_output('\x09')
inchar(0x69, vt, { C = true })
expect_output('\x09')
inchar(0x49, vt, { A = true })
expect_output('\x1bI')
inchar(0x69, vt, { A = true })
expect_output('\x1bi')
inchar(0x49, vt, { A = true, C = true })
expect_output('\x1b\x09')
inchar(0x69, vt, { A = true, C = true })
expect_output('\x1b\x09')
-- Ctrl+Digits
inchar(0x30, vt, { C = true })
expect_output('0')
inchar(0x31, vt, { C = true })
expect_output('1')
inchar(0x32, vt, { C = true })
expect_output('\x00')
inchar(0x33, vt, { C = true })
expect_output('\x1b')
inchar(0x34, vt, { C = true })
expect_output('\x1c')
inchar(0x35, vt, { C = true })
expect_output('\x1d')
inchar(0x36, vt, { C = true })
expect_output('\x1e')
inchar(0x37, vt, { C = true })
expect_output('\x1f')
inchar(0x38, vt, { C = true })
expect_output('\x7f')
inchar(0x39, vt, { C = true })
expect_output('9')
-- Ctrl+/
inchar(0x2F, vt, { C = true })
expect_output('\x1f')
end)
itp('26state_query', function()