mirror of
https://github.com/neovim/neovim.git
synced 2025-09-05 19:08:15 +00:00
Merge pull request #32038 from gpanders/push-nsrttwwnsqvm
feat(terminal): add support for kitty keyboard protocol
This commit is contained in:
@@ -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
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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]);
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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',
|
||||
|
@@ -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()
|
||||
|
Reference in New Issue
Block a user