aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorGregory Anders <8965202+gpanders@users.noreply.github.com>2024-07-21 21:47:37 -0500
committerGitHub <noreply@github.com>2024-07-21 21:47:37 -0500
commitf93ecd2760f5859fd5eeec28c7c2196ece98e9a1 (patch)
treed481d1220ebe4196ef754352272b25fd618dd77a /src
parent7381f0a1d517474dcf8d8d019cbda430ce4bff23 (diff)
downloadrneovim-f93ecd2760f5859fd5eeec28c7c2196ece98e9a1.tar.gz
rneovim-f93ecd2760f5859fd5eeec28c7c2196ece98e9a1.tar.bz2
rneovim-f93ecd2760f5859fd5eeec28c7c2196ece98e9a1.zip
feat(tui): parse CSI subparams in termkey (#29805)
libtermkey does not know how to parse CSI subparameters (parameters separated by ':', ASCII 0x3A) and currently just ignores them. However, many important CSI sequences sent by the terminal make use of subparameters, most notably key events when using the kitty keyboard protocol [1]. Enabling subparameters is a prerequisite for expanding kitty keyboard protocol support in Neovim. Concretely, we do this by returning pointers into the internal termkey buffer for each CSI parameter rather than parsing them into integers directly. When a caller wants to actually use the parameter as an integer, they must call termkey_interpret_csi_param, which parses the full parameter string into an integer parameter and zero or more subparameters. The pointers into the internal buffer will become invalidated when new input arrives from the terminal so it is important that the individual params are used and parsed right away. All of our code (and libtermkey's code) does this, so this is fine for now, but is something to keep in mind moving forward. [1]: https://sw.kovidgoyal.net/kitty/keyboard-protocol/
Diffstat (limited to 'src')
-rw-r--r--src/nvim/tui/input.c28
-rw-r--r--src/termkey/driver-csi.c245
-rw-r--r--src/termkey/termkey.h9
3 files changed, 215 insertions, 67 deletions
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index 9f58607bf7..a6e27c9391 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -596,10 +596,10 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
{
// There is no specified limit on the number of parameters a CSI sequence can
// contain, so just allocate enough space for a large upper bound
- long args[16];
- size_t nargs = 16;
+ TermKeyCsiParam params[16];
+ size_t nparams = 16;
unsigned long cmd;
- if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) {
+ if (termkey_interpret_csi(input->tk, key, params, &nparams, &cmd) != TERMKEY_RES_KEY) {
return;
}
@@ -639,12 +639,22 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
}
break;
case 't':
- if (nargs == 5 && args[0] == 48) {
- // In-band resize event (DEC private mode 2048)
- int height_chars = (int)args[1];
- int width_chars = (int)args[2];
- tui_set_size(input->tui_data, width_chars, height_chars);
- ui_client_set_size(width_chars, height_chars);
+ if (nparams == 5) {
+ // We only care about the first 3 parameters, and we ignore subparameters
+ long args[3];
+ for (size_t i = 0; i < ARRAY_SIZE(args); i++) {
+ if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
+ return;
+ }
+ }
+
+ if (args[0] == 48) {
+ // In-band resize event (DEC private mode 2048)
+ int height_chars = (int)args[1];
+ int width_chars = (int)args[2];
+ tui_set_size(input->tui_data, width_chars, height_chars);
+ ui_client_set_size(width_chars, height_chars);
+ }
}
break;
default:
diff --git a/src/termkey/driver-csi.c b/src/termkey/driver-csi.c
index 4cd5bbafe4..f9c39757b7 100644
--- a/src/termkey/driver-csi.c
+++ b/src/termkey/driver-csi.c
@@ -1,6 +1,7 @@
#include "termkey.h"
#include "termkey-internal.h"
+#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -15,7 +16,7 @@ typedef struct {
char *saved_string;
} TermKeyCsi;
-typedef TermKeyResult CsiHandler(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args);
+typedef TermKeyResult CsiHandler(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams);
static CsiHandler *csi_handlers[64];
/*
@@ -24,12 +25,21 @@ static CsiHandler *csi_handlers[64];
static struct keyinfo csi_ss3s[64];
-static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
- if(args > 1 && arg[1] != -1)
- key->modifiers = arg[1] - 1;
- else
+ TermKeyResult result = TERMKEY_RES_KEY;
+
+ if(nparams > 1 && params[1].param != NULL) {
+ long arg = 0;
+ result = termkey_interpret_csi_param(params[1], &arg, NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ key->modifiers = arg - 1;
+ } else {
key->modifiers = 0;
+ }
key->type = csi_ss3s[cmd - 0x40].type;
key->code.sym = csi_ss3s[cmd - 0x40].sym;
@@ -37,9 +47,9 @@ static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd,
key->modifiers |= csi_ss3s[cmd - 0x40].modifier_set;
if(key->code.sym == TERMKEY_SYM_UNKNOWN)
- return TERMKEY_RES_NONE;
+ result = TERMKEY_RES_NONE;
- return TERMKEY_RES_KEY;
+ return result;
}
static void register_csi_ss3_full(TermKeyType type, TermKeySym sym, int modifier_set, int modifier_mask, unsigned char cmd)
@@ -85,25 +95,48 @@ static void register_ss3kpalt(TermKeyType type, TermKeySym sym, unsigned char cm
static struct keyinfo csifuncs[35]; /* This value must be increased if more CSI function keys are added */
#define NCSIFUNCS (sizeof(csifuncs)/sizeof(csifuncs[0]))
-static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
- if(args > 1 && arg[1] != -1)
- key->modifiers = arg[1] - 1;
- else
+ if (nparams == 0) {
+ return TERMKEY_RES_NONE;
+ }
+
+ TermKeyResult result = TERMKEY_RES_KEY;
+ long args[3];
+
+ if(nparams > 1 && params[1].param != NULL) {
+ result = termkey_interpret_csi_param(params[1], &args[1], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ key->modifiers = args[1] - 1;
+ } else {
key->modifiers = 0;
+ }
key->type = TERMKEY_TYPE_KEYSYM;
- if(arg[0] == 27) {
+ result = termkey_interpret_csi_param(params[0], &args[0], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ if(args[0] == 27 && nparams > 2 && params[2].param != NULL) {
+ result = termkey_interpret_csi_param(params[2], &args[2], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
int mod = key->modifiers;
- (*tk->method.emit_codepoint)(tk, arg[2], key);
+ (*tk->method.emit_codepoint)(tk, args[2], key);
key->modifiers |= mod;
}
- else if(arg[0] >= 0 && arg[0] < NCSIFUNCS) {
- key->type = csifuncs[arg[0]].type;
- key->code.sym = csifuncs[arg[0]].sym;
- key->modifiers &= ~(csifuncs[arg[0]].modifier_mask);
- key->modifiers |= csifuncs[arg[0]].modifier_set;
+ else if(args[0] >= 0 && args[0] < NCSIFUNCS) {
+ key->type = csifuncs[args[0]].type;
+ key->code.sym = csifuncs[args[0]].sym;
+ key->modifiers &= ~(csifuncs[args[0]].modifier_mask);
+ key->modifiers |= csifuncs[args[0]].modifier_set;
}
else
key->code.sym = TERMKEY_SYM_UNKNOWN;
@@ -112,10 +145,10 @@ static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, long
#ifdef DEBUG
fprintf(stderr, "CSI: Unknown function key %ld\n", arg[0]);
#endif
- return TERMKEY_RES_NONE;
+ result = TERMKEY_RES_NONE;
}
- return TERMKEY_RES_KEY;
+ return result;
}
static void register_csifunc(TermKeyType type, TermKeySym sym, int number)
@@ -136,18 +169,35 @@ static void register_csifunc(TermKeyType type, TermKeySym sym, int number)
* Handler for CSI u extended Unicode keys
*/
-static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
switch(cmd) {
case 'u': {
- if(args > 1 && arg[1] != -1)
- key->modifiers = arg[1] - 1;
- else
+ long args[2];
+ if(nparams > 1 && params[1].param != NULL) {
+ long subparam = 0;
+ size_t nsubparams = 1;
+ if (termkey_interpret_csi_param(params[1], &args[1], &subparam, &nsubparams) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (nsubparams > 0 && subparam != 1) {
+ // Not a press event. Ignore for now
+ return TERMKEY_RES_NONE;
+ }
+
+ key->modifiers = args[1] - 1;
+ } else {
key->modifiers = 0;
+ }
+
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
int mod = key->modifiers;
key->type = TERMKEY_TYPE_KEYSYM;
- (*tk->method.emit_codepoint)(tk, arg[0], key);
+ (*tk->method.emit_codepoint)(tk, args[0], key);
key->modifiers |= mod;
return TERMKEY_RES_KEY;
@@ -162,7 +212,7 @@ static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, long *a
* Note: This does not handle X10 encoding
*/
-static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
int initial = cmd >> 8;
cmd &= 0xff;
@@ -175,26 +225,37 @@ static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, long *a
return TERMKEY_RES_NONE;
}
- if(!initial && args >= 3) { // rxvt protocol
+ if (nparams < 3) {
+ return TERMKEY_RES_NONE;
+ }
+
+ long args[3];
+ for (size_t i = 0; i < 3; i++) {
+ if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+ }
+
+ if(!initial) { // rxvt protocol
key->type = TERMKEY_TYPE_MOUSE;
- key->code.mouse[0] = arg[0];
+ key->code.mouse[0] = args[0];
key->modifiers = (key->code.mouse[0] & 0x1c) >> 2;
key->code.mouse[0] &= ~0x1c;
- termkey_key_set_linecol(key, arg[1], arg[2]);
+ termkey_key_set_linecol(key, args[1], args[2]);
return TERMKEY_RES_KEY;
}
- if(initial == '<' && args >= 3) { // SGR protocol
+ if(initial == '<') { // SGR protocol
key->type = TERMKEY_TYPE_MOUSE;
- key->code.mouse[0] = arg[0];
+ key->code.mouse[0] = args[0];
key->modifiers = (key->code.mouse[0] & 0x1c) >> 2;
key->code.mouse[0] &= ~0x1c;
- termkey_key_set_linecol(key, arg[1], arg[2]);
+ termkey_key_set_linecol(key, args[1], args[2]);
if(cmd == 'm') // release
key->code.mouse[3] |= 0x80;
@@ -265,19 +326,28 @@ TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKe
* A plain CSI R with no arguments is probably actually <F3>
*/
-static TermKeyResult handle_csi_R(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csi_R(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
switch(cmd) {
case 'R'|'?'<<8:
- if(args < 2)
+ if(nparams < 2)
return TERMKEY_RES_NONE;
+ long args[2];
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
key->type = TERMKEY_TYPE_POSITION;
- termkey_key_set_linecol(key, arg[1], arg[0]);
+ termkey_key_set_linecol(key, args[1], args[0]);
return TERMKEY_RES_KEY;
default:
- return handle_csi_ss3_full(tk, key, cmd, arg, args);
+ return handle_csi_ss3_full(tk, key, cmd, params, nparams);
}
}
@@ -295,19 +365,28 @@ TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int
* Handler for CSI $y mode status reports
*/
-static TermKeyResult handle_csi_y(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args)
+static TermKeyResult handle_csi_y(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams)
{
switch(cmd) {
case 'y'|'$'<<16:
case 'y'|'$'<<16 | '?'<<8:
- if(args < 2)
+ if(nparams < 2)
return TERMKEY_RES_NONE;
+ long args[2];
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
key->type = TERMKEY_TYPE_MODEREPORT;
key->code.mouse[0] = (cmd >> 8);
- key->code.mouse[1] = arg[0] >> 8;
- key->code.mouse[2] = arg[0] & 0xff;
- key->code.mouse[3] = arg[1];
+ key->code.mouse[1] = args[0] >> 8;
+ key->code.mouse[2] = args[0] & 0xff;
+ key->code.mouse[3] = args[1];
return TERMKEY_RES_KEY;
default:
@@ -334,7 +413,7 @@ TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, i
#define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
-static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, long args[], size_t *nargs, unsigned long *commandp)
+static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, TermKeyCsiParam params[], size_t *nargs, unsigned long *commandp)
{
size_t csi_end = introlen;
@@ -365,18 +444,19 @@ static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, lo
while(p < csi_end) {
unsigned char c = CHARAT(p);
- if(c >= '0' && c <= '9') {
+ if(c >= '0' && c < ';') {
if(!present) {
- args[argi] = c - '0';
+ params[argi].param = &CHARAT(p);
present = 1;
}
- else {
- args[argi] = (args[argi] * 10) + c - '0';
- }
}
else if(c == ';') {
- if(!present)
- args[argi] = -1;
+ if(!present) {
+ params[argi].param = NULL;
+ params[argi].length = 0;
+ } else {
+ params[argi].length = &CHARAT(p) - params[argi].param;
+ }
present = 0;
argi++;
@@ -391,8 +471,10 @@ static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, lo
p++;
}
- if(present)
+ if(present) {
+ params[argi].length = &CHARAT(p) - params[argi].param;
argi++;
+ }
*nargs = argi;
*csi_len = csi_end + 1;
@@ -400,7 +482,7 @@ static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, lo
return TERMKEY_RES_KEY;
}
-TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, long args[], size_t *nargs, unsigned long *cmd)
+TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, TermKeyCsiParam params[], size_t *nparams, unsigned long *cmd)
{
size_t dummy;
@@ -409,7 +491,56 @@ TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, long arg
if(key->type != TERMKEY_TYPE_UNKNOWN_CSI)
return TERMKEY_RES_NONE;
- return parse_csi(tk, 0, &dummy, args, nargs, cmd);
+ return parse_csi(tk, 0, &dummy, params, nparams, cmd);
+}
+
+TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, long *paramp, long subparams[], size_t *nsubparams)
+{
+ if (paramp == NULL) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (param.param == NULL) {
+ *paramp = -1;
+ if (nsubparams) {
+ *nsubparams = 0;
+ }
+ return TERMKEY_RES_KEY;
+ }
+
+ long arg = 0;
+ size_t i = 0;
+ size_t capacity = nsubparams ? *nsubparams : 0;
+ size_t length = 0;
+ for (; i < param.length && length <= capacity; i++) {
+ unsigned char c = param.param[i];
+ if (c == ':') {
+ if (length == 0) {
+ *paramp = arg;
+ } else {
+ subparams[length - 1] = arg;
+ }
+
+ arg = 0;
+ length++;
+ continue;
+ }
+
+ assert(c >= '0' && c <= '9');
+ arg = (10 * arg) + (c - '0');
+ }
+
+ if (length == 0) {
+ *paramp = arg;
+ } else {
+ subparams[length - 1] = arg;
+ }
+
+ if (nsubparams) {
+ *nsubparams = length;
+ }
+
+ return TERMKEY_RES_KEY;
}
static int register_keys(void)
@@ -531,11 +662,11 @@ static void free_driver(void *info)
static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep)
{
size_t csi_len;
- size_t args = 16;
- long arg[16];
+ size_t nparams = 16;
+ TermKeyCsiParam params[16];
unsigned long cmd;
- TermKeyResult ret = parse_csi(tk, introlen, &csi_len, arg, &args, &cmd);
+ TermKeyResult ret = parse_csi(tk, introlen, &csi_len, params, &nparams, &cmd);
if(ret == TERMKEY_RES_AGAIN) {
if(!force)
@@ -547,7 +678,7 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen,
return TERMKEY_RES_KEY;
}
- if(cmd == 'M' && args < 3) { // Mouse in X10 encoding consumes the next 3 bytes also
+ if(cmd == 'M' && nparams < 3) { // Mouse in X10 encoding consumes the next 3 bytes also
tk->buffstart += csi_len;
tk->buffcount -= csi_len;
@@ -566,7 +697,7 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen,
// We know from the logic above that cmd must be >= 0x40 and < 0x80
if(csi_handlers[(cmd & 0xff) - 0x40])
- result = (*csi_handlers[(cmd & 0xff) - 0x40])(tk, key, cmd, arg, args);
+ result = (*csi_handlers[(cmd & 0xff) - 0x40])(tk, key, cmd, params, nparams);
if(result == TERMKEY_RES_NONE) {
#ifdef DEBUG
diff --git a/src/termkey/termkey.h b/src/termkey/termkey.h
index 8e10fcff0c..94405f6516 100644
--- a/src/termkey/termkey.h
+++ b/src/termkey/termkey.h
@@ -143,6 +143,11 @@ typedef struct {
char utf8[7];
} TermKeyKey;
+typedef struct {
+ const unsigned char *param;
+ size_t length;
+} TermKeyCsiParam;
+
typedef struct TermKey TermKey;
enum {
@@ -215,7 +220,9 @@ TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int
TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, int *initial, int *mode, int *value);
-TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, long args[], size_t *nargs, unsigned long *cmd);
+TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, TermKeyCsiParam params[], size_t *nparams, unsigned long *cmd);
+
+TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, long *paramp, long subparams[], size_t *nsubparams);
TermKeyResult termkey_interpret_string(TermKey *tk, const TermKeyKey *key, const char **strp);