diff options
| author | Gregory Anders <8965202+gpanders@users.noreply.github.com> | 2023-11-30 13:56:54 -0600 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-11-30 13:56:54 -0600 |
| commit | 5e1ff4302f2b167d4f73c50c47f7ace97f442eca (patch) | |
| tree | 55f2ec9900cffa0703fb5062a3bfc3ae235ff5eb /src | |
| parent | 307d5bcc7940d14f7d17e7c2de795ebed7b0f00c (diff) | |
| parent | 5d3562d810850ee99c6352f345749d34a08a66d1 (diff) | |
| download | rneovim-5e1ff4302f2b167d4f73c50c47f7ace97f442eca.tar.gz rneovim-5e1ff4302f2b167d4f73c50c47f7ace97f442eca.tar.bz2 rneovim-5e1ff4302f2b167d4f73c50c47f7ace97f442eca.zip | |
Merge pull request #25870 from dundargoc/build/libtermkey
build: vendor libtermkey
Diffstat (limited to 'src')
| -rw-r--r-- | src/nvim/CMakeLists.txt | 12 | ||||
| -rw-r--r-- | src/nvim/tui/input.h | 2 | ||||
| -rw-r--r-- | src/termkey/driver-csi.c | 770 | ||||
| -rw-r--r-- | src/termkey/driver-ti.c | 635 | ||||
| -rw-r--r-- | src/termkey/termkey-internal.h | 112 | ||||
| -rw-r--r-- | src/termkey/termkey.c | 1606 | ||||
| -rw-r--r-- | src/termkey/termkey.h | 249 |
7 files changed, 3379 insertions, 7 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a8ce9edff8..6a01632040 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -37,7 +37,6 @@ target_link_libraries(main_lib INTERFACE ${LUV_LIBRARY}) find_package(Iconv REQUIRED) find_package(Lpeg REQUIRED) -find_package(Libtermkey 0.22 REQUIRED) find_package(Libvterm 0.3.3 REQUIRED) find_package(Msgpack 1.0.0 REQUIRED) find_package(Treesitter 0.20.8 REQUIRED) @@ -45,7 +44,6 @@ find_package(Unibilium 2.0 REQUIRED) target_link_libraries(main_lib INTERFACE iconv - libtermkey libvterm msgpack treesitter @@ -60,6 +58,8 @@ if (LIBINTL_LIBRARY) target_link_libraries(main_lib INTERFACE ${LIBINTL_LIBRARY}) endif() +target_compile_definitions(main_lib INTERFACE HAVE_UNIBILIUM) + # The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing. option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) if(PREFER_LUA) @@ -376,8 +376,8 @@ file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}) glob_wrapper(NVIM_SOURCES *.c) glob_wrapper(NVIM_HEADERS *.h) -glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c) -glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h) +glob_wrapper(EXTERNAL_SOURCES ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../termkey/*.c) +glob_wrapper(EXTERNAL_HEADERS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../termkey/*.h) glob_wrapper(NLUA0_SOURCES ../mpack/*.c) @@ -436,13 +436,13 @@ endforeach() list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) -# xdiff, mpack, lua-cjson: inlined external project, we don't maintain it. #9306 +# xdiff, mpack, lua-cjson, termkey: inlined external project, we don't maintain it. #9306 if(MSVC) set_source_files_properties( ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -wd4090 -wd4244 -wd4267") else() set_source_files_properties( - ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes -Wno-misleading-indentation") + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-missing-noreturn -Wno-missing-format-attribute -Wno-double-promotion -Wno-strict-prototypes -Wno-misleading-indentation -Wno-sign-compare -Wno-implicit-fallthrough -Wno-missing-prototypes -Wno-missing-field-initializers") endif() # Log level (NVIM_LOG_DEBUG in log.h) diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 2743a5e286..27a0291f5b 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -2,7 +2,6 @@ #include <stdbool.h> #include <stdint.h> -#include <termkey.h> #include <uv.h> #include "nvim/event/loop.h" @@ -12,6 +11,7 @@ #include "nvim/tui/input_defs.h" // IWYU pragma: export #include "nvim/tui/tui.h" #include "nvim/types_defs.h" +#include "termkey/termkey.h" typedef enum { kKeyEncodingLegacy, ///< Legacy key encoding diff --git a/src/termkey/driver-csi.c b/src/termkey/driver-csi.c new file mode 100644 index 0000000000..86b59ac203 --- /dev/null +++ b/src/termkey/driver-csi.c @@ -0,0 +1,770 @@ +#include "termkey.h" +#include "termkey-internal.h" + +#include <stdio.h> +#include <string.h> + +// There are 64 codes 0x40 - 0x7F +static int keyinfo_initialised = 0; +static struct keyinfo ss3s[64]; +static char ss3_kpalts[64]; + +typedef struct { + TermKey *tk; + int saved_string_id; + char *saved_string; +} TermKeyCsi; + +typedef TermKeyResult CsiHandler(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args); +static CsiHandler *csi_handlers[64]; + +/* + * Handler for CSI/SS3 cmd keys + */ + +static struct keyinfo csi_ss3s[64]; + +static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args) +{ + if(args > 1 && arg[1] != -1) + key->modifiers = arg[1] - 1; + else + key->modifiers = 0; + + key->type = csi_ss3s[cmd - 0x40].type; + key->code.sym = csi_ss3s[cmd - 0x40].sym; + key->modifiers &= ~(csi_ss3s[cmd - 0x40].modifier_mask); + key->modifiers |= csi_ss3s[cmd - 0x40].modifier_set; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) + return TERMKEY_RES_NONE; + + return TERMKEY_RES_KEY; +} + +static void register_csi_ss3_full(TermKeyType type, TermKeySym sym, int modifier_set, int modifier_mask, unsigned char cmd) +{ + if(cmd < 0x40 || cmd >= 0x80) { + return; + } + + csi_ss3s[cmd - 0x40].type = type; + csi_ss3s[cmd - 0x40].sym = sym; + csi_ss3s[cmd - 0x40].modifier_set = modifier_set; + csi_ss3s[cmd - 0x40].modifier_mask = modifier_mask; + + csi_handlers[cmd - 0x40] = &handle_csi_ss3_full; +} + +static void register_csi_ss3(TermKeyType type, TermKeySym sym, unsigned char cmd) +{ + register_csi_ss3_full(type, sym, 0, 0, cmd); +} + +/* + * Handler for SS3 keys with kpad alternate representations + */ + +static void register_ss3kpalt(TermKeyType type, TermKeySym sym, unsigned char cmd, char kpalt) +{ + if(cmd < 0x40 || cmd >= 0x80) { + return; + } + + ss3s[cmd - 0x40].type = type; + ss3s[cmd - 0x40].sym = sym; + ss3s[cmd - 0x40].modifier_set = 0; + ss3s[cmd - 0x40].modifier_mask = 0; + ss3_kpalts[cmd - 0x40] = kpalt; +} + +/* + * Handler for CSI number ~ function keys + */ + +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) +{ + if(args > 1 && arg[1] != -1) + key->modifiers = arg[1] - 1; + else + key->modifiers = 0; + + key->type = TERMKEY_TYPE_KEYSYM; + + if(arg[0] == 27) { + int mod = key->modifiers; + (*tk->method.emit_codepoint)(tk, arg[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 + key->code.sym = TERMKEY_SYM_UNKNOWN; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) { +#ifdef DEBUG + fprintf(stderr, "CSI: Unknown function key %ld\n", arg[0]); +#endif + return TERMKEY_RES_NONE; + } + + return TERMKEY_RES_KEY; +} + +static void register_csifunc(TermKeyType type, TermKeySym sym, int number) +{ + if(number >= NCSIFUNCS) { + return; + } + + csifuncs[number].type = type; + csifuncs[number].sym = sym; + csifuncs[number].modifier_set = 0; + csifuncs[number].modifier_mask = 0; + + csi_handlers['~' - 0x40] = &handle_csifunc; +} + +/* + * Handler for CSI u extended Unicode keys + */ + +static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args) +{ + switch(cmd) { + case 'u': { + if(args > 1 && arg[1] != -1) + key->modifiers = arg[1] - 1; + else + key->modifiers = 0; + + int mod = key->modifiers; + key->type = TERMKEY_TYPE_KEYSYM; + (*tk->method.emit_codepoint)(tk, arg[0], key); + key->modifiers |= mod; + + return TERMKEY_RES_KEY; + } + default: + return TERMKEY_RES_NONE; + } +} + +/* + * Handler for CSI M / CSI m mouse events in SGR and rxvt encodings + * Note: This does not handle X10 encoding + */ + +static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args) +{ + int initial = cmd >> 8; + cmd &= 0xff; + + switch(cmd) { + case 'M': + case 'm': + break; + default: + return TERMKEY_RES_NONE; + } + + if(!initial && args >= 3) { // rxvt protocol + key->type = TERMKEY_TYPE_MOUSE; + key->code.mouse[0] = arg[0]; + + key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + termkey_key_set_linecol(key, arg[1], arg[2]); + + return TERMKEY_RES_KEY; + } + + if(initial == '<' && args >= 3) { // SGR protocol + key->type = TERMKEY_TYPE_MOUSE; + key->code.mouse[0] = arg[0]; + + key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + termkey_key_set_linecol(key, arg[1], arg[2]); + + if(cmd == 'm') // release + key->code.mouse[3] |= 0x80; + + return TERMKEY_RES_KEY; + } + + return TERMKEY_RES_NONE; +} + +TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKeyMouseEvent *event, int *button, int *line, int *col) +{ + if(key->type != TERMKEY_TYPE_MOUSE) + return TERMKEY_RES_NONE; + + if(button) + *button = 0; + + termkey_key_get_linecol(key, line, col); + + if(!event) + return TERMKEY_RES_KEY; + + int btn = 0; + + int code = key->code.mouse[0]; + + int drag = code & 0x20; + + code &= ~0x3c; + + switch(code) { + case 0: + case 1: + case 2: + *event = drag ? TERMKEY_MOUSE_DRAG : TERMKEY_MOUSE_PRESS; + btn = code + 1; + break; + + case 3: + *event = TERMKEY_MOUSE_RELEASE; + // no button hint + break; + + case 64: + case 65: + *event = drag ? TERMKEY_MOUSE_DRAG : TERMKEY_MOUSE_PRESS; + btn = code + 4 - 64; + break; + + default: + *event = TERMKEY_MOUSE_UNKNOWN; + } + + if(button) + *button = btn; + + if(key->code.mouse[3] & 0x80) + *event = TERMKEY_MOUSE_RELEASE; + + return TERMKEY_RES_KEY; +} + +/* + * Handler for CSI ? R position reports + * 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) +{ + switch(cmd) { + case 'R'|'?'<<8: + if(args < 2) + return TERMKEY_RES_NONE; + + key->type = TERMKEY_TYPE_POSITION; + termkey_key_set_linecol(key, arg[1], arg[0]); + return TERMKEY_RES_KEY; + + default: + return handle_csi_ss3_full(tk, key, cmd, arg, args); + } +} + +TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int *line, int *col) +{ + if(key->type != TERMKEY_TYPE_POSITION) + return TERMKEY_RES_NONE; + + termkey_key_get_linecol(key, line, col); + + return TERMKEY_RES_KEY; +} + +/* + * Handler for CSI $y mode status reports + */ + +static TermKeyResult handle_csi_y(TermKey *tk, TermKeyKey *key, int cmd, long *arg, int args) +{ + switch(cmd) { + case 'y'|'$'<<16: + case 'y'|'$'<<16 | '?'<<8: + if(args < 2) + return TERMKEY_RES_NONE; + + 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]; + return TERMKEY_RES_KEY; + + default: + return TERMKEY_RES_NONE; + } +} + +TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, int *initial, int *mode, int *value) +{ + if(key->type != TERMKEY_TYPE_MODEREPORT) + return TERMKEY_RES_NONE; + + if(initial) + *initial = key->code.mouse[0]; + + if(mode) + *mode = ((uint8_t)key->code.mouse[1] << 8) | (uint8_t)key->code.mouse[2]; + + if(value) + *value = key->code.mouse[3]; + + return TERMKEY_RES_KEY; +} + +#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) +{ + size_t csi_end = introlen; + + while(csi_end < tk->buffcount) { + if(CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) + break; + csi_end++; + } + + if(csi_end >= tk->buffcount) + return TERMKEY_RES_AGAIN; + + unsigned char cmd = CHARAT(csi_end); + *commandp = cmd; + + char present = 0; + int argi = 0; + + size_t p = introlen; + + // See if there is an initial byte + if(CHARAT(p) >= '<' && CHARAT(p) <= '?') { + *commandp |= (CHARAT(p) << 8); + p++; + } + + // Now attempt to parse out up number;number;... separated values + while(p < csi_end) { + unsigned char c = CHARAT(p); + + if(c >= '0' && c <= '9') { + if(!present) { + args[argi] = c - '0'; + present = 1; + } + else { + args[argi] = (args[argi] * 10) + c - '0'; + } + } + else if(c == ';') { + if(!present) + args[argi] = -1; + present = 0; + argi++; + + if(argi > 16) + break; + } + else if(c >= 0x20 && c <= 0x2f) { + *commandp |= c << 16; + break; + } + + p++; + } + + if(present) + argi++; + + *nargs = argi; + *csi_len = csi_end + 1; + + return TERMKEY_RES_KEY; +} + +TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, long args[], size_t *nargs, unsigned long *cmd) +{ + size_t dummy; + + if(tk->hightide == 0) + return TERMKEY_RES_NONE; + if(key->type != TERMKEY_TYPE_UNKNOWN_CSI) + return TERMKEY_RES_NONE; + + return parse_csi(tk, 0, &dummy, args, nargs, cmd); +} + +static int register_keys(void) +{ + int i; + + for(i = 0; i < 64; i++) { + csi_ss3s[i].sym = TERMKEY_SYM_UNKNOWN; + ss3s[i].sym = TERMKEY_SYM_UNKNOWN; + ss3_kpalts[i] = 0; + } + + for(i = 0; i < NCSIFUNCS; i++) + csifuncs[i].sym = TERMKEY_SYM_UNKNOWN; + + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP, 'A'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN, 'B'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT, 'C'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT, 'D'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 'E'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 'F'); + register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 'H'); + register_csi_ss3(TERMKEY_TYPE_FUNCTION, 1, 'P'); + register_csi_ss3(TERMKEY_TYPE_FUNCTION, 2, 'Q'); + register_csi_ss3(TERMKEY_TYPE_FUNCTION, 3, 'R'); + register_csi_ss3(TERMKEY_TYPE_FUNCTION, 4, 'S'); + + register_csi_ss3_full(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB, TERMKEY_KEYMOD_SHIFT, TERMKEY_KEYMOD_SHIFT, 'Z'); + + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPENTER, 'M', 0); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPEQUALS, 'X', '='); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPMULT, 'j', '*'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPPLUS, 'k', '+'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPCOMMA, 'l', ','); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPMINUS, 'm', '-'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPPERIOD, 'n', '.'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPDIV, 'o', '/'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP0, 'p', '0'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP1, 'q', '1'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP2, 'r', '2'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP3, 's', '3'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP4, 't', '4'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP5, 'u', '5'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP6, 'v', '6'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP7, 'w', '7'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP8, 'x', '8'); + register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP9, 'y', '9'); + + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND, 1); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT, 2); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE, 3); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT, 4); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 5); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 6); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 7); + register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 8); + + register_csifunc(TERMKEY_TYPE_FUNCTION, 1, 11); + register_csifunc(TERMKEY_TYPE_FUNCTION, 2, 12); + register_csifunc(TERMKEY_TYPE_FUNCTION, 3, 13); + register_csifunc(TERMKEY_TYPE_FUNCTION, 4, 14); + register_csifunc(TERMKEY_TYPE_FUNCTION, 5, 15); + register_csifunc(TERMKEY_TYPE_FUNCTION, 6, 17); + register_csifunc(TERMKEY_TYPE_FUNCTION, 7, 18); + register_csifunc(TERMKEY_TYPE_FUNCTION, 8, 19); + register_csifunc(TERMKEY_TYPE_FUNCTION, 9, 20); + register_csifunc(TERMKEY_TYPE_FUNCTION, 10, 21); + register_csifunc(TERMKEY_TYPE_FUNCTION, 11, 23); + register_csifunc(TERMKEY_TYPE_FUNCTION, 12, 24); + register_csifunc(TERMKEY_TYPE_FUNCTION, 13, 25); + register_csifunc(TERMKEY_TYPE_FUNCTION, 14, 26); + register_csifunc(TERMKEY_TYPE_FUNCTION, 15, 28); + register_csifunc(TERMKEY_TYPE_FUNCTION, 16, 29); + register_csifunc(TERMKEY_TYPE_FUNCTION, 17, 31); + register_csifunc(TERMKEY_TYPE_FUNCTION, 18, 32); + register_csifunc(TERMKEY_TYPE_FUNCTION, 19, 33); + register_csifunc(TERMKEY_TYPE_FUNCTION, 20, 34); + + csi_handlers['u' - 0x40] = &handle_csi_u; + + csi_handlers['M' - 0x40] = &handle_csi_m; + csi_handlers['m' - 0x40] = &handle_csi_m; + + csi_handlers['R' - 0x40] = &handle_csi_R; + + csi_handlers['y' - 0x40] = &handle_csi_y; + + keyinfo_initialised = 1; + return 1; +} + +static void *new_driver(TermKey *tk, const char *term) +{ + if(!keyinfo_initialised) + if(!register_keys()) + return NULL; + + TermKeyCsi *csi = malloc(sizeof *csi); + if(!csi) + return NULL; + + csi->tk = tk; + csi->saved_string_id = 0; + csi->saved_string = NULL; + + return csi; +} + +static void free_driver(void *info) +{ + TermKeyCsi *csi = info; + + if(csi->saved_string) + free(csi->saved_string); + + free(csi); +} + +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]; + unsigned long cmd; + + TermKeyResult ret = parse_csi(tk, introlen, &csi_len, arg, &args, &cmd); + + if(ret == TERMKEY_RES_AGAIN) { + if(!force) + return TERMKEY_RES_AGAIN; + + (*tk->method.emit_codepoint)(tk, '[', key); + key->modifiers |= TERMKEY_KEYMOD_ALT; + *nbytep = introlen; + return TERMKEY_RES_KEY; + } + + if(cmd == 'M' && args < 3) { // Mouse in X10 encoding consumes the next 3 bytes also + tk->buffstart += csi_len; + tk->buffcount -= csi_len; + + TermKeyResult mouse_result = (*tk->method.peekkey_mouse)(tk, key, nbytep); + + tk->buffstart -= csi_len; + tk->buffcount += csi_len; + + if(mouse_result == TERMKEY_RES_KEY) + *nbytep += csi_len; + + return mouse_result; + } + + TermKeyResult result = TERMKEY_RES_NONE; + + // 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); + + if(result == TERMKEY_RES_NONE) { +#ifdef DEBUG + switch(args) { + case 0: + fprintf(stderr, "CSI: Unknown cmd=%c\n", (char)cmd); + break; + case 1: + fprintf(stderr, "CSI: Unknown arg1=%ld cmd=%c\n", arg[0], (char)cmd); + break; + case 2: + fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld cmd=%c\n", arg[0], arg[1], (char)cmd); + break; + case 3: + fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld arg3=%ld cmd=%c\n", arg[0], arg[1], arg[2], (char)cmd); + break; + default: + fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld arg3=%ld ... args=%d cmd=%c\n", arg[0], arg[1], arg[2], args, (char)cmd); + break; + } +#endif + key->type = TERMKEY_TYPE_UNKNOWN_CSI; + key->code.number = cmd; + key->modifiers = 0; + + tk->hightide = csi_len - introlen; + *nbytep = introlen; // Do not yet eat the data bytes + return TERMKEY_RES_KEY; + } + + *nbytep = csi_len; + return result; +} + +static TermKeyResult peekkey_ss3(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep) +{ + if(tk->buffcount < introlen + 1) { + if(!force) + return TERMKEY_RES_AGAIN; + + (*tk->method.emit_codepoint)(tk, 'O', key); + key->modifiers |= TERMKEY_KEYMOD_ALT; + *nbytep = tk->buffcount; + return TERMKEY_RES_KEY; + } + + unsigned char cmd = CHARAT(introlen); + + if(cmd < 0x40 || cmd >= 0x80) + return TERMKEY_RES_NONE; + + key->type = csi_ss3s[cmd - 0x40].type; + key->code.sym = csi_ss3s[cmd - 0x40].sym; + key->modifiers = csi_ss3s[cmd - 0x40].modifier_set; + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) { + if(tk->flags & TERMKEY_FLAG_CONVERTKP && ss3_kpalts[cmd - 0x40]) { + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = ss3_kpalts[cmd - 0x40]; + key->modifiers = 0; + + key->utf8[0] = key->code.codepoint; + key->utf8[1] = 0; + } + else { + key->type = ss3s[cmd - 0x40].type; + key->code.sym = ss3s[cmd - 0x40].sym; + key->modifiers = ss3s[cmd - 0x40].modifier_set; + } + } + + if(key->code.sym == TERMKEY_SYM_UNKNOWN) { +#ifdef DEBUG + fprintf(stderr, "CSI: Unknown SS3 %c (0x%02x)\n", (char)cmd, cmd); +#endif + return TERMKEY_RES_NONE; + } + + *nbytep = introlen + 1; + + return TERMKEY_RES_KEY; +} + +static TermKeyResult peekkey_ctrlstring(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep) +{ + size_t str_end = introlen; + + while(str_end < tk->buffcount) { + if(CHARAT(str_end) == 0x07) // BEL + break; + if(CHARAT(str_end) == 0x9c) // ST + break; + if(CHARAT(str_end) == 0x1b && + (str_end + 1) < tk->buffcount && + CHARAT(str_end+1) == 0x5c) // ESC-prefixed ST + break; + + str_end++; + } + + if(str_end >= tk->buffcount) + return TERMKEY_RES_AGAIN; + +#ifdef DEBUG + fprintf(stderr, "Found a control string: %*s", + str_end - introlen, tk->buffer + tk->buffstart + introlen); +#endif + + *nbytep = str_end + 1; + if(CHARAT(str_end) == 0x1b) + (*nbytep)++; + + if(csi->saved_string) + free(csi->saved_string); + + size_t len = str_end - introlen; + + csi->saved_string_id++; + csi->saved_string = malloc(len + 1); + + strncpy(csi->saved_string, (char *)tk->buffer + tk->buffstart + introlen, len); + csi->saved_string[len] = 0; + + key->type = (CHARAT(introlen-1) & 0x1f) == 0x10 ? + TERMKEY_TYPE_DCS : TERMKEY_TYPE_OSC; + key->code.number = csi->saved_string_id; + key->modifiers = 0; + + return TERMKEY_RES_KEY; +} + +static TermKeyResult peekkey(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep) +{ + if(tk->buffcount == 0) + return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; + + TermKeyCsi *csi = info; + + switch(CHARAT(0)) { + case 0x1b: + if(tk->buffcount < 2) + return TERMKEY_RES_NONE; + + switch(CHARAT(1)) { + case 0x4f: // ESC-prefixed SS3 + return peekkey_ss3(tk, csi, 2, key, force, nbytep); + + case 0x50: // ESC-prefixed DCS + case 0x5d: // ESC-prefixed OSC + return peekkey_ctrlstring(tk, csi, 2, key, force, nbytep); + + case 0x5b: // ESC-prefixed CSI + return peekkey_csi(tk, csi, 2, key, force, nbytep); + } + + return TERMKEY_RES_NONE; + + case 0x8f: // SS3 + return peekkey_ss3(tk, csi, 1, key, force, nbytep); + + case 0x90: // DCS + case 0x9d: // OSC + return peekkey_ctrlstring(tk, csi, 1, key, force, nbytep); + + case 0x9b: // CSI + return peekkey_csi(tk, csi, 1, key, force, nbytep); + } + + return TERMKEY_RES_NONE; +} + +struct TermKeyDriver termkey_driver_csi = { + .name = "CSI", + + .new_driver = new_driver, + .free_driver = free_driver, + + .peekkey = peekkey, +}; + +TermKeyResult termkey_interpret_string(TermKey *tk, const TermKeyKey *key, const char **strp) +{ + struct TermKeyDriverNode *p; + for(p = tk->drivers; p; p = p->next) + if(p->driver == &termkey_driver_csi) + break; + + if(!p) + return TERMKEY_RES_NONE; + + if(key->type != TERMKEY_TYPE_DCS && + key->type != TERMKEY_TYPE_OSC) + return TERMKEY_RES_NONE; + + TermKeyCsi *csi = p->info; + + if(csi->saved_string_id != key->code.number) + return TERMKEY_RES_NONE; + + *strp = csi->saved_string; + + return TERMKEY_RES_KEY; +} diff --git a/src/termkey/driver-ti.c b/src/termkey/driver-ti.c new file mode 100644 index 0000000000..00a7066796 --- /dev/null +++ b/src/termkey/driver-ti.c @@ -0,0 +1,635 @@ +// we want strdup() +#define _XOPEN_SOURCE 600 + +#include "termkey.h" +#include "termkey-internal.h" + +#ifdef HAVE_UNIBILIUM +# include <unibilium.h> +#else +# include <curses.h> +# include <term.h> + +/* curses.h has just polluted our namespace. We want this back */ +# undef buttons +#endif + +#include <ctype.h> +#include <errno.h> +#include <stdbool.h> +#include <stdio.h> +#include <string.h> +#ifndef _WIN32 +# include <unistd.h> +#else +# include <io.h> +#endif +#include <sys/types.h> +#include <sys/stat.h> + +#define streq(a,b) (!strcmp(a,b)) + +#define MAX_FUNCNAME 9 + +static struct { + const char *funcname; + TermKeyType type; + TermKeySym sym; + int mods; +} funcs[] = +{ + /* THIS LIST MUST REMAIN SORTED! */ + { "backspace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BACKSPACE, 0 }, + { "begin", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 }, + { "beg", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 }, + { "btab", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB, TERMKEY_KEYMOD_SHIFT }, + { "cancel", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CANCEL, 0 }, + { "clear", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLEAR, 0 }, + { "close", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLOSE, 0 }, + { "command", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COMMAND, 0 }, + { "copy", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COPY, 0 }, + { "dc", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE, 0 }, + { "down", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN, 0 }, + { "end", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 0 }, + { "enter", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_ENTER, 0 }, + { "exit", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_EXIT, 0 }, + { "find", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND, 0 }, + { "help", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HELP, 0 }, + { "home", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 0 }, + { "ic", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT, 0 }, + { "left", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT, 0 }, + { "mark", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MARK, 0 }, + { "message", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MESSAGE, 0 }, + { "move", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MOVE, 0 }, + { "next", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 }, // Not quite, but it's the best we can do + { "npage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 }, + { "open", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPEN, 0 }, + { "options", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPTIONS, 0 }, + { "ppage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 }, + { "previous", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 }, // Not quite, but it's the best we can do + { "print", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PRINT, 0 }, + { "redo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REDO, 0 }, + { "reference", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFERENCE, 0 }, + { "refresh", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFRESH, 0 }, + { "replace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REPLACE, 0 }, + { "restart", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESTART, 0 }, + { "resume", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESUME, 0 }, + { "right", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT, 0 }, + { "save", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SAVE, 0 }, + { "select", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT, 0 }, + { "suspend", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SUSPEND, 0 }, + { "undo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UNDO, 0 }, + { "up", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP, 0 }, + { NULL }, +}; + +#ifdef HAVE_UNIBILIUM +static enum unibi_string unibi_lookup_str(const char *name) +{ + for(enum unibi_string ret = unibi_string_begin_+1; ret < unibi_string_end_; ret++) + if(streq(unibi_name_str(ret), name)) + return ret; + + return -1; +} + +static const char *unibi_get_str_by_name(const unibi_term *ut, const char *name) +{ + enum unibi_string idx = unibi_lookup_str(name); + if(idx == (enum unibi_string)-1) + return NULL; + + return unibi_get_str(ut, idx); +} +#endif + +/* To be efficient at lookups, we store the byte sequence => keyinfo mapping + * in a trie. This avoids a slow linear search through a flat list of + * sequences. Because it is likely most nodes will be very sparse, we optimise + * vector to store an extent map after the database is loaded. + */ + +typedef enum { + TYPE_KEY, + TYPE_ARR, +} trie_nodetype; + +struct trie_node { + trie_nodetype type; +}; + +struct trie_node_key { + trie_nodetype type; + struct keyinfo key; +}; + +struct trie_node_arr { + trie_nodetype type; + unsigned char min, max; /* INCLUSIVE endpoints of the extent range */ + struct trie_node *arr[]; /* dynamic size at allocation time */ +}; + +typedef struct { + TermKey *tk; + +#ifdef HAVE_UNIBILIUM + unibi_term *unibi; /* only valid until first 'start' call */ +#else + char *term; /* only valid until first 'start' call */ +#endif + + struct trie_node *root; + + char *start_string; + char *stop_string; +} TermKeyTI; + +static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node); + +static struct trie_node *new_node_key(TermKeyType type, TermKeySym sym, int modmask, int modset) +{ + struct trie_node_key *n = malloc(sizeof(*n)); + if(!n) + return NULL; + + n->type = TYPE_KEY; + + n->key.type = type; + n->key.sym = sym; + n->key.modifier_mask = modmask; + n->key.modifier_set = modset; + + return (struct trie_node*)n; +} + +static struct trie_node *new_node_arr(unsigned char min, unsigned char max) +{ + struct trie_node_arr *n = malloc(sizeof(*n) + ((int)max-min+1) * sizeof(n->arr[0])); + if(!n) + return NULL; + + n->type = TYPE_ARR; + n->min = min; n->max = max; + + int i; + for(i = min; i <= max; i++) + n->arr[i-min] = NULL; + + return (struct trie_node*)n; +} + +static struct trie_node *lookup_next(struct trie_node *n, unsigned char b) +{ + switch(n->type) { + case TYPE_KEY: + fprintf(stderr, "ABORT: lookup_next within a TYPE_KEY node\n"); + abort(); + case TYPE_ARR: + { + struct trie_node_arr *nar = (struct trie_node_arr*)n; + if(b < nar->min || b > nar->max) + return NULL; + return nar->arr[b - nar->min]; + } + } + + return NULL; // Never reached but keeps compiler happy +} + +static void free_trie(struct trie_node *n) +{ + swi |