From f9108378b7a7e08b48685f0a3ff4f7a3a14b56d6 Mon Sep 17 00:00:00 2001 From: dundargoc Date: Wed, 14 Aug 2024 15:52:51 +0200 Subject: [PATCH] refactor: adopt termkey and eliminate duplicate code Termkey is abandoned and it's now our code, so there's no reason not to treat it as such. An alternative approach could be to have a proper repo that we maintain such as with unibilium, although with this approach we can make a few assumptions that will allow us to remove more code. Also eliminate duplicate code from both termkey and libvterm. --- MAINTAIN.md | 1 - src/nvim/CMakeLists.txt | 55 +- src/nvim/os/win_defs.h | 3 - src/nvim/tui/input.c | 17 +- src/nvim/tui/input.h | 2 +- src/nvim/tui/termkey/README | 1 + src/{ => nvim/tui}/termkey/driver-csi.c | 619 ++++--- src/nvim/tui/termkey/driver-csi.h | 7 + src/{ => nvim/tui}/termkey/driver-ti.c | 525 +++--- src/nvim/tui/termkey/driver-ti.h | 7 + src/{ => nvim/tui}/termkey/termkey-internal.h | 79 +- src/nvim/tui/termkey/termkey.c | 1315 ++++++++++++++ src/nvim/tui/termkey/termkey.h | 10 + src/nvim/tui/termkey/termkey_defs.h | 199 ++ src/termkey/termkey.c | 1606 ----------------- src/termkey/termkey.h | 256 --- src/vterm/keyboard.c | 3 +- src/vterm/mouse.c | 2 +- src/vterm/screen.c | 5 +- src/vterm/utf8.h | 39 - 20 files changed, 2159 insertions(+), 2592 deletions(-) create mode 100644 src/nvim/tui/termkey/README rename src/{ => nvim/tui}/termkey/driver-csi.c (59%) create mode 100644 src/nvim/tui/termkey/driver-csi.h rename src/{ => nvim/tui}/termkey/driver-ti.c (50%) create mode 100644 src/nvim/tui/termkey/driver-ti.h rename src/{ => nvim/tui}/termkey/termkey-internal.h (53%) create mode 100644 src/nvim/tui/termkey/termkey.c create mode 100644 src/nvim/tui/termkey/termkey.h create mode 100644 src/nvim/tui/termkey/termkey_defs.h delete mode 100644 src/termkey/termkey.c delete mode 100644 src/termkey/termkey.h delete mode 100644 src/vterm/utf8.h diff --git a/MAINTAIN.md b/MAINTAIN.md index 4920074e36..f7dd8a1ef7 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -160,7 +160,6 @@ These dependencies are "vendored" (inlined), we must update the sources manually * Needs to be updated when LPeg is updated. * `src/bit.c`: only for PUC lua: port of `require'bit'` from luajit https://bitop.luajit.org/ * `runtime/lua/coxpcall.lua`: coxpcall (only needed for PUC lua, builtin to luajit) -* `src/termkey`: [libtermkey](https://github.com/neovim/libtermkey) Other dependencies -------------------------- diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 1b229c1d87..ab08f01e33 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -54,8 +54,6 @@ if(ENABLE_WASMTIME) target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME) 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) @@ -153,7 +151,7 @@ if(UNIX) endif() if(CMAKE_SYSTEM_NAME MATCHES "Windows") - target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN) + target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN WIN32_LEAN_AND_MEAN) target_link_libraries(main_lib INTERFACE netapi32) elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin") target_link_libraries(nvim_bin PRIVATE "-framework CoreServices") @@ -366,8 +364,8 @@ file(MAKE_DIRECTORY ${TOUCHES_DIR} ${GENERATED_DIR} ${GENERATED_INCLUDES_DIR}) file(GLOB NVIM_SOURCES CONFIGURE_DEPENDS *.c) file(GLOB NVIM_HEADERS CONFIGURE_DEPENDS *.h) -file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../termkey/*.c ../vterm/*.c) -file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../termkey/*.h ../vterm/*.h) +file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../vterm/*.c) +file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../vterm/*.h) file(GLOB NLUA0_SOURCES CONFIGURE_DEPENDS ../mpack/*.c) @@ -378,6 +376,15 @@ if(PREFER_LUA) target_compile_definitions(main_lib INTERFACE NVIM_VENDOR_BIT) endif() +# Inlined external projects, we don't maintain it. #9306 +if(MSVC) + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-wd4090;-wd4244;-wd4267") +else() + set_source_files_properties( + ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-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() + list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/nlua0.c) foreach(subdir @@ -386,6 +393,7 @@ foreach(subdir api/private msgpack_rpc tui + tui/termkey event eval lua @@ -407,49 +415,36 @@ endforeach() list(SORT NVIM_SOURCES) list(SORT NVIM_HEADERS) -list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS}) - foreach(sfile ${NVIM_SOURCES}) get_filename_component(f ${sfile} NAME) if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$") - list(APPEND to_remove ${sfile}) + list(REMOVE_ITEM NVIM_SOURCES ${sfile}) endif() if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$") - list(APPEND to_remove ${sfile}) + list(REMOVE_ITEM NVIM_SOURCES ${sfile}) endif() if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$") - list(APPEND to_remove ${sfile}) + list(REMOVE_ITEM NVIM_SOURCES ${sfile}) endif() if(NOT WIN32 AND ${f} MATCHES "^(os_win_console.c)$") - list(APPEND to_remove ${sfile}) + list(REMOVE_ITEM NVIM_SOURCES ${sfile}) endif() endforeach() -list(REMOVE_ITEM NVIM_SOURCES ${to_remove}) - foreach(hfile ${NVIM_HEADERS}) get_filename_component(f ${hfile} NAME) if(WIN32 AND ${f} MATCHES "^(unix_defs.h)$") - list(APPEND to_remove_h ${hfile}) + list(REMOVE_ITEM NVIM_HEADERS ${hfile}) endif() if(WIN32 AND ${f} MATCHES "^(pty_process_unix.h)$") - list(APPEND to_remove_h ${hfile}) + list(REMOVE_ITEM NVIM_HEADERS ${hfile}) endif() if(NOT WIN32 AND ${f} MATCHES "^(win_defs.h)$") - list(APPEND to_remove_h ${hfile}) + list(REMOVE_ITEM NVIM_HEADERS ${hfile}) endif() endforeach() -list(REMOVE_ITEM NVIM_HEADERS ${to_remove_h}) - -# xdiff, mpack, lua-cjson, termkey: inlined external project, we don't maintain it. #9306 -if(MSVC) - set_source_files_properties( - ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-wd4090;-wd4244;-wd4267") -else() - set_source_files_properties( - ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-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() +list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS}) # Log level (NVIM_LOG_DEBUG in log.h) if(CI_BUILD) @@ -849,7 +844,7 @@ endif() add_glob_target( TARGET lintc-clang-tidy COMMAND ${CLANG_TIDY_PRG} - FILES ${NVIM_SOURCES} ${NVIM_HEADERS} + FILES ${LINT_NVIM_SOURCES} FLAGS --quiet EXCLUDE ${EXCLUDE_CLANG_TIDY}) @@ -862,7 +857,7 @@ endif() add_glob_target( TARGET clang-analyzer COMMAND ${CLANG_TIDY_PRG} - FILES ${NVIM_SOURCES} ${NVIM_HEADERS} + FILES ${LINT_NVIM_SOURCES} FLAGS --quiet --checks=' -*, @@ -905,13 +900,13 @@ add_glob_target( TARGET lintc-uncrustify COMMAND ${UNCRUSTIFY_PRG} FLAGS -c ${UNCRUSTIFY_CONFIG} -q --check - FILES ${LINT_NVIM_SOURCES}) + FILES ${NVIM_SOURCES} ${NVIM_HEADERS}) add_glob_target( TARGET formatc COMMAND ${UNCRUSTIFY_PRG} FLAGS -c ${UNCRUSTIFY_CONFIG} --replace --no-backup - FILES ${LINT_NVIM_SOURCES}) + FILES ${NVIM_SOURCES} ${NVIM_HEADERS}) add_dependencies(lintc-uncrustify uncrustify_update_config) add_dependencies(formatc uncrustify_update_config) diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 024719806c..852059f78b 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -16,9 +16,6 @@ #include #include -// vterm.h defines an `unsigned int small` in a struct, triggering error C2632 -#undef small - // Windows does not have S_IFLNK but libuv defines it // and sets the flag for us when calling uv_fs_stat. #include diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index a6e27c9391..3eb8d4ba2e 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -7,24 +7,27 @@ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/event/loop.h" +#include "nvim/event/rstream.h" #include "nvim/event/stream.h" #include "nvim/macros_defs.h" #include "nvim/main.h" #include "nvim/map_defs.h" #include "nvim/memory.h" +#include "nvim/msgpack_rpc/channel.h" #include "nvim/option_vars.h" #include "nvim/os/os.h" #include "nvim/os/os_defs.h" #include "nvim/strings.h" #include "nvim/tui/input.h" #include "nvim/tui/input_defs.h" +#include "nvim/tui/termkey/driver-csi.h" +#include "nvim/tui/termkey/termkey.h" #include "nvim/tui/tui.h" #include "nvim/ui_client.h" + #ifdef MSWIN # include "nvim/os/os_win_console.h" #endif -#include "nvim/event/rstream.h" -#include "nvim/msgpack_rpc/channel.h" #define READ_STREAM_SIZE 0xfff @@ -261,7 +264,7 @@ static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen) static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key) { - const char *name = pmap_get(int)(&kitty_key_map, (int)key->code.codepoint); + const char *name = pmap_get(int)(&kitty_key_map, key->code.codepoint); if (name) { char buf[64]; size_t len = 0; @@ -598,7 +601,7 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) // contain, so just allocate enough space for a large upper bound TermKeyCsiParam params[16]; size_t nparams = 16; - unsigned long cmd; + unsigned cmd; if (termkey_interpret_csi(input->tk, key, params, &nparams, &cmd) != TERMKEY_RES_KEY) { return; } @@ -641,7 +644,7 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) case 't': if (nparams == 5) { // We only care about the first 3 parameters, and we ignore subparameters - long args[3]; + int 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; @@ -650,8 +653,8 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key) if (args[0] == 48) { // In-band resize event (DEC private mode 2048) - int height_chars = (int)args[1]; - int width_chars = (int)args[2]; + int height_chars = args[1]; + int width_chars = args[2]; tui_set_size(input->tui_data, width_chars, height_chars); ui_client_set_size(width_chars, height_chars); } diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h index 8d0c0c20e9..4c2baf908e 100644 --- a/src/nvim/tui/input.h +++ b/src/nvim/tui/input.h @@ -6,9 +6,9 @@ #include "nvim/event/defs.h" #include "nvim/tui/input_defs.h" // IWYU pragma: keep +#include "nvim/tui/termkey/termkey_defs.h" #include "nvim/tui/tui_defs.h" #include "nvim/types_defs.h" -#include "termkey/termkey.h" typedef enum { kKeyEncodingLegacy, ///< Legacy key encoding diff --git a/src/nvim/tui/termkey/README b/src/nvim/tui/termkey/README new file mode 100644 index 0000000000..fd081025fc --- /dev/null +++ b/src/nvim/tui/termkey/README @@ -0,0 +1 @@ +// Adapted from libtermkey: https://github.com/neovim/libtermkey diff --git a/src/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c similarity index 59% rename from src/termkey/driver-csi.c rename to src/nvim/tui/termkey/driver-csi.c index f9c39757b7..28c7eaccfd 100644 --- a/src/termkey/driver-csi.c +++ b/src/nvim/tui/termkey/driver-csi.c @@ -1,36 +1,37 @@ -#include "termkey.h" -#include "termkey-internal.h" - #include #include #include +#include "nvim/memory.h" +#include "nvim/tui/termkey/driver-csi.h" +#include "nvim/tui/termkey/termkey-internal.h" +#include "nvim/tui/termkey/termkey.h" +#include "nvim/tui/termkey/termkey_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/driver-csi.c.generated.h" +#endif + // 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, TermKeyCsiParam *params, int nparams); +typedef TermKeyResult CsiHandler(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, + int nparams); static CsiHandler *csi_handlers[64]; -/* - * Handler for CSI/SS3 cmd keys - */ +// Handler for CSI/SS3 cmd keys static struct keyinfo csi_ss3s[64]; -static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams) +static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, + TermKeyCsiParam *params, int nparams) { TermKeyResult result = TERMKEY_RES_KEY; - if(nparams > 1 && params[1].param != NULL) { - long arg = 0; + if (nparams > 1 && params[1].param != NULL) { + int arg = 0; result = termkey_interpret_csi_param(params[1], &arg, NULL, NULL); if (result != TERMKEY_RES_KEY) { return result; @@ -46,15 +47,17 @@ static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd, key->modifiers &= ~(csi_ss3s[cmd - 0x40].modifier_mask); key->modifiers |= csi_ss3s[cmd - 0x40].modifier_set; - if(key->code.sym == TERMKEY_SYM_UNKNOWN) + if (key->code.sym == TERMKEY_SYM_UNKNOWN) { result = TERMKEY_RES_NONE; + } return result; } -static void register_csi_ss3_full(TermKeyType type, TermKeySym sym, int modifier_set, int modifier_mask, unsigned char cmd) +static void register_csi_ss3_full(TermKeyType type, TermKeySym sym, int modifier_set, + int modifier_mask, unsigned char cmd) { - if(cmd < 0x40 || cmd >= 0x80) { + if (cmd < 0x40 || cmd >= 0x80) { return; } @@ -71,13 +74,10 @@ 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 - */ - +/// 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) { + if (cmd < 0x40 || cmd >= 0x80) { return; } @@ -88,23 +88,22 @@ static void register_ss3kpalt(TermKeyType type, TermKeySym sym, unsigned char cm ss3_kpalts[cmd - 0x40] = kpalt; } -/* - * Handler for CSI number ~ function keys - */ +// 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])) +#define NCSIFUNCS 35 // This value must be increased if more CSI function keys are added +static struct keyinfo csifuncs[NCSIFUNCS]; -static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams) +static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, + int nparams) { if (nparams == 0) { return TERMKEY_RES_NONE; } TermKeyResult result = TERMKEY_RES_KEY; - long args[3]; + int args[3]; - if(nparams > 1 && params[1].param != NULL) { + 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; @@ -122,7 +121,7 @@ static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermK return result; } - if(args[0] == 27 && nparams > 2 && params[2].param != NULL) { + 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; @@ -131,17 +130,16 @@ static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermK int mod = key->modifiers; (*tk->method.emit_codepoint)(tk, args[2], key); key->modifiers |= mod; - } - else if(args[0] >= 0 && args[0] < NCSIFUNCS) { + } 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 + } else { key->code.sym = TERMKEY_SYM_UNKNOWN; + } - if(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 @@ -153,7 +151,7 @@ static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermK static void register_csifunc(TermKeyType type, TermKeySym sym, int number) { - if(number >= NCSIFUNCS) { + if (number >= NCSIFUNCS) { return; } @@ -165,82 +163,79 @@ static void register_csifunc(TermKeyType type, TermKeySym sym, int number) csi_handlers['~' - 0x40] = &handle_csifunc; } -/* - * Handler for CSI u extended Unicode keys - */ - -static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams) +/// Handler for CSI u extended Unicode keys +static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, + int nparams) { - switch(cmd) { - case 'u': { - 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) { + switch (cmd) { + case 'u': { + int args[2]; + if (nparams > 1 && params[1].param != NULL) { + int subparam = 0; + size_t nsubparams = 1; + if (termkey_interpret_csi_param(params[1], &args[1], &subparam, + &nsubparams) != TERMKEY_RES_KEY) { return TERMKEY_RES_ERROR; } - int mod = key->modifiers; - key->type = TERMKEY_TYPE_KEYSYM; - (*tk->method.emit_codepoint)(tk, args[0], key); - key->modifiers |= mod; + if (nsubparams > 0 && subparam != 1) { + // Not a press event. Ignore for now + return TERMKEY_RES_NONE; + } - return TERMKEY_RES_KEY; + key->modifiers = args[1] - 1; + } else { + key->modifiers = 0; } - default: - return TERMKEY_RES_NONE; + + 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, args[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, TermKeyCsiParam *params, int nparams) +/// 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, TermKeyCsiParam *params, + int nparams) { int initial = cmd >> 8; cmd &= 0xff; - switch(cmd) { - case 'M': - case 'm': - break; - default: - return TERMKEY_RES_NONE; + switch (cmd) { + case 'M': + case 'm': + break; + default: + return TERMKEY_RES_NONE; } if (nparams < 3) { return TERMKEY_RES_NONE; } - long args[3]; + int 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 + if (!initial) { // rxvt protocol key->type = TERMKEY_TYPE_MOUSE; - key->code.mouse[0] = args[0]; + key->code.mouse[0] = (char)args[0]; - key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; + key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; key->code.mouse[0] &= ~0x1c; termkey_key_set_linecol(key, args[1], args[2]); @@ -248,17 +243,18 @@ static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, TermKey return TERMKEY_RES_KEY; } - if(initial == '<') { // SGR protocol + if (initial == '<') { // SGR protocol key->type = TERMKEY_TYPE_MOUSE; - key->code.mouse[0] = args[0]; + key->code.mouse[0] = (char)args[0]; - key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; + key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; key->code.mouse[0] &= ~0x1c; termkey_key_set_linecol(key, args[1], args[2]); - if(cmd == 'm') // release + if (cmd == 'm') { // release key->code.mouse[3] |= 0x80; + } return TERMKEY_RES_KEY; } @@ -266,28 +262,32 @@ static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, TermKey return TERMKEY_RES_NONE; } -TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKeyMouseEvent *event, int *button, int *line, int *col) +TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKeyMouseEvent *event, + int *button, int *line, int *col) { - if(key->type != TERMKEY_TYPE_MOUSE) + if (key->type != TERMKEY_TYPE_MOUSE) { return TERMKEY_RES_NONE; + } - if(button) + if (button) { *button = 0; + } termkey_key_get_linecol(key, line, col); - if(!event) + if (!event) { return TERMKEY_RES_KEY; + } int btn = 0; - int code = key->code.mouse[0]; + int code = (unsigned char)key->code.mouse[0]; int drag = code & 0x20; code &= ~0x3c; - switch(code) { + switch (code) { case 0: case 1: case 2: @@ -312,119 +312,128 @@ TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKe *event = TERMKEY_MOUSE_UNKNOWN; } - if(button) + if (button) { *button = btn; + } - if(key->code.mouse[3] & 0x80) + 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 - */ - -static TermKeyResult handle_csi_R(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, int nparams) +/// Handler for CSI ? R position reports +/// A plain CSI R with no arguments is probably actually +static TermKeyResult handle_csi_R(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params, + int nparams) { - switch(cmd) { - case 'R'|'?'<<8: - 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; + switch (cmd) { + case 'R'|'?' << 8: + if (nparams < 2) { + return TERMKEY_RES_NONE; } - if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) { - return TERMKEY_RES_ERROR; - } + int args[2]; + if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) { + return TERMKEY_RES_ERROR; + } - key->type = TERMKEY_TYPE_POSITION; - termkey_key_set_linecol(key, args[1], args[0]); - return TERMKEY_RES_KEY; + if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) { + return TERMKEY_RES_ERROR; + } - default: - return handle_csi_ss3_full(tk, key, cmd, params, nparams); + key->type = TERMKEY_TYPE_POSITION; + termkey_key_set_linecol(key, args[1], args[0]); + return TERMKEY_RES_KEY; + + default: + return handle_csi_ss3_full(tk, key, cmd, params, nparams); } } TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int *line, int *col) { - if(key->type != TERMKEY_TYPE_POSITION) + 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, TermKeyCsiParam *params, int nparams) +/// Handler for CSI $y mode status reports +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(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] = args[0] >> 8; - key->code.mouse[2] = args[0] & 0xff; - key->code.mouse[3] = args[1]; - return TERMKEY_RES_KEY; - - default: + switch (cmd) { + case 'y'|'$' << 16: + case 'y'|'$' << 16 | '?' << 8: + if (nparams < 2) { return TERMKEY_RES_NONE; + } + + int 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] = (char)(cmd >> 8); + key->code.mouse[1] = (char)(args[0] >> 8); + key->code.mouse[2] = (char)(args[0] & 0xff); + key->code.mouse[3] = (char)args[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) +TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, int *initial, + int *mode, int *value) { - if(key->type != TERMKEY_TYPE_MODEREPORT) + if (key->type != TERMKEY_TYPE_MODEREPORT) { return TERMKEY_RES_NONE; + } - if(initial) - *initial = key->code.mouse[0]; + if (initial) { + *initial = (unsigned char)key->code.mouse[0]; + } - if(mode) + if (mode) { *mode = ((uint8_t)key->code.mouse[1] << 8) | (uint8_t)key->code.mouse[2]; + } - if(value) - *value = key->code.mouse[3]; + if (value) { + *value = (unsigned char)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, TermKeyCsiParam params[], 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 *commandp) { size_t csi_end = introlen; - while(csi_end < tk->buffcount) { - if(CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) + while (csi_end < tk->buffcount) { + if (CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) { break; + } csi_end++; } - if(csi_end >= tk->buffcount) + if (csi_end >= tk->buffcount) { return TERMKEY_RES_AGAIN; + } unsigned char cmd = CHARAT(csi_end); *commandp = cmd; @@ -435,66 +444,69 @@ static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len, Te size_t p = introlen; // See if there is an initial byte - if(CHARAT(p) >= '<' && CHARAT(p) <= '?') { - *commandp |= (CHARAT(p) << 8); + if (CHARAT(p) >= '<' && CHARAT(p) <= '?') { + *commandp |= (unsigned)(CHARAT(p) << 8); p++; } // Now attempt to parse out up number;number;... separated values - while(p < csi_end) { + while (p < csi_end) { unsigned char c = CHARAT(p); - if(c >= '0' && c < ';') { - if(!present) { + if (c >= '0' && c < ';') { + if (!present) { params[argi].param = &CHARAT(p); present = 1; } - } - else if(c == ';') { - if(!present) { + } else if (c == ';') { + if (!present) { params[argi].param = NULL; params[argi].length = 0; } else { - params[argi].length = &CHARAT(p) - params[argi].param; + params[argi].length = (size_t)(&CHARAT(p) - params[argi].param); } present = 0; argi++; - if(argi > 16) + if (argi > 16) { break; - } - else if(c >= 0x20 && c <= 0x2f) { - *commandp |= c << 16; + } + } else if (c >= 0x20 && c <= 0x2f) { + *commandp |= (unsigned)(c << 16); break; } p++; } - if(present) { - params[argi].length = &CHARAT(p) - params[argi].param; + if (present) { + params[argi].length = (size_t)(&CHARAT(p) - params[argi].param); argi++; } - *nargs = argi; + *nargs = (size_t)argi; *csi_len = csi_end + 1; return TERMKEY_RES_KEY; } -TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, TermKeyCsiParam params[], size_t *nparams, unsigned long *cmd) +TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, TermKeyCsiParam params[], + size_t *nparams, unsigned *cmd) { size_t dummy; - if(tk->hightide == 0) + if (tk->hightide == 0) { return TERMKEY_RES_NONE; - if(key->type != TERMKEY_TYPE_UNKNOWN_CSI) + } + if (key->type != TERMKEY_TYPE_UNKNOWN_CSI) { return TERMKEY_RES_NONE; + } return parse_csi(tk, 0, &dummy, params, nparams, cmd); } -TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, long *paramp, long subparams[], size_t *nsubparams) +TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, int *paramp, int subparams[], + size_t *nsubparams) { if (paramp == NULL) { return TERMKEY_RES_ERROR; @@ -508,7 +520,7 @@ TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, long *paramp, l return TERMKEY_RES_KEY; } - long arg = 0; + int arg = 0; size_t i = 0; size_t capacity = nsubparams ? *nsubparams : 0; size_t length = 0; @@ -547,14 +559,15 @@ static int register_keys(void) { int i; - for(i = 0; i < 64; i++) { + for (i = 0; i < 64; i++) { csi_ss3s[i].sym = TERMKEY_SYM_UNKNOWN; - ss3s[i].sym = TERMKEY_SYM_UNKNOWN; + ss3s[i].sym = TERMKEY_SYM_UNKNOWN; ss3_kpalts[i] = 0; } - for(i = 0; i < NCSIFUNCS; i++) + 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'); @@ -568,7 +581,8 @@ static int register_keys(void) 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_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', '='); @@ -632,15 +646,15 @@ static int register_keys(void) return 1; } -static void *new_driver(TermKey *tk, const char *term) +void *new_driver_csi(TermKey *tk, const char *term) { - if(!keyinfo_initialised) - if(!register_keys()) + if (!keyinfo_initialised) { + if (!register_keys()) { return NULL; + } + } - TermKeyCsi *csi = malloc(sizeof *csi); - if(!csi) - return NULL; + TermKeyCsi *csi = xmalloc(sizeof *csi); csi->tk = tk; csi->saved_string_id = 0; @@ -649,28 +663,31 @@ static void *new_driver(TermKey *tk, const char *term) return csi; } -static void free_driver(void *info) +void free_driver_csi(void *info) { TermKeyCsi *csi = info; - if(csi->saved_string) - free(csi->saved_string); + if (csi->saved_string) { + xfree(csi->saved_string); + } - free(csi); + xfree(csi); } -static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep) +static TermKeyResult peekkey_csi_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, + int force, size_t *nbytep) { size_t csi_len; size_t nparams = 16; TermKeyCsiParam params[16]; - unsigned long cmd; + unsigned cmd; TermKeyResult ret = parse_csi(tk, introlen, &csi_len, params, &nparams, &cmd); - if(ret == TERMKEY_RES_AGAIN) { - if(!force) + if (ret == TERMKEY_RES_AGAIN) { + if (!force) { return TERMKEY_RES_AGAIN; + } (*tk->method.emit_codepoint)(tk, '[', key); key->modifiers |= TERMKEY_KEYMOD_ALT; @@ -678,7 +695,7 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, return TERMKEY_RES_KEY; } - if(cmd == 'M' && nparams < 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; @@ -687,8 +704,9 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, tk->buffstart -= csi_len; tk->buffcount += csi_len; - if(mouse_result == TERMKEY_RES_KEY) + if (mouse_result == TERMKEY_RES_KEY) { *nbytep += csi_len; + } return mouse_result; } @@ -696,35 +714,38 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, 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, params, nparams); + if (csi_handlers[(cmd & 0xff) - 0x40]) { + result = (*csi_handlers[(cmd & 0xff) - 0x40])(tk, key, (int)cmd, params, (int)nparams); + } - if(result == TERMKEY_RES_NONE) { + 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; + 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->code.number = (int)cmd; key->modifiers = 0; tk->hightide = csi_len - introlen; - *nbytep = introlen; // Do not yet eat the data bytes + *nbytep = introlen; // Do not yet eat the data bytes return TERMKEY_RES_KEY; } @@ -732,11 +753,13 @@ static TermKeyResult peekkey_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, return result; } -static TermKeyResult peekkey_ss3(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep) +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) + if (tk->buffcount < introlen + 1) { + if (!force) { return TERMKEY_RES_AGAIN; + } (*tk->method.emit_codepoint)(tk, 'O', key); key->modifiers |= TERMKEY_KEYMOD_ALT; @@ -746,30 +769,30 @@ static TermKeyResult peekkey_ss3(TermKey *tk, TermKeyCsi *csi, size_t introlen, unsigned char cmd = CHARAT(introlen); - if(cmd < 0x40 || cmd >= 0x80) + 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]) { + 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->code.codepoint = (unsigned char)ss3_kpalts[cmd - 0x40]; key->modifiers = 0; - key->utf8[0] = key->code.codepoint; + key->utf8[0] = (char)key->code.codepoint; key->utf8[1] = 0; - } - else { + } 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) { + if (key->code.sym == TERMKEY_SYM_UNKNOWN) { #ifdef DEBUG fprintf(stderr, "CSI: Unknown SS3 %c (0x%02x)\n", (char)cmd, cmd); #endif @@ -781,123 +804,99 @@ static TermKeyResult peekkey_ss3(TermKey *tk, TermKeyCsi *csi, size_t introlen, return TERMKEY_RES_KEY; } -static TermKeyResult peekkey_ctrlstring(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key, int force, size_t *nbytep) +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 + while (str_end < tk->buffcount) { + if (CHARAT(str_end) == 0x07) { // BEL break; - if(CHARAT(str_end) == 0x9c) // ST + } + 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 + } + 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) + 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); + str_end - introlen, tk->buffer + tk->buffstart + introlen); #endif *nbytep = str_end + 1; - if(CHARAT(str_end) == 0x1b) + if (CHARAT(str_end) == 0x1b) { (*nbytep)++; + } - if(csi->saved_string) - free(csi->saved_string); + if (csi->saved_string) { + xfree(csi->saved_string); + } size_t len = str_end - introlen; csi->saved_string_id++; - csi->saved_string = malloc(len + 1); + csi->saved_string = xmalloc(len + 1); - strncpy(csi->saved_string, (char *)tk->buffer + tk->buffstart + introlen, len); + strncpy(csi->saved_string, (char *)tk->buffer + tk->buffstart + introlen, len); // NOLINT(runtime/printf) csi->saved_string[len] = 0; - key->type = (CHARAT(introlen-1) & 0x1f) == 0x10 ? - TERMKEY_TYPE_DCS : TERMKEY_TYPE_OSC; + 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) +TermKeyResult peekkey_csi(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep) { - if(tk->buffcount == 0) + 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); - } - + switch (CHARAT(0)) { + case 0x1b: + if (tk->buffcount < 2) { return TERMKEY_RES_NONE; + } - case 0x8f: // SS3 - return peekkey_ss3(tk, csi, 1, key, force, nbytep); + switch (CHARAT(1)) { + case 0x4f: // ESC-prefixed SS3 + return peekkey_ss3(tk, csi, 2, key, force, nbytep); - case 0x90: // DCS - case 0x9d: // OSC - return peekkey_ctrlstring(tk, csi, 1, key, force, nbytep); + case 0x50: // ESC-prefixed DCS + case 0x5d: // ESC-prefixed OSC + return peekkey_ctrlstring(tk, csi, 2, key, force, nbytep); - case 0x9b: // CSI - return peekkey_csi(tk, csi, 1, key, force, nbytep); + case 0x5b: // ESC-prefixed CSI + return peekkey_csi_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_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/nvim/tui/termkey/driver-csi.h b/src/nvim/tui/termkey/driver-csi.h new file mode 100644 index 0000000000..0abd8b5c2e --- /dev/null +++ b/src/nvim/tui/termkey/driver-csi.h @@ -0,0 +1,7 @@ +#pragma once + +#include "nvim/tui/termkey/termkey_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/driver-csi.h.generated.h" +#endif diff --git a/src/termkey/driver-ti.c b/src/nvim/tui/termkey/driver-ti.c similarity index 50% rename from src/termkey/driver-ti.c rename to src/nvim/tui/termkey/driver-ti.c index 1f8ee10808..09c6a35004 100644 --- a/src/termkey/driver-ti.c +++ b/src/nvim/tui/termkey/driver-ti.c @@ -1,33 +1,28 @@ -// we want strdup() -#define _XOPEN_SOURCE 600 - -#include "termkey.h" -#include "termkey-internal.h" - -#ifdef HAVE_UNIBILIUM -# include -#else -# include -# include - -/* curses.h has just polluted our namespace. We want this back */ -# undef buttons -#endif - #include #include #include #include #include +#include +#include +#include + +#include "nvim/memory.h" +#include "nvim/tui/termkey/driver-ti.h" +#include "nvim/tui/termkey/termkey-internal.h" +#include "nvim/tui/termkey/termkey.h" + #ifndef _WIN32 # include #else # include #endif -#include -#include -#define streq(a,b) (!strcmp(a,b)) +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/driver-ti.c.generated.h" +#endif + +#define streq(a, b) (!strcmp(a, b)) #define MAX_FUNCNAME 9 @@ -36,9 +31,8 @@ static struct { TermKeyType type; TermKeySym sym; int mods; -} funcs[] = -{ - /* THIS LIST MUST REMAIN SORTED! */ +} 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 }, @@ -61,12 +55,12 @@ static struct { { "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 + { "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 + { "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 }, @@ -80,34 +74,34 @@ static struct { { "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 }, + { NULL, 0, 0, 0 }, }; -#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)) + 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; + return (enum unibi_string)-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) + 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. - */ +// 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, @@ -125,126 +119,110 @@ struct trie_node_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 */ + 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; + struct trie_node_key *n = xmalloc(sizeof(*n)); n->type = TYPE_KEY; n->key.type = type; - n->key.sym = sym; + n->key.sym = sym; n->key.modifier_mask = modmask; - n->key.modifier_set = modset; + n->key.modifier_set = modset; - return (struct trie_node*)n; + 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; + struct trie_node_arr *n = xmalloc(sizeof(*n) + (max - min + 1) * sizeof(n->arr[0])); n->type = TYPE_ARR; n->min = min; n->max = max; int i; - for(i = min; i <= max; i++) - n->arr[i-min] = NULL; + for (i = min; i <= max; i++) { + n->arr[i - min] = NULL; + } - return (struct trie_node*)n; + return (struct trie_node *)n; } static struct trie_node *lookup_next(struct trie_node *n, unsigned char b) { - switch(n->type) { + 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]; + 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 + return NULL; // Never reached but keeps compiler happy } static void free_trie(struct trie_node *n) { - switch(n->type) { + switch (n->type) { case TYPE_KEY: break; - case TYPE_ARR: - { - struct trie_node_arr *nar = (struct trie_node_arr*)n; - int i; - for(i = nar->min; i <= nar->max; i++) - if(nar->arr[i - nar->min]) - free_trie(nar->arr[i - nar->min]); - break; + case TYPE_ARR: { + struct trie_node_arr *nar = (struct trie_node_arr *)n; + int i; + for (i = nar->min; i <= nar->max; i++) { + if (nar->arr[i - nar->min]) { + free_trie(nar->arr[i - nar->min]); + } } + break; + } } - free(n); + xfree(n); } static struct trie_node *compress_trie(struct trie_node *n) { - if(!n) + if (!n) { return NULL; + } - switch(n->type) { + switch (n->type) { case TYPE_KEY: return n; - case TYPE_ARR: - { - struct trie_node_arr *nar = (struct trie_node_arr*)n; - unsigned char min, max; - // Find the real bounds - for(min = 0; !nar->arr[min]; min++) - if(min == 255 && !nar->arr[min]) { - free(nar); - return new_node_arr(1, 0); - } - - for(max = 0xff; !nar->arr[max]; max--) - ; - - struct trie_node_arr *new = (struct trie_node_arr*)new_node_arr(min, max); - int i; - for(i = min; i <= max; i++) - new->arr[i - min] = compress_trie(nar->arr[i]); - - free(nar); - return (struct trie_node*)new; + case TYPE_ARR: { + struct trie_node_arr *nar = (struct trie_node_arr *)n; + unsigned char min, max; + // Find the real bounds + for (min = 0; !nar->arr[min]; min++) { + if (min == 255 && !nar->arr[min]) { + xfree(nar); + return new_node_arr(1, 0); + } } + + for (max = 0xff; !nar->arr[max]; max--) {} + + struct trie_node_arr *new = (struct trie_node_arr *)new_node_arr(min, max); + int i; + for (i = min; i <= max; i++) { + new->arr[i - min] = compress_trie(nar->arr[i]); + } + + xfree(nar); + return (struct trie_node *)new; + } } return n; @@ -254,21 +232,20 @@ static bool try_load_terminfo_key(TermKeyTI *ti, const char *name, struct keyinf { const char *value = NULL; -#ifdef HAVE_UNIBILIUM - if(ti->unibi) + if (ti->unibi) { value = unibi_get_str_by_name(ti->unibi, name); -#else - if(ti->term) - value = tigetstr(name); -#endif + } - if(ti->tk->ti_getstr_hook) + if (ti->tk->ti_getstr_hook) { value = (ti->tk->ti_getstr_hook)(name, value, ti->tk->ti_getstr_hook_data); + } - if(!value || value == (char*)-1 || !value[0]) + if (!value || value == (char *)-1 || !value[0]) { return false; + } - struct trie_node *node = new_node_key(info->type, info->sym, info->modifier_mask, info->modifier_set); + struct trie_node *node = new_node_key(info->type, info->sym, info->modifier_mask, + info->modifier_set); insert_seq(ti, value, node); return true; @@ -278,288 +255,257 @@ static int load_terminfo(TermKeyTI *ti) { int i; -#ifdef HAVE_UNIBILIUM unibi_term *unibi = ti->unibi; -#else - { - int err; - - /* Have to cast away the const. But it's OK - we know terminfo won't really - * modify term */ - if(setupterm((char*)ti->term, 1, &err) != OK) - return 0; - } -#endif ti->root = new_node_arr(0, 0xff); - if(!ti->root) + if (!ti->root) { return 0; + } - /* First the regular key strings - */ - for(i = 0; funcs[i].funcname; i++) { + // First the regular key strings + for (i = 0; funcs[i].funcname; i++) { char name[MAX_FUNCNAME + 5 + 1]; - sprintf(name, "key_%s", funcs[i].funcname); - if(!try_load_terminfo_key(ti, name, &(struct keyinfo){ - .type = funcs[i].type, - .sym = funcs[i].sym, - .modifier_mask = funcs[i].mods, - .modifier_set = funcs[i].mods, - })) + sprintf(name, "key_%s", funcs[i].funcname); // NOLINT(runtime/printf) + if (!try_load_terminfo_key(ti, name, &(struct keyinfo){ + .type = funcs[i].type, + .sym = funcs[i].sym, + .modifier_mask = funcs[i].mods, + .modifier_set = funcs[i].mods, + })) { continue; + } - /* Maybe it has a shifted version */ - sprintf(name, "key_s%s", funcs[i].funcname); + // Maybe it has a shifted version + sprintf(name, "key_s%s", funcs[i].funcname); // NOLINT(runtime/printf) try_load_terminfo_key(ti, name, &(struct keyinfo){ - .type = funcs[i].type, - .sym = funcs[i].sym, - .modifier_mask = funcs[i].mods | TERMKEY_KEYMOD_SHIFT, - .modifier_set = funcs[i].mods | TERMKEY_KEYMOD_SHIFT, + .type = funcs[i].type, + .sym = funcs[i].sym, + .modifier_mask = funcs[i].mods | TERMKEY_KEYMOD_SHIFT, + .modifier_set = funcs[i].mods | TERMKEY_KEYMOD_SHIFT, }); } - /* Now the F keys - */ - for(i = 1; i < 255; i++) { + // Now the F keys + for (i = 1; i < 255; i++) { char name[9]; - sprintf(name, "key_f%d", i); - if(!try_load_terminfo_key(ti, name, &(struct keyinfo){ - .type = TERMKEY_TYPE_FUNCTION, - .sym = i, - .modifier_mask = 0, - .modifier_set = 0, - })) + sprintf(name, "key_f%d", i); // NOLINT(runtime/printf) + if (!try_load_terminfo_key(ti, name, &(struct keyinfo){ + .type = TERMKEY_TYPE_FUNCTION, + .sym = i, + .modifier_mask = 0, + .modifier_set = 0, + })) { break; + } } - /* Finally mouse mode */ + // Finally mouse mode { const char *value = NULL; -#ifdef HAVE_UNIBILIUM - if(ti->unibi) + if (ti->unibi) { value = unibi_get_str_by_name(ti->unibi, "key_mouse"); -#else - if(ti->term) - value = tigetstr("key_mouse"); -#endif + } - if(ti->tk->ti_getstr_hook) + if (ti->tk->ti_getstr_hook) { value = (ti->tk->ti_getstr_hook)("key_mouse", value, ti->tk->ti_getstr_hook_data); + } - /* Some terminfos (e.g. xterm-1006) claim a different key_mouse that won't - * give X10 encoding. We'll only accept this if it's exactly "\e[M" - */ - if(value && streq(value, "\x1b[M")) { + // Some terminfos (e.g. xterm-1006) claim a different key_mouse that won't + // give X10 encoding. We'll only accept this if it's exactly "\e[M" + if (value && streq(value, "\x1b[M")) { struct trie_node *node = new_node_key(TERMKEY_TYPE_MOUSE, 0, 0, 0); insert_seq(ti, value, node); } } - /* Take copies of these terminfo strings, in case we build multiple termkey - * instances for multiple different termtypes, and it's different by the - * time we want to use it - */ -#ifdef HAVE_UNIBILIUM - const char *keypad_xmit = unibi ? - unibi_get_str(unibi, unibi_keypad_xmit) : - NULL; -#endif + // Take copies of these terminfo strings, in case we build multiple termkey + // instances for multiple different termtypes, and it's different by the + // time we want to use it + const char *keypad_xmit = unibi + ? unibi_get_str(unibi, unibi_keypad_xmit) + : NULL; - if(keypad_xmit) - ti->start_string = strdup(keypad_xmit); - else + if (keypad_xmit) { + ti->start_string = xstrdup(keypad_xmit); + } else { ti->start_string = NULL; + } -#ifdef HAVE_UNIBILIUM - const char *keypad_local = unibi ? - unibi_get_str(unibi, unibi_keypad_local) : - NULL; -#endif + const char *keypad_local = unibi + ? unibi_get_str(unibi, unibi_keypad_local) + : NULL; - if(keypad_local) - ti->stop_string = strdup(keypad_local); - else + if (keypad_local) { + ti->stop_string = xstrdup(keypad_local); + } else { ti->stop_string = NULL; + } -#ifdef HAVE_UNIBILIUM - if(unibi) + if (unibi) { unibi_destroy(unibi); + } ti->unibi = NULL; -#else - if(ti->term) - free(ti->term); - - ti->term = NULL; -#endif ti->root = compress_trie(ti->root); return 1; } -static void *new_driver(TermKey *tk, const char *term) +void *new_driver_ti(TermKey *tk, const char *term) { - TermKeyTI *ti = malloc(sizeof *ti); - if(!ti) - return NULL; + TermKeyTI *ti = xmalloc(sizeof *ti); ti->tk = tk; ti->root = NULL; ti->start_string = NULL; ti->stop_string = NULL; -#ifdef HAVE_UNIBILIUM ti->unibi = unibi_from_term(term); int saved_errno = errno; - if(!ti->unibi && saved_errno != ENOENT) { - free(ti); + if (!ti->unibi && saved_errno != ENOENT) { + xfree(ti); return NULL; } - /* ti->unibi may be NULL if errno == ENOENT. That means the terminal wasn't - * known. Lets keep going because if we get getstr hook that might invent - * new strings for us - */ -#else - { - int err; - - ti->term = NULL; - - /* Have to cast away the const. But it's OK - we know terminfo won't really - * modify term */ - if(setupterm((char*)term, 1, &err) == OK) - ti->term = strdup(term); - } -#endif + // ti->unibi may be NULL if errno == ENOENT. That means the terminal wasn't + // known. Lets keep going because if we get getstr hook that might invent + // new strings for us return ti; } -static int start_driver(TermKey *tk, void *info) +int start_driver_ti(TermKey *tk, void *info) { TermKeyTI *ti = info; struct stat statbuf; char *start_string; size_t len; - if(!ti->root) + if (!ti->root) { load_terminfo(ti); + } start_string = ti->start_string; - if(tk->fd == -1 || !start_string) + if (tk->fd == -1 || !start_string) { return 1; + } - /* The terminfo database will contain keys in application cursor key mode. - * We may need to enable that mode - */ + // The terminfo database will contain keys in application cursor key mode. + // We may need to enable that mode - /* There's no point trying to write() to a pipe */ - if(fstat(tk->fd, &statbuf) == -1) + // There's no point trying to write() to a pipe + if (fstat(tk->fd, &statbuf) == -1) { return 0; + } #ifndef _WIN32 - if(S_ISFIFO(statbuf.st_mode)) + if (S_ISFIFO(statbuf.st_mode)) { return 1; + } #endif // Can't call putp or tputs because they suck and don't give us fd control len = strlen(start_string); - while(len) { - size_t written = write(tk->fd, start_string, len); - if(written == -1) + while (len) { + size_t written = (size_t)write(tk->fd, start_string, (unsigned)len); + if (written == (size_t)-1) { return 0; + } start_string += written; len -= written; } return 1; } -static int stop_driver(TermKey *tk, void *info) +int stop_driver_ti(TermKey *tk, void *info) { TermKeyTI *ti = info; struct stat statbuf; char *stop_string = ti->stop_string; size_t len; - if(tk->fd == -1 || !stop_string) + if (tk->fd == -1 || !stop_string) { return 1; + } - /* There's no point trying to write() to a pipe */ - if(fstat(tk->fd, &statbuf) == -1) + // There's no point trying to write() to a pipe + if (fstat(tk->fd, &statbuf) == -1) { return 0; + } #ifndef _WIN32 - if(S_ISFIFO(statbuf.st_mode)) + if (S_ISFIFO(statbuf.st_mode)) { return 1; + } #endif - /* The terminfo database will contain keys in application cursor key mode. - * We may need to enable that mode - */ + // The terminfo database will contain keys in application cursor key mode. + // We may need to enable that mode // Can't call putp or tputs because they suck and don't give us fd control len = strlen(stop_string); - while(len) { - size_t written = write(tk->fd, stop_string, len); - if(written == -1) + while (len) { + size_t written = (size_t)write(tk->fd, stop_string, (unsigned)len); + if (written == (size_t)-1) { return 0; + } stop_string += written; len -= written; } return 1; } -static void free_driver(void *info) +void free_driver_ti(void *info) { TermKeyTI *ti = info; free_trie(ti->root); - if(ti->start_string) - free(ti->start_string); + if (ti->start_string) { + xfree(ti->start_string); + } - if(ti->stop_string) - free(ti->stop_string); + if (ti->stop_string) { + xfree(ti->stop_string); + } -#ifdef HAVE_UNIBILIUM - if(ti->unibi) + if (ti->unibi) { unibi_destroy(ti->unibi); -#else - if(ti->term) - free(ti->term); -#endif + } - free(ti); + xfree(ti); } #define CHARAT(i) (tk->buffer[tk->buffstart + (i)]) -static TermKeyResult peekkey(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep) +TermKeyResult peekkey_ti(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep) { TermKeyTI *ti = info; - if(tk->buffcount == 0) + if (tk->buffcount == 0) { return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; + } struct trie_node *p = ti->root; - unsigned int pos = 0; - while(pos < tk->buffcount) { + unsigned pos = 0; + while (pos < tk->buffcount) { p = lookup_next(p, CHARAT(pos)); - if(!p) + if (!p) { break; + } pos++; - if(p->type != TYPE_KEY) + if (p->type != TYPE_KEY) { continue; + } - struct trie_node_key *nk = (struct trie_node_key*)p; - if(nk->key.type == TERMKEY_TYPE_MOUSE) { + struct trie_node_key *nk = (struct trie_node_key *)p; + if (nk->key.type == TERMKEY_TYPE_MOUSE) { tk->buffstart += pos; tk->buffcount -= pos; @@ -568,14 +514,15 @@ static TermKeyResult peekkey(TermKey *tk, void *info, TermKeyKey *key, int force tk->buffstart -= pos; tk->buffcount += pos; - if(mouse_result == TERMKEY_RES_KEY) + if (mouse_result == TERMKEY_RES_KEY) { *nbytep += pos; + } return mouse_result; } - key->type = nk->key.type; - key->code.sym = nk->key.sym; + key->type = nk->key.type; + key->code.sym = nk->key.sym; key->modifiers = nk->key.modifier_set; *nbytep = pos; return TERMKEY_RES_KEY; @@ -583,8 +530,9 @@ static TermKeyResult peekkey(TermKey *tk, void *info, TermKeyKey *key, int force // If p is not NULL then we hadn't walked off the end yet, so we have a // partial match - if(p && !force) + if (p && !force) { return TERMKEY_RES_AGAIN; + } return TERMKEY_RES_NONE; } @@ -597,39 +545,42 @@ static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node) // Unsigned because we'll be using it as an array subscript unsigned char b; - while((b = seq[pos])) { + while ((b = (unsigned char)seq[pos])) { struct trie_node *next = lookup_next(p, b); - if(!next) + if (!next) { break; + } p = next; pos++; } - while((b = seq[pos])) { + while ((b = (unsigned char)seq[pos])) { struct trie_node *next; - if(seq[pos+1]) + if (seq[pos + 1]) { // Intermediate node next = new_node_arr(0, 0xff); - else + } else { // Final key node next = node; + } - if(!next) + if (!next) { return 0; + } - switch(p->type) { - case TYPE_ARR: - { - struct trie_node_arr *nar = (struct trie_node_arr*)p; - if(b < nar->min || b > nar->max) { - fprintf(stderr, "ASSERT FAIL: Trie insert at 0x%02x is outside of extent bounds (0x%02x..0x%02x)\n", - b, nar->min, nar->max); - abort(); - } - nar->arr[b - nar->min] = next; - p = next; - break; + switch (p->type) { + case TYPE_ARR: { + struct trie_node_arr *nar = (struct trie_node_arr *)p; + if (b < nar->min || b > nar->max) { + fprintf(stderr, + "ASSERT FAIL: Trie insert at 0x%02x is outside of extent bounds (0x%02x..0x%02x)\n", + b, nar->min, nar->max); + abort(); } + nar->arr[b - nar->min] = next; + p = next; + break; + } case TYPE_KEY: fprintf(stderr, "ASSERT FAIL: Tried to insert child node in TYPE_KEY\n"); abort(); @@ -640,15 +591,3 @@ static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node) return 1; } - -struct TermKeyDriver termkey_driver_ti = { - .name = "terminfo", - - .new_driver = new_driver, - .free_driver = free_driver, - - .start_driver = start_driver, - .stop_driver = stop_driver, - - .peekkey = peekkey, -}; diff --git a/src/nvim/tui/termkey/driver-ti.h b/src/nvim/tui/termkey/driver-ti.h new file mode 100644 index 0000000000..df9bd72d5b --- /dev/null +++ b/src/nvim/tui/termkey/driver-ti.h @@ -0,0 +1,7 @@ +#pragma once + +#include "nvim/tui/termkey/termkey_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/driver-ti.h.generated.h" +#endif diff --git a/src/termkey/termkey-internal.h b/src/nvim/tui/termkey/termkey-internal.h similarity index 53% rename from src/termkey/termkey-internal.h rename to src/nvim/tui/termkey/termkey-internal.h index c300b02616..107591f950 100644 --- a/src/termkey/termkey-internal.h +++ b/src/nvim/tui/termkey/termkey-internal.h @@ -1,31 +1,29 @@ -#ifndef GUARD_TERMKEY_INTERNAL_H_ -#define GUARD_TERMKEY_INTERNAL_H_ +#pragma once + +#include + +#include "nvim/tui/termkey/termkey_defs.h" #define HAVE_TERMIOS - #ifdef _WIN32 # undef HAVE_TERMIOS #endif -#include "termkey.h" - -#include #ifdef HAVE_TERMIOS # include #endif #ifdef _MSC_VER -#include +# include typedef SSIZE_T ssize_t; #endif -struct TermKeyDriver -{ - const char *name; - void *(*new_driver)(TermKey *tk, const char *term); - void (*free_driver)(void *info); - int (*start_driver)(TermKey *tk, void *info); - int (*stop_driver)(TermKey *tk, void *info); +struct TermKeyDriver { + const char *name; + void *(*new_driver)(TermKey *tk, const char *term); + void (*free_driver)(void *info); + int (*start_driver)(TermKey *tk, void *info); + int (*stop_driver)(TermKey *tk, void *info); TermKeyResult (*peekkey)(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytes); }; @@ -38,21 +36,21 @@ struct keyinfo { struct TermKeyDriverNode; struct TermKeyDriverNode { - struct TermKeyDriver *driver; - void *info; + struct TermKeyDriver *driver; + void *info; struct TermKeyDriverNode *next; }; struct TermKey { - int fd; - int flags; - int canonflags; + int fd; + int flags; + int canonflags; unsigned char *buffer; - size_t buffstart; // First offset in buffer - size_t buffcount; // NUMBER of entires valid in buffer - size_t buffsize; // Total malloc'ed size - size_t hightide; /* Position beyond buffstart at which peekkey() should next start - * normally 0, but see also termkey_interpret_csi */ + size_t buffstart; // First offset in buffer + size_t buffcount; // NUMBER of entires valid in buffer + size_t buffsize; // Total malloc'ed size + size_t hightide; // Position beyond buffstart at which peekkey() should next start + // normally 0, but see also termkey_interpret_csi #ifdef HAVE_TERMIOS struct termios restore_termios; @@ -62,12 +60,12 @@ struct TermKey { TermKey_Terminfo_Getstr_Hook *ti_getstr_hook; void *ti_getstr_hook_data; - int waittime; // msec + int waittime; // msec - char is_closed; - char is_started; + char is_closed; + char is_started; - int nkeynames; + int nkeynames; const char **keynames; // There are 32 C0 codes @@ -78,7 +76,7 @@ struct TermKey { // Now some "protected" methods for the driver to call but which we don't // want exported as real symbols in the library struct { - void (*emit_codepoint)(TermKey *tk, long codepoint, TermKeyKey *key); + void (*emit_codepoint)(TermKey *tk, int codepoint, TermKeyKey *key); TermKeyResult (*peekkey_simple)(TermKey *tk, TermKeyKey *key, int force, size_t *nbytes); TermKeyResult (*peekkey_mouse)(TermKey *tk, TermKeyKey *key, size_t *nbytes); } method; @@ -86,27 +84,26 @@ struct TermKey { static inline void termkey_key_get_linecol(const TermKeyKey *key, int *line, int *col) { - if(col) - *col = (unsigned char)key->code.mouse[1] | ((unsigned char)key->code.mouse[3] & 0x0f) << 8; + if (col) { + *col = (unsigned char)key->code.mouse[1] | ((unsigned char)key->code.mouse[3] & 0x0f) << 8; + } - if(line) + if (line) { *line = (unsigned char)key->code.mouse[2] | ((unsigned char)key->code.mouse[3] & 0x70) << 4; + } } static inline void termkey_key_set_linecol(TermKeyKey *key, int line, int col) { - if(line > 0xfff) + if (line > 0xfff) { line = 0xfff; + } - if(col > 0x7ff) + if (col > 0x7ff) { col = 0x7ff; + } - key->code.mouse[1] = (line & 0x0ff); - key->code.mouse[2] = (col & 0x0ff); + key->code.mouse[1] = (char)(line & 0x0ff); + key->code.mouse[2] = (char)(col & 0x0ff); key->code.mouse[3] = (line & 0xf00) >> 8 | (col & 0x300) >> 4; } - -extern struct TermKeyDriver termkey_driver_csi; -extern struct TermKeyDriver termkey_driver_ti; - -#endif diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c new file mode 100644 index 0000000000..e6440118f3 --- /dev/null +++ b/src/nvim/tui/termkey/termkey.c @@ -0,0 +1,1315 @@ +#include +#include +#include +#include +#include + +#include "nvim/mbyte.h" +#include "nvim/memory.h" +#include "nvim/tui/termkey/driver-csi.h" +#include "nvim/tui/termkey/driver-ti.h" +#include "nvim/tui/termkey/termkey-internal.h" +#include "nvim/tui/termkey/termkey.h" +#include "nvim/tui/termkey/termkey_defs.h" + +#ifndef _WIN32 +# include +# include +# include +#else +# include +#endif + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/termkey.c.generated.h" +#endif + +#ifdef _MSC_VER +# define strcaseeq(a, b) (_stricmp(a, b) == 0) +#else +# define strcaseeq(a, b) (strcasecmp(a, b) == 0) +#endif + +struct TermKeyDriver termkey_driver_ti = { + .name = "terminfo", + + .new_driver = new_driver_ti, + .free_driver = free_driver_ti, + + .start_driver = start_driver_ti, + .stop_driver = stop_driver_ti, + + .peekkey = peekkey_ti, +}; + +struct TermKeyDriver termkey_driver_csi = { + .name = "CSI", + + .new_driver = new_driver_csi, + .free_driver = free_driver_csi, + + .peekkey = peekkey_csi, +}; + +static struct TermKeyDriver *drivers[] = { + &termkey_driver_ti, + &termkey_driver_csi, + NULL, +}; + +static struct { + TermKeySym sym; + const char *name; +} keynames[] = { + { TERMKEY_SYM_NONE, "NONE" }, + { TERMKEY_SYM_BACKSPACE, "Backspace" }, + { TERMKEY_SYM_TAB, "Tab" }, + { TERMKEY_SYM_ENTER, "Enter" }, + { TERMKEY_SYM_ESCAPE, "Escape" }, + { TERMKEY_SYM_SPACE, "Space" }, + { TERMKEY_SYM_DEL, "DEL" }, + { TERMKEY_SYM_UP, "Up" }, + { TERMKEY_SYM_DOWN, "Down" }, + { TERMKEY_SYM_LEFT, "Left" }, + { TERMKEY_SYM_RIGHT, "Right" }, + { TERMKEY_SYM_BEGIN, "Begin" }, + { TERMKEY_SYM_FIND, "Find" }, + { TERMKEY_SYM_INSERT, "Insert" }, + { TERMKEY_SYM_DELETE, "Delete" }, + { TERMKEY_SYM_SELECT, "Select" }, + { TERMKEY_SYM_PAGEUP, "PageUp" }, + { TERMKEY_SYM_PAGEDOWN, "PageDown" }, + { TERMKEY_SYM_HOME, "Home" }, + { TERMKEY_SYM_END, "End" }, + { TERMKEY_SYM_CANCEL, "Cancel" }, + { TERMKEY_SYM_CLEAR, "Clear" }, + { TERMKEY_SYM_CLOSE, "Close" }, + { TERMKEY_SYM_COMMAND, "Command" }, + { TERMKEY_SYM_COPY, "Copy" }, + { TERMKEY_SYM_EXIT, "Exit" }, + { TERMKEY_SYM_HELP, "Help" }, + { TERMKEY_SYM_MARK, "Mark" }, + { TERMKEY_SYM_MESSAGE, "Message" }, + { TERMKEY_SYM_MOVE, "Move" }, + { TERMKEY_SYM_OPEN, "Open" }, + { TERMKEY_SYM_OPTIONS, "Options" }, + { TERMKEY_SYM_PRINT, "Print" }, + { TERMKEY_SYM_REDO, "Redo" }, + { TERMKEY_SYM_REFERENCE, "Reference" }, + { TERMKEY_SYM_REFRESH, "Refresh" }, + { TERMKEY_SYM_REPLACE, "Replace" }, + { TERMKEY_SYM_RESTART, "Restart" }, + { TERMKEY_SYM_RESUME, "Resume" }, + { TERMKEY_SYM_SAVE, "Save" }, + { TERMKEY_SYM_SUSPEND, "Suspend" }, + { TERMKEY_SYM_UNDO, "Undo" }, + { TERMKEY_SYM_KP0, "KP0" }, + { TERMKEY_SYM_KP1, "KP1" }, + { TERMKEY_SYM_KP2, "KP2" }, + { TERMKEY_SYM_KP3, "KP3" }, + { TERMKEY_SYM_KP4, "KP4" }, + { TERMKEY_SYM_KP5, "KP5" }, + { TERMKEY_SYM_KP6, "KP6" }, + { TERMKEY_SYM_KP7, "KP7" }, + { TERMKEY_SYM_KP8, "KP8" }, + { TERMKEY_SYM_KP9, "KP9" }, + { TERMKEY_SYM_KPENTER, "KPEnter" }, + { TERMKEY_SYM_KPPLUS, "KPPlus" }, + { TERMKEY_SYM_KPMINUS, "KPMinus" }, + { TERMKEY_SYM_KPMULT, "KPMult" }, + { TERMKEY_SYM_KPDIV, "KPDiv" }, + { TERMKEY_SYM_KPCOMMA, "KPComma" }, + { TERMKEY_SYM_KPPERIOD, "KPPeriod" }, + { TERMKEY_SYM_KPEQUALS, "KPEquals" }, + { 0, NULL }, +}; + +// Mouse event names +static const char *evnames[] = { "Unknown", "Press", "Drag", "Release" }; + +#define CHARAT(i) (tk->buffer[tk->buffstart + (i)]) + +#ifdef DEBUG +// Some internal debugging functions + +static void print_buffer(TermKey *tk) +{ + int i; + for (i = 0; i < tk->buffcount && i < 20; i++) { + fprintf(stderr, "%02x ", CHARAT(i)); + } + if (tk->buffcount > 20) { + fprintf(stderr, "..."); + } +} + +static void print_key(TermKey *tk, TermKeyKey *key) +{ + switch (key->type) { + case TERMKEY_TYPE_UNICODE: + fprintf(stderr, "Unicode codepoint=U+%04lx utf8='%s'", key->code.codepoint, key->utf8); + break; + case TERMKEY_TYPE_FUNCTION: + fprintf(stderr, "Function F%d", key->code.number); + break; + case TERMKEY_TYPE_KEYSYM: + fprintf(stderr, "Keysym sym=%d(%s)", key->code.sym, termkey_get_keyname(tk, key->code.sym)); + break; + case TERMKEY_TYPE_MOUSE: { + TermKeyMouseEvent ev; + int button, line, col; + termkey_interpret_mouse(tk, key, &ev, &button, &line, &col); + fprintf(stderr, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev, button, line, col); + } + break; + case TERMKEY_TYPE_POSITION: { + int line, col; + termkey_interpret_position(tk, key, &line, &col); + fprintf(stderr, "Position report pos=(%d,%d)\n", line, col); + } + break; + case TERMKEY_TYPE_MODEREPORT: { + int initial, mode, value; + termkey_interpret_modereport(tk, key, &initial, &mode, &value); + fprintf(stderr, "Mode report mode=%s %d val=%d\n", initial == '?' ? "DEC" : "ANSI", mode, + value); + } + break; + case TERMKEY_TYPE_DCS: + fprintf(stderr, "Device Control String"); + break; + case TERMKEY_TYPE_OSC: + fprintf(stderr, "Operating System Control"); + break; + case TERMKEY_TYPE_UNKNOWN_CSI: + fprintf(stderr, "unknown CSI\n"); + break; + } + + int m = key->modifiers; + fprintf(stderr, " mod=%s%s%s+%02x", + (m & TERMKEY_KEYMOD_CTRL ? "C" : ""), + (m & TERMKEY_KEYMOD_ALT ? "A" : ""), + (m & TERMKEY_KEYMOD_SHIFT ? "S" : ""), + m & ~(TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT|TERMKEY_KEYMOD_SHIFT)); +} + +static const char *res2str(TermKeyResult res) +{ + static char errorbuffer[256]; + + switch (res) { + case TERMKEY_RES_KEY: + return "TERMKEY_RES_KEY"; + case TERMKEY_RES_EOF: + return "TERMKEY_RES_EOF"; + case TERMKEY_RES_AGAIN: + return "TERMKEY_RES_AGAIN"; + case TERMKEY_RES_NONE: + return "TERMKEY_RES_NONE"; + case TERMKEY_RES_ERROR: + snprintf(errorbuffer, sizeof errorbuffer, "TERMKEY_RES_ERROR(errno=%d)\n", errno); + return (const char *)errorbuffer; + } + + return "unknown"; +} +#endif + +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; +} + +/// Similar to snprintf(str, size, "%s", src) except it turns CamelCase into +/// space separated values +static int snprint_cameltospaces(char *str, size_t size, const char *src) +{ + int prev_lower = 0; + size_t l = 0; + while (*src && l < size - 1) { + if (isupper(*src) && prev_lower) { + if (str) { + str[l++] = ' '; + } + if (l >= size - 1) { + break; + } + } + prev_lower = islower(*src); + str[l++] = (char)tolower(*src++); + } + str[l] = 0; + // For consistency with snprintf, return the number of bytes that would have + // been written, excluding '\0' + while (*src) { + if (isupper(*src) && prev_lower) { + l++; + } + prev_lower = islower(*src); + src++; l++; + } + return (int)l; +} + +/// Similar to strcmp(str, strcamel, n) except that: +/// it compares CamelCase in strcamel with space separated values in str; +/// it takes char**s and updates them +/// n counts bytes of strcamel, not str +static int strpncmp_camel(const char **strp, const char **strcamelp, size_t n) +{ + const char *str = *strp, *strcamel = *strcamelp; + int prev_lower = 0; + + for (; (*str || *strcamel) && n; n--) { + char b = (char)tolower(*strcamel); + if (isupper(*strcamel) && prev_lower) { + if (*str != ' ') { + break; + } + str++; + if (*str != b) { + break; + } + } else if (*str != b) { + break; + } + + prev_lower = islower(*strcamel); + + str++; + strcamel++; + } + + *strp = str; + *strcamelp = strcamel; + return *str - *strcamel; +} + +static TermKey *termkey_alloc(void) +{ + TermKey *tk = xmalloc(sizeof(TermKey)); + + // Default all the object fields but don't allocate anything + + tk->fd = -1; + tk->flags = 0; + tk->canonflags = 0; + + tk->buffer = NULL; + tk->buffstart = 0; + tk->buffcount = 0; + tk->buffsize = 256; // bytes + tk->hightide = 0; + +#ifdef HAVE_TERMIOS + tk->restore_termios_valid = 0; +#endif + + tk->ti_getstr_hook = NULL; + tk->ti_getstr_hook_data = NULL; + + tk->waittime = 50; // msec + + tk->is_closed = 0; + tk->is_started = 0; + + tk->nkeynames = 64; + tk->keynames = NULL; + + for (int i = 0; i < 32; i++) { + tk->c0[i].sym = TERMKEY_SYM_NONE; + } + + tk->drivers = NULL; + + tk->method.emit_codepoint = &emit_codepoint; + tk->method.peekkey_simple = &peekkey_simple; + tk->method.peekkey_mouse = &peekkey_mouse; + + return tk; +} + +static int termkey_init(TermKey *tk, const char *term) +{ + tk->buffer = xmalloc(tk->buffsize); + tk->keynames = xmalloc(sizeof(tk->keynames[0]) * (size_t)tk->nkeynames); + + int i; + for (i = 0; i < tk->nkeynames; i++) { + tk->keynames[i] = NULL; + } + + for (i = 0; keynames[i].name; i++) { + if (termkey_register_keyname(tk, keynames[i].sym, keynames[i].name) == -1) { + goto abort_free_keynames; + } + } + + register_c0(tk, TERMKEY_SYM_TAB, 0x09, NULL); + register_c0(tk, TERMKEY_SYM_ENTER, 0x0d, NULL); + register_c0(tk, TERMKEY_SYM_ESCAPE, 0x1b, NULL); + + struct TermKeyDriverNode *tail = NULL; + + for (i = 0; drivers[i]; i++) { + void *info = (*drivers[i]->new_driver)(tk, term); + if (!info) { + continue; + } + +#ifdef DEBUG + fprintf(stderr, "Loading the %s driver...\n", drivers[i]->name); +#endif + + struct TermKeyDriverNode *thisdrv = xmalloc(sizeof(*thisdrv)); + if (!thisdrv) { + goto abort_free_drivers; + } + + thisdrv->driver = drivers[i]; + thisdrv->info = info; + thisdrv->next = NULL; + + if (!tail) { + tk->drivers = thisdrv; + } else { + tail->next = thisdrv; + } + + tail = thisdrv; + +#ifdef DEBUG + fprintf(stderr, "Loaded %s driver\n", drivers[i]->name); +#endif + } + + if (!tk->drivers) { + errno = ENOENT; + goto abort_free_keynames; + } + + return 1; + +abort_free_drivers: + for (struct TermKeyDriverNode *p = tk->drivers; p;) { + (*p->driver->free_driver)(p->info); + struct TermKeyDriverNode *next = p->next; + xfree(p); + p = next; + } + +abort_free_keynames: + xfree(tk->keynames); + xfree(tk->buffer); + + return 0; +} + +TermKey *termkey_new_abstract(const char *term, int flags) +{ + TermKey *tk = termkey_alloc(); + if (!tk) { + return NULL; + } + + tk->fd = -1; + + termkey_set_flags(tk, flags); + + if (!termkey_init(tk, term)) { + xfree(tk); + return NULL; + } + + if (!(flags & TERMKEY_FLAG_NOSTART) && !termkey_start(tk)) { + goto abort; + } + + return tk; + +abort: + xfree(tk); + return NULL; +} + +void termkey_free(TermKey *tk) +{ + xfree(tk->buffer); tk->buffer = NULL; + xfree(tk->keynames); tk->keynames = NULL; + + struct TermKeyDriverNode *p; + for (p = tk->drivers; p;) { + (*p->driver->free_driver)(p->info); + struct TermKeyDriverNode *next = p->next; + xfree(p); + p = next; + } + + xfree(tk); +} + +void termkey_destroy(TermKey *tk) +{ + if (tk->is_started) { + termkey_stop(tk); + } + + termkey_free(tk); +} + +void termkey_hook_terminfo_getstr(TermKey *tk, TermKey_Terminfo_Getstr_Hook *hookfn, void *data) +{ + tk->ti_getstr_hook = hookfn; + tk->ti_getstr_hook_data = data; +} + +int termkey_start(TermKey *tk) +{ + if (tk->is_started) { + return 1; + } + +#ifdef HAVE_TERMIOS + if (tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) { + struct termios termios; + if (tcgetattr(tk->fd, &termios) == 0) { + tk->restore_termios = termios; + tk->restore_termios_valid = 1; + + termios.c_iflag &= (tcflag_t) ~(IXON|INLCR|ICRNL); + termios.c_lflag &= (tcflag_t) ~(ICANON|ECHO +# ifdef IEXTEN + | IEXTEN +# endif + ); + termios.c_cc[VMIN] = 1; + termios.c_cc[VTIME] = 0; + + if (tk->flags & TERMKEY_FLAG_CTRLC) { + // want no signal keys at all, so just disable ISIG + termios.c_lflag &= (tcflag_t) ~ISIG; + } else { + // Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT + termios.c_cc[VQUIT] = _POSIX_VDISABLE; + termios.c_cc[VSUSP] = _POSIX_VDISABLE; + // Some OSes have Ctrl-Y==VDSUSP +# ifdef VDSUSP + termios.c_cc[VDSUSP] = _POSIX_VDISABLE; +# endif + } + +# ifdef DEBUG + fprintf(stderr, "Setting termios(3) flags\n"); +# endif + tcsetattr(tk->fd, TCSANOW, &termios); + } + } +#endif + + struct TermKeyDriverNode *p; + for (p = tk->drivers; p; p = p->next) { + if (p->driver->start_driver) { + if (!(*p->driver->start_driver)(tk, p->info)) { + return 0; + } + } + } + +#ifdef DEBUG + fprintf(stderr, "Drivers started; termkey instance %p is ready\n", tk); +#endif + + tk->is_started = 1; + return 1; +} + +int termkey_stop(TermKey *tk) +{ + if (!tk->is_started) { + return 1; + } + + struct TermKeyDriverNode *p; + for (p = tk->drivers; p; p = p->next) { + if (p->driver->stop_driver) { + (*p->driver->stop_driver)(tk, p->info); + } + } + +#ifdef HAVE_TERMIOS + if (tk->restore_termios_valid) { + tcsetattr(tk->fd, TCSANOW, &tk->restore_termios); + } +#endif + + tk->is_started = 0; + + return 1; +} + +void termkey_set_flags(TermKey *tk, int newflags) +{ + tk->flags = newflags; + + if (tk->flags & TERMKEY_FLAG_SPACESYMBOL) { + tk->canonflags |= TERMKEY_CANON_SPACESYMBOL; + } else { + tk->canonflags &= ~TERMKEY_CANON_SPACESYMBOL; + } +} + +int termkey_get_canonflags(TermKey *tk) +{ + return tk->canonflags; +} + +void termkey_set_canonflags(TermKey *tk, int flags) +{ + tk->canonflags = flags; + + if (tk->canonflags & TERMKEY_CANON_SPACESYMBOL) { + tk->flags |= TERMKEY_FLAG_SPACESYMBOL; + } else { + tk->flags &= ~TERMKEY_FLAG_SPACESYMBOL; + } +} + +size_t termkey_get_buffer_size(TermKey *tk) +{ + return tk->buffsize; +} + +int termkey_set_buffer_size(TermKey *tk, size_t size) +{ + unsigned char *buffer = xrealloc(tk->buffer, size); + + tk->buffer = buffer; + tk->buffsize = size; + + return 1; +} + +size_t termkey_get_buffer_remaining(TermKey *tk) +{ + // Return the total number of free bytes in the buffer, because that's what + // is available to the user. + return tk->buffsize - tk->buffcount; +} + +static void eat_bytes(TermKey *tk, size_t count) +{ + if (count >= tk->buffcount) { + tk->buffstart = 0; + tk->buffcount = 0; + return; + } + + tk->buffstart += count; + tk->buffcount -= count; +} + +// TODO(dundargoc): we should be able to replace this with utf_char2bytes from mbyte.c +int fill_utf8(int codepoint, char *str) +{ + int nbytes = utf_char2len(codepoint); + + str[nbytes] = 0; + + // This is easier done backwards + int b = nbytes; + while (b > 1) { + b--; + str[b] = (char)0x80 | (codepoint & 0x3f); + codepoint >>= 6; + } + + switch (nbytes) { + case 1: + str[0] = (codepoint & 0x7f); break; + case 2: + str[0] = (char)0xc0 | (codepoint & 0x1f); break; + case 3: + str[0] = (char)0xe0 | (codepoint & 0x0f); break; + case 4: + str[0] = (char)0xf0 | (codepoint & 0x07); break; + case 5: + str[0] = (char)0xf8 | (codepoint & 0x03); break; + case 6: + str[0] = (char)0xfc | (codepoint & 0x01); break; + } + + return nbytes; +} + +#define UTF8_INVALID 0xFFFD +static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, size_t *nbytep) +{ + unsigned nbytes; + + unsigned char b0 = bytes[0]; + + if (b0 < 0x80) { + // Single byte ASCII + *cp = b0; + *nbytep = 1; + return TERMKEY_RES_KEY; + } else if (b0 < 0xc0) { + // Starts with a continuation byte - that's not right + *cp = UTF8_INVALID; + *nbytep = 1; + return TERMKEY_RES_KEY; + } else if (b0 < 0xe0) { + nbytes = 2; + *cp = b0 & 0x1f; + } else if (b0 < 0xf0) { + nbytes = 3; + *cp = b0 & 0x0f; + } else if (b0 < 0xf8) { + nbytes = 4; + *cp = b0 & 0x07; + } else if (b0 < 0xfc) { + nbytes = 5; + *cp = b0 & 0x03; + } else if (b0 < 0xfe) { + nbytes = 6; + *cp = b0 & 0x01; + } else { + *cp = UTF8_INVALID; + *nbytep = 1; + return TERMKEY_RES_KEY; + } + + for (unsigned b = 1; b < nbytes; b++) { + unsigned char cb; + + if (b >= len) { + return TERMKEY_RES_AGAIN; + } + + cb = bytes[b]; + if (cb < 0x80 || cb >= 0xc0) { + *cp = UTF8_INVALID; + *nbytep = b; + return TERMKEY_RES_KEY; + } + + *cp <<= 6; + *cp |= cb & 0x3f; + } + + // Check for overlong sequences + if ((int)nbytes > utf_char2len(*cp)) { + *cp = UTF8_INVALID; + } + + // Check for UTF-16 surrogates or invalid *cps + if ((*cp >= 0xD800 && *cp <= 0xDFFF) + || *cp == 0xFFFE + || *cp == 0xFFFF) { + *cp = UTF8_INVALID; + } + + *nbytep = nbytes; + return TERMKEY_RES_KEY; +} + +static void emit_codepoint(TermKey *tk, int codepoint, TermKeyKey *key) +{ + if (codepoint == 0) { + // ASCII NUL = Ctrl-Space + key->type = TERMKEY_TYPE_KEYSYM; + key->code.sym = TERMKEY_SYM_SPACE; + key->modifiers = TERMKEY_KEYMOD_CTRL; + } else if (codepoint < 0x20) { + // C0 range + key->code.codepoint = 0; + key->modifiers = 0; + + if (!(tk->flags & TERMKEY_FLAG_NOINTERPRET) && tk->c0[codepoint].sym != TERMKEY_SYM_UNKNOWN) { + key->code.sym = tk->c0[codepoint].sym; + key->modifiers |= tk->c0[codepoint].modifier_set; + } + + if (!key->code.sym) { + key->type = TERMKEY_TYPE_UNICODE; + // Generically modified Unicode ought not report the SHIFT state, or else + // we get into complications trying to report Shift-; vs : and so on... + // In order to be able to represent Ctrl-Shift-A as CTRL modified + // unicode A, we need to call Ctrl-A simply 'a', lowercase + if (codepoint + 0x40 >= 'A' && codepoint + 0x40 <= 'Z') { + // it's a letter - use lowercase instead + key->code.codepoint = codepoint + 0x60; + } else { + key->code.codepoint = codepoint + 0x40; + } + key->modifiers = TERMKEY_KEYMOD_CTRL; + } else { + key->type = TERMKEY_TYPE_KEYSYM; + } + } else if (codepoint == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { + // ASCII DEL + key->type = TERMKEY_TYPE_KEYSYM; + key->code.sym = TERMKEY_SYM_DEL; + key->modifiers = 0; + } else if (codepoint >= 0x20 && codepoint < 0x80) { + // ASCII lowbyte range + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint; + key->modifiers = 0; + } else if (codepoint >= 0x80 && codepoint < 0xa0) { + // UTF-8 never starts with a C1 byte. So we can be sure of these + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint - 0x40; + key->modifiers = TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT; + } else { + // UTF-8 codepoint + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = codepoint; + key->modifiers = 0; + } + + termkey_canonicalise(tk, key); + + if (key->type == TERMKEY_TYPE_UNICODE) { + fill_utf8(key->code.codepoint, key->utf8); + } +} + +void termkey_canonicalise(TermKey *tk, TermKeyKey *key) +{ + int flags = tk->canonflags; + + if (flags & TERMKEY_CANON_SPACESYMBOL) { + if (key->type == TERMKEY_TYPE_UNICODE && key->code.codepoint == 0x20) { + key->type = TERMKEY_TYPE_KEYSYM; + key->code.sym = TERMKEY_SYM_SPACE; + } + } else { + if (key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_SPACE) { + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = 0x20; + fill_utf8(key->code.codepoint, key->utf8); + } + } + + if (flags & TERMKEY_CANON_DELBS) { + if (key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_DEL) { + key->code.sym = TERMKEY_SYM_BACKSPACE; + } + } +} + +static TermKeyResult peekkey(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep) +{ + int again = 0; + + if (!tk->is_started) { + errno = EINVAL; + return TERMKEY_RES_ERROR; + } + +#ifdef DEBUG + fprintf(stderr, "getkey(force=%d): buffer ", force); + print_buffer(tk); + fprintf(stderr, "\n"); +#endif + + if (tk->hightide) { + tk->buffstart += tk->hightide; + tk->buffcount -= tk->hightide; + tk->hightide = 0; + } + + TermKeyResult ret; + struct TermKeyDriverNode *p; + for (p = tk->drivers; p; p = p->next) { + ret = (p->driver->peekkey)(tk, p->info, key, force, nbytep); + +#ifdef DEBUG + fprintf(stderr, "Driver %s yields %s\n", p->driver->name, res2str(ret)); +#endif + + switch (ret) { + case TERMKEY_RES_KEY: +#ifdef DEBUG + print_key(tk, key); fprintf(stderr, "\n"); +#endif + // Slide the data down to stop it running away + { + size_t halfsize = tk->buffsize / 2; + + if (tk->buffstart > halfsize) { + memcpy(tk->buffer, tk->buffer + halfsize, halfsize); + tk->buffstart -= halfsize; + } + } + FALLTHROUGH; + case TERMKEY_RES_EOF: + case TERMKEY_RES_ERROR: + return ret; + + case TERMKEY_RES_AGAIN: + if (!force) { + again = 1; + } + FALLTHROUGH; + case TERMKEY_RES_NONE: + break; + } + } + + if (again) { + return TERMKEY_RES_AGAIN; + } + + ret = peekkey_simple(tk, key, force, nbytep); + +#ifdef DEBUG + fprintf(stderr, "getkey_simple(force=%d) yields %s\n", force, res2str(ret)); + if (ret == TERMKEY_RES_KEY) { + print_key(tk, key); fprintf(stderr, "\n"); + } +#endif + + return ret; +} + +static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep) +{ + if (tk->buffcount == 0) { + return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; + } + + unsigned char b0 = CHARAT(0); + + if (b0 == 0x1b) { + // Escape-prefixed value? Might therefore be Alt+key + if (tk->buffcount == 1) { + // This might be an press, or it may want to be part of a longer + // sequence + if (!force) { + return TERMKEY_RES_AGAIN; + } + + (*tk->method.emit_codepoint)(tk, b0, key); + *nbytep = 1; + return TERMKEY_RES_KEY; + } + + // Try another key there + tk->buffstart++; + tk->buffcount--; + + // Run the full driver + TermKeyResult metakey_result = peekkey(tk, key, force, nbytep); + + tk->buffstart--; + tk->buffcount++; + + switch (metakey_result) { + case TERMKEY_RES_KEY: + key->modifiers |= TERMKEY_KEYMOD_ALT; + (*nbytep)++; + break; + + case TERMKEY_RES_NONE: + case TERMKEY_RES_EOF: + case TERMKEY_RES_AGAIN: + case TERMKEY_RES_ERROR: + break; + } + + return metakey_result; + } else if (b0 < 0xa0) { + // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte + (*tk->method.emit_codepoint)(tk, b0, key); + *nbytep = 1; + return TERMKEY_RES_KEY; + } else if (tk->flags & TERMKEY_FLAG_UTF8) { + // Some UTF-8 + int codepoint; + TermKeyResult res = parse_utf8(tk->buffer + tk->buffstart, tk->buffcount, &codepoint, nbytep); + + if (res == TERMKEY_RES_AGAIN && force) { + // There weren't enough bytes for a complete UTF-8 sequence but caller + // demands an answer. About the best thing we can do here is eat as many + // bytes as we have, and emit a UTF8_INVALID. If the remaining bytes + // arrive later, they'll be invalid too. + codepoint = UTF8_INVALID; + *nbytep = tk->buffcount; + res = TERMKEY_RES_KEY; + } + + key->type = TERMKEY_TYPE_UNICODE; + key->modifiers = 0; + (*tk->method.emit_codepoint)(tk, codepoint, key); + return res; + } else { + // Non UTF-8 case - just report the raw byte + key->type = TERMKEY_TYPE_UNICODE; + key->code.codepoint = b0; + key->modifiers = 0; + + key->utf8[0] = (char)key->code.codepoint; + key->utf8[1] = 0; + + *nbytep = 1; + + return TERMKEY_RES_KEY; + } +} + +static TermKeyResult peekkey_mouse(TermKey *tk, TermKeyKey *key, size_t *nbytep) +{ + if (tk->buffcount < 3) { + return TERMKEY_RES_AGAIN; + } + + key->type = TERMKEY_TYPE_MOUSE; + key->code.mouse[0] = (char)CHARAT(0) - 0x20; + key->code.mouse[1] = (char)CHARAT(1) - 0x20; + key->code.mouse[2] = (char)CHARAT(2) - 0x20; + key->code.mouse[3] = 0; + + key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; + key->code.mouse[0] &= ~0x1c; + + *nbytep = 3; + return TERMKEY_RES_KEY; +} + +TermKeyResult termkey_getkey(TermKey *tk, TermKeyKey *key) +{ + size_t nbytes = 0; + TermKeyResult ret = peekkey(tk, key, 0, &nbytes); + + if (ret == TERMKEY_RES_KEY) { + eat_bytes(tk, nbytes); + } + + if (ret == TERMKEY_RES_AGAIN) { + // Call peekkey() again in force mode to obtain whatever it can + (void)peekkey(tk, key, 1, &nbytes); + } + // Don't eat it yet though + + return ret; +} + +TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key) +{ + size_t nbytes = 0; + TermKeyResult ret = peekkey(tk, key, 1, &nbytes); + + if (ret == TERMKEY_RES_KEY) { + eat_bytes(tk, nbytes); + } + + return ret; +} + +size_t termkey_push_bytes(TermKey *tk, const char *bytes, size_t len) +{ + if (tk->buffstart) { + memmove(tk->buffer, tk->buffer + tk->buffstart, tk->buffcount); + tk->buffstart = 0; + } + + // Not expecting it ever to be greater but doesn't hurt to handle that + if (tk->buffcount >= tk->buffsize) { + errno = ENOMEM; + return (size_t)-1; + } + + if (len > tk->buffsize - tk->buffcount) { + len = tk->buffsize - tk->buffcount; + } + + // memcpy(), not strncpy() in case of null bytes in input + memcpy(tk->buffer + tk->buffcount, bytes, len); + tk->buffcount += len; + + return len; +} + +TermKeySym termkey_register_keyname(TermKey *tk, TermKeySym sym, const char *name) +{ + if (!sym) { + sym = tk->nkeynames; + } + + if (sym >= tk->nkeynames) { + const char **new_keynames = xrealloc(tk->keynames, sizeof(new_keynames[0]) * ((size_t)sym + 1)); + + tk->keynames = new_keynames; + + // Fill in the hole + for (int i = tk->nkeynames; i < sym; i++) { + tk->keynames[i] = NULL; + } + + tk->nkeynames = sym + 1; + } + + tk->keynames[sym] = name; + + return sym; +} + +const char *termkey_get_keyname(TermKey *tk, TermKeySym sym) +{ + if (sym == TERMKEY_SYM_UNKNOWN) { + return "UNKNOWN"; + } + + if (sym < tk->nkeynames) { + return tk->keynames[sym]; + } + + return "UNKNOWN"; +} + +static const char *termkey_lookup_keyname_format(TermKey *tk, const char *str, TermKeySym *sym, + TermKeyFormat format) +{ + // We store an array, so we can't do better than a linear search. Doesn't + // matter because user won't be calling this too often + + for (*sym = 0; *sym < tk->nkeynames; (*sym)++) { + const char *thiskey = tk->keynames[*sym]; + if (!thiskey) { + continue; + } + size_t len = strlen(thiskey); + if (format & TERMKEY_FORMAT_LOWERSPACE) { + const char *thisstr = str; + if (strpncmp_camel(&thisstr, &thiskey, len) == 0) { + return thisstr; + } + } else { + if (strncmp(str, thiskey, len) == 0) { + return (char *)str + len; + } + } + } + + return NULL; +} + +const char *termkey_lookup_keyname(TermKey *tk, const char *str, TermKeySym *sym) +{ + return termkey_lookup_keyname_format(tk, str, sym, 0); +} + +static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name) +{ + return register_c0_full(tk, sym, 0, 0, ctrl, name); +} + +static TermKeySym register_c0_full(TermKey *tk, TermKeySym sym, int modifier_set, int modifier_mask, + unsigned char ctrl, const char *name) +{ + if (ctrl >= 0x20) { + errno = EINVAL; + return -1; + } + + if (name) { + sym = termkey_register_keyname(tk, sym, name); + } + + tk->c0[ctrl].sym = sym; + tk->c0[ctrl].modifier_set = modifier_set; + tk->c0[ctrl].modifier_mask = modifier_mask; + + return sym; +} + +static struct modnames { + const char *shift, *alt, *ctrl; +} +modnames[] = { + { "S", "A", "C" }, // 0 + { "Shift", "Alt", "Ctrl" }, // LONGMOD + { "S", "M", "C" }, // ALTISMETA + { "Shift", "Meta", "Ctrl" }, // ALTISMETA+LONGMOD + { "s", "a", "c" }, // LOWERMOD + { "shift", "alt", "ctrl" }, // LOWERMOD+LONGMOD + { "s", "m", "c" }, // LOWERMOD+ALTISMETA + { "shift", "meta", "ctrl" }, // LOWERMOD+ALTISMETA+LONGMOD +}; + +size_t termkey_strfkey(TermKey *tk, char *buffer, size_t len, TermKeyKey *key, TermKeyFormat format) +{ + size_t pos = 0; + size_t l = 0; + + struct modnames *mods = &modnames[!!(format & TERMKEY_FORMAT_LONGMOD) + + !!(format & TERMKEY_FORMAT_ALTISMETA) * 2 + + !!(format & TERMKEY_FORMAT_LOWERMOD) * 4]; + + int wrapbracket = (format & TERMKEY_FORMAT_WRAPBRACKET) + && (key->type != TERMKEY_TYPE_UNICODE || key->modifiers != 0); + + char sep = (format & TERMKEY_FORMAT_SPACEMOD) ? ' ' : '-'; + + if (format & TERMKEY_FORMAT_CARETCTRL + && key->type == TERMKEY_TYPE_UNICODE + && key->modifiers == TERMKEY_KEYMOD_CTRL) { + long codepoint = key->code.codepoint; + + // Handle some of the special cases first + if (codepoint >= 'a' && codepoint <= 'z') { + l = (size_t)snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c", + (char)codepoint - 0x20); + if (l <= 0) { + return pos; + } + pos += l; + return pos; + } else if ((codepoint >= '@' && codepoint < 'A') + || (codepoint > 'Z' && codepoint <= '_')) { + l = (size_t)snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c", (char)codepoint); + if (l <= 0) { + return pos; + } + pos += l; + return pos; + } + } + + if (wrapbracket) { + l = (size_t)snprintf(buffer + pos, len - pos, "<"); + if (l <= 0) { + return pos; + } + pos += l; + } + + if (key->modifiers & TERMKEY_KEYMOD_ALT) { + l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->alt, sep); + if (l <= 0) { + return pos; + } + pos += l; + } + + if (key->modifiers & TERMKEY_KEYMOD_CTRL) { + l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->ctrl, sep); + if (l <= 0) { + return pos; + } + pos += l; + } + + if (key->modifiers & TERMKEY_KEYMOD_SHIFT) { + l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->shift, sep); + if (l <= 0) { + return pos; + } + pos += l; + } + + switch (key->type) { + case TERMKEY_TYPE_UNICODE: + if (!key->utf8[0]) { // In case of user-supplied key structures + fill_utf8(key->code.codepoint, key->utf8); + } + l = (size_t)snprintf(buffer + pos, len - pos, "%s", key->utf8); + break; + case TERMKEY_TYPE_KEYSYM: { + const char *name = termkey_get_keyname(tk, key->code.sym); + if (format & TERMKEY_FORMAT_LOWERSPACE) { + l = (size_t)snprint_cameltospaces(buffer + pos, len - pos, name); + } else { + l = (size_t)snprintf(buffer + pos, len - pos, "%s", name); + } + } + break; + case TERMKEY_TYPE_FUNCTION: + l = (size_t)snprintf(buffer + pos, len - pos, "%c%d", + (format & TERMKEY_FORMAT_LOWERSPACE ? 'f' : 'F'), key->code.number); + break; + case TERMKEY_TYPE_MOUSE: { + TermKeyMouseEvent ev; + int button; + int line, col; + termkey_interpret_mouse(tk, key, &ev, &button, &line, &col); + + l = (size_t)snprintf(buffer + pos, len - pos, "Mouse%s(%d)", + evnames[ev], button); + + if (format & TERMKEY_FORMAT_MOUSE_POS) { + if (l <= 0) { + return pos; + } + pos += l; + + l = (size_t)snprintf(buffer + pos, len - pos, " @ (%u,%u)", col, line); + } + } + break; + case TERMKEY_TYPE_POSITION: + l = (size_t)snprintf(buffer + pos, len - pos, "Position"); + break; + case TERMKEY_TYPE_MODEREPORT: { + int initial, mode, value; + termkey_interpret_modereport(tk, key, &initial, &mode, &value); + if (initial) { + l = (size_t)snprintf(buffer + pos, len - pos, "Mode(%c%d=%d)", initial, mode, value); + } else { + l = (size_t)snprintf(buffer + pos, len - pos, "Mode(%d=%d)", mode, value); + } + } + break; + case TERMKEY_TYPE_DCS: + l = (size_t)snprintf(buffer + pos, len - pos, "DCS"); + break; + case TERMKEY_TYPE_OSC: + l = (size_t)snprintf(buffer + pos, len - pos, "OSC"); + break; + case TERMKEY_TYPE_UNKNOWN_CSI: + l = (size_t)snprintf(buffer + pos, len - pos, "CSI %c", key->code.number & 0xff); + break; + } + + if (l <= 0) { + return pos; + } + pos += l; + + if (wrapbracket) { + l = (size_t)snprintf(buffer + pos, len - pos, ">"); + if (l <= 0) { + return pos; + } + pos += l; + } + + return pos; +} diff --git a/src/nvim/tui/termkey/termkey.h b/src/nvim/tui/termkey/termkey.h new file mode 100644 index 0000000000..21ed141346 --- /dev/null +++ b/src/nvim/tui/termkey/termkey.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +#include "nvim/tui/termkey/termkey_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "tui/termkey/termkey.h.generated.h" +#endif diff --git a/src/nvim/tui/termkey/termkey_defs.h b/src/nvim/tui/termkey/termkey_defs.h new file mode 100644 index 0000000000..7c218ba7c2 --- /dev/null +++ b/src/nvim/tui/termkey/termkey_defs.h @@ -0,0 +1,199 @@ +#pragma once + +#include +#include +#include +#include + +#include "nvim/event/defs.h" +#include "nvim/tui/tui_defs.h" +#include "nvim/types_defs.h" + +typedef struct TermKey TermKey; + +typedef struct { + TermKey *tk; + int saved_string_id; + char *saved_string; +} TermKeyCsi; + +typedef enum { + TERMKEY_RES_NONE, + TERMKEY_RES_KEY, + TERMKEY_RES_EOF, + TERMKEY_RES_AGAIN, + TERMKEY_RES_ERROR, +} TermKeyResult; + +typedef enum { + TERMKEY_SYM_UNKNOWN = -1, + TERMKEY_SYM_NONE = 0, + + // Special names in C0 + TERMKEY_SYM_BACKSPACE, + TERMKEY_SYM_TAB, + TERMKEY_SYM_ENTER, + TERMKEY_SYM_ESCAPE, + + // Special names in G0 + TERMKEY_SYM_SPACE, + TERMKEY_SYM_DEL, + + // Special keys + TERMKEY_SYM_UP, + TERMKEY_SYM_DOWN, + TERMKEY_SYM_LEFT, + TERMKEY_SYM_RIGHT, + TERMKEY_SYM_BEGIN, + TERMKEY_SYM_FIND, + TERMKEY_SYM_INSERT, + TERMKEY_SYM_DELETE, + TERMKEY_SYM_SELECT, + TERMKEY_SYM_PAGEUP, + TERMKEY_SYM_PAGEDOWN, + TERMKEY_SYM_HOME, + TERMKEY_SYM_END, + + // Special keys from terminfo + TERMKEY_SYM_CANCEL, + TERMKEY_SYM_CLEAR, + TERMKEY_SYM_CLOSE, + TERMKEY_SYM_COMMAND, + TERMKEY_SYM_COPY, + TERMKEY_SYM_EXIT, + TERMKEY_SYM_HELP, + TERMKEY_SYM_MARK, + TERMKEY_SYM_MESSAGE, + TERMKEY_SYM_MOVE, + TERMKEY_SYM_OPEN, + TERMKEY_SYM_OPTIONS, + TERMKEY_SYM_PRINT, + TERMKEY_SYM_REDO, + TERMKEY_SYM_REFERENCE, + TERMKEY_SYM_REFRESH, + TERMKEY_SYM_REPLACE, + TERMKEY_SYM_RESTART, + TERMKEY_SYM_RESUME, + TERMKEY_SYM_SAVE, + TERMKEY_SYM_SUSPEND, + TERMKEY_SYM_UNDO, + + // Numeric keypad special keys + TERMKEY_SYM_KP0, + TERMKEY_SYM_KP1, + TERMKEY_SYM_KP2, + TERMKEY_SYM_KP3, + TERMKEY_SYM_KP4, + TERMKEY_SYM_KP5, + TERMKEY_SYM_KP6, + TERMKEY_SYM_KP7, + TERMKEY_SYM_KP8, + TERMKEY_SYM_KP9, + TERMKEY_SYM_KPENTER, + TERMKEY_SYM_KPPLUS, + TERMKEY_SYM_KPMINUS, + TERMKEY_SYM_KPMULT, + TERMKEY_SYM_KPDIV, + TERMKEY_SYM_KPCOMMA, + TERMKEY_SYM_KPPERIOD, + TERMKEY_SYM_KPEQUALS, + + // et cetera ad nauseum + TERMKEY_N_SYMS, +} TermKeySym; + +typedef enum { + TERMKEY_TYPE_UNICODE, + TERMKEY_TYPE_FUNCTION, + TERMKEY_TYPE_KEYSYM, + TERMKEY_TYPE_MOUSE, + TERMKEY_TYPE_POSITION, + TERMKEY_TYPE_MODEREPORT, + TERMKEY_TYPE_DCS, + TERMKEY_TYPE_OSC, + // add other recognised types here + + TERMKEY_TYPE_UNKNOWN_CSI = -1, +} TermKeyType; + +typedef enum { + TERMKEY_MOUSE_UNKNOWN, + TERMKEY_MOUSE_PRESS, + TERMKEY_MOUSE_DRAG, + TERMKEY_MOUSE_RELEASE, +} TermKeyMouseEvent; + +enum { + TERMKEY_KEYMOD_SHIFT = 1 << 0, + TERMKEY_KEYMOD_ALT = 1 << 1, + TERMKEY_KEYMOD_CTRL = 1 << 2, +}; + +typedef struct { + const unsigned char *param; + size_t length; +} TermKeyCsiParam; + +enum { + TERMKEY_FLAG_NOINTERPRET = 1 << 0, // Do not interpret C0//DEL codes if possible + TERMKEY_FLAG_CONVERTKP = 1 << 1, // Convert KP codes to regular keypresses + TERMKEY_FLAG_RAW = 1 << 2, // Input is raw bytes, not UTF-8 + TERMKEY_FLAG_UTF8 = 1 << 3, // Input is definitely UTF-8 + TERMKEY_FLAG_NOTERMIOS = 1 << 4, // Do not make initial termios calls on construction + TERMKEY_FLAG_SPACESYMBOL = 1 << 5, // Sets TERMKEY_CANON_SPACESYMBOL + TERMKEY_FLAG_CTRLC = 1 << 6, // Allow Ctrl-C to be read as normal, disabling SIGINT + TERMKEY_FLAG_EINTR = 1 << 7, // Return ERROR on signal (EINTR) rather than retry + TERMKEY_FLAG_NOSTART = 1 << 8, // Do not call termkey_start() in constructor +}; + +enum { + TERMKEY_CANON_SPACESYMBOL = 1 << 0, // Space is symbolic rather than Unicode + TERMKEY_CANON_DELBS = 1 << 1, // Del is converted to Backspace +}; + +typedef struct { + TermKeyType type; + union { + int codepoint; // TERMKEY_TYPE_UNICODE + int number; // TERMKEY_TYPE_FUNCTION + TermKeySym sym; // TERMKEY_TYPE_KEYSYM + char mouse[4]; // TERMKEY_TYPE_MOUSE + // opaque. see termkey_interpret_mouse + } code; + + int modifiers; + + // Any Unicode character can be UTF-8 encoded in no more than 6 bytes, plus + // terminating NUL + char utf8[7]; +} TermKeyKey; + +// Mostly-undocumented hooks for doing evil evil things +typedef const char *TermKey_Terminfo_Getstr_Hook(const char *name, const char *value, void *data); + +typedef enum { + TERMKEY_FORMAT_LONGMOD = 1 << 0, // Shift-... instead of S-... + TERMKEY_FORMAT_CARETCTRL = 1 << 1, // ^X instead of C-X + TERMKEY_FORMAT_ALTISMETA = 1 << 2, // Meta- or M- instead of Alt- or A- + TERMKEY_FORMAT_WRAPBRACKET = 1 << 3, // Wrap special keys in brackets like + TERMKEY_FORMAT_SPACEMOD = 1 << 4, // M Foo instead of M-Foo + TERMKEY_FORMAT_LOWERMOD = 1 << 5, // meta or m instead of Meta or M + TERMKEY_FORMAT_LOWERSPACE = 1 << 6, // page down instead of PageDown + + TERMKEY_FORMAT_MOUSE_POS = 1 << 8, // Include mouse position if relevant; @ col,line +} TermKeyFormat; + +// Some useful combinations + +#define TERMKEY_FORMAT_VIM (TermKeyFormat)(TERMKEY_FORMAT_ALTISMETA|TERMKEY_FORMAT_WRAPBRACKET) + +typedef struct { + TermKey *tk; + + unibi_term *unibi; // only valid until first 'start' call + + struct trie_node *root; + + char *start_string; + char *stop_string; +} TermKeyTI; diff --git a/src/termkey/termkey.c b/src/termkey/termkey.c deleted file mode 100644 index 832e5a9a9e..0000000000 --- a/src/termkey/termkey.c +++ /dev/null @@ -1,1606 +0,0 @@ -#include "termkey.h" -#include "termkey-internal.h" - -#include -#include -#ifndef _WIN32 -# include -# include -# include -#else -# include -#endif -#include - -#include - -#ifdef _MSC_VER -# define strcaseeq(a,b) (_stricmp(a,b) == 0) -#else -# define strcaseeq(a,b) (strcasecmp(a,b) == 0) -#endif - -void termkey_check_version(int major, int minor) -{ - if(major != TERMKEY_VERSION_MAJOR) { - fprintf(stderr, "libtermkey major version mismatch; %d (wants) != %d (library)\n", - major, TERMKEY_VERSION_MAJOR); - exit(1); - } - - if(minor > TERMKEY_VERSION_MINOR) { - fprintf(stderr, "libtermkey minor version mismatch; %d (wants) > %d (library)\n", - minor, TERMKEY_VERSION_MINOR); - exit(1); - } - - // Happy -} - -static struct TermKeyDriver *drivers[] = { - &termkey_driver_ti, - &termkey_driver_csi, - NULL, -}; - -// Forwards for the "protected" methods -// static void eat_bytes(TermKey *tk, size_t count); -static void emit_codepoint(TermKey *tk, long codepoint, TermKeyKey *key); -static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, size_t *nbytes); -static TermKeyResult peekkey_mouse(TermKey *tk, TermKeyKey *key, size_t *nbytes); - -static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name); -static TermKeySym register_c0_full(TermKey *tk, TermKeySym sym, int modifier_set, int modifier_mask, unsigned char ctrl, const char *name); - -static struct { - TermKeySym sym; - const char *name; -} keynames[] = { - { TERMKEY_SYM_NONE, "NONE" }, - { TERMKEY_SYM_BACKSPACE, "Backspace" }, - { TERMKEY_SYM_TAB, "Tab" }, - { TERMKEY_SYM_ENTER, "Enter" }, - { TERMKEY_SYM_ESCAPE, "Escape" }, - { TERMKEY_SYM_SPACE, "Space" }, - { TERMKEY_SYM_DEL, "DEL" }, - { TERMKEY_SYM_UP, "Up" }, - { TERMKEY_SYM_DOWN, "Down" }, - { TERMKEY_SYM_LEFT, "Left" }, - { TERMKEY_SYM_RIGHT, "Right" }, - { TERMKEY_SYM_BEGIN, "Begin" }, - { TERMKEY_SYM_FIND, "Find" }, - { TERMKEY_SYM_INSERT, "Insert" }, - { TERMKEY_SYM_DELETE, "Delete" }, - { TERMKEY_SYM_SELECT, "Select" }, - { TERMKEY_SYM_PAGEUP, "PageUp" }, - { TERMKEY_SYM_PAGEDOWN, "PageDown" }, - { TERMKEY_SYM_HOME, "Home" }, - { TERMKEY_SYM_END, "End" }, - { TERMKEY_SYM_CANCEL, "Cancel" }, - { TERMKEY_SYM_CLEAR, "Clear" }, - { TERMKEY_SYM_CLOSE, "Close" }, - { TERMKEY_SYM_COMMAND, "Command" }, - { TERMKEY_SYM_COPY, "Copy" }, - { TERMKEY_SYM_EXIT, "Exit" }, - { TERMKEY_SYM_HELP, "Help" }, - { TERMKEY_SYM_MARK, "Mark" }, - { TERMKEY_SYM_MESSAGE, "Message" }, - { TERMKEY_SYM_MOVE, "Move" }, - { TERMKEY_SYM_OPEN, "Open" }, - { TERMKEY_SYM_OPTIONS, "Options" }, - { TERMKEY_SYM_PRINT, "Print" }, - { TERMKEY_SYM_REDO, "Redo" }, - { TERMKEY_SYM_REFERENCE, "Reference" }, - { TERMKEY_SYM_REFRESH, "Refresh" }, - { TERMKEY_SYM_REPLACE, "Replace" }, - { TERMKEY_SYM_RESTART, "Restart" }, - { TERMKEY_SYM_RESUME, "Resume" }, - { TERMKEY_SYM_SAVE, "Save" }, - { TERMKEY_SYM_SUSPEND, "Suspend" }, - { TERMKEY_SYM_UNDO, "Undo" }, - { TERMKEY_SYM_KP0, "KP0" }, - { TERMKEY_SYM_KP1, "KP1" }, - { TERMKEY_SYM_KP2, "KP2" }, - { TERMKEY_SYM_KP3, "KP3" }, - { TERMKEY_SYM_KP4, "KP4" }, - { TERMKEY_SYM_KP5, "KP5" }, - { TERMKEY_SYM_KP6, "KP6" }, - { TERMKEY_SYM_KP7, "KP7" }, - { TERMKEY_SYM_KP8, "KP8" }, - { TERMKEY_SYM_KP9, "KP9" }, - { TERMKEY_SYM_KPENTER, "KPEnter" }, - { TERMKEY_SYM_KPPLUS, "KPPlus" }, - { TERMKEY_SYM_KPMINUS, "KPMinus" }, - { TERMKEY_SYM_KPMULT, "KPMult" }, - { TERMKEY_SYM_KPDIV, "KPDiv" }, - { TERMKEY_SYM_KPCOMMA, "KPComma" }, - { TERMKEY_SYM_KPPERIOD, "KPPeriod" }, - { TERMKEY_SYM_KPEQUALS, "KPEquals" }, - { 0, NULL }, -}; - -// Mouse event names -static const char *evnames[] = { "Unknown", "Press", "Drag", "Release" }; - -#define CHARAT(i) (tk->buffer[tk->buffstart + (i)]) - -#ifdef DEBUG -/* Some internal debugging functions */ - -static void print_buffer(TermKey *tk) -{ - int i; - for(i = 0; i < tk->buffcount && i < 20; i++) - fprintf(stderr, "%02x ", CHARAT(i)); - if(tk->buffcount > 20) - fprintf(stderr, "..."); -} - -static void print_key(TermKey *tk, TermKeyKey *key) -{ - switch(key->type) { - case TERMKEY_TYPE_UNICODE: - fprintf(stderr, "Unicode codepoint=U+%04lx utf8='%s'", key->code.codepoint, key->utf8); - break; - case TERMKEY_TYPE_FUNCTION: - fprintf(stderr, "Function F%d", key->code.number); - break; - case TERMKEY_TYPE_KEYSYM: - fprintf(stderr, "Keysym sym=%d(%s)", key->code.sym, termkey_get_keyname(tk, key->code.sym)); - break; - case TERMKEY_TYPE_MOUSE: - { - TermKeyMouseEvent ev; - int button, line, col; - termkey_interpret_mouse(tk, key, &ev, &button, &line, &col); - fprintf(stderr, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev, button, line, col); - } - break; - case TERMKEY_TYPE_POSITION: - { - int line, col; - termkey_interpret_position(tk, key, &line, &col); - fprintf(stderr, "Position report pos=(%d,%d)\n", line, col); - } - break; - case TERMKEY_TYPE_MODEREPORT: - { - int initial, mode, value; - termkey_interpret_modereport(tk, key, &initial, &mode, &value); - fprintf(stderr, "Mode report mode=%s %d val=%d\n", initial == '?' ? "DEC" : "ANSI", mode, value); - } - break; - case TERMKEY_TYPE_DCS: - fprintf(stderr, "Device Control String"); - break; - case TERMKEY_TYPE_OSC: - fprintf(stderr, "Operating System Control"); - break; - case TERMKEY_TYPE_UNKNOWN_CSI: - fprintf(stderr, "unknown CSI\n"); - break; - } - - int m = key->modifiers; - fprintf(stderr, " mod=%s%s%s+%02x", - (m & TERMKEY_KEYMOD_CTRL ? "C" : ""), - (m & TERMKEY_KEYMOD_ALT ? "A" : ""), - (m & TERMKEY_KEYMOD_SHIFT ? "S" : ""), - m & ~(TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT|TERMKEY_KEYMOD_SHIFT)); -} - -static const char *res2str(TermKeyResult res) -{ - static char errorbuffer[256]; - - switch(res) { - case TERMKEY_RES_KEY: - return "TERMKEY_RES_KEY"; - case TERMKEY_RES_EOF: - return "TERMKEY_RES_EOF"; - case TERMKEY_RES_AGAIN: - return "TERMKEY_RES_AGAIN"; - case TERMKEY_RES_NONE: - return "TERMKEY_RES_NONE"; - case TERMKEY_RES_ERROR: - snprintf(errorbuffer, sizeof errorbuffer, "TERMKEY_RES_ERROR(errno=%d)\n", errno); - return (const char*)errorbuffer; - } - - return "unknown"; -} -#endif - -/* Similar to snprintf(str, size, "%s", src) except it turns CamelCase into - * space separated values - */ -static int snprint_cameltospaces(char *str, size_t size, const char *src) -{ - int prev_lower = 0; - size_t l = 0; - while(*src && l < size - 1) { - if(isupper(*src) && prev_lower) { - if(str) - str[l++] = ' '; - if(l >= size - 1) - break; - } - prev_lower = islower(*src); - str[l++] = tolower(*src++); - } - str[l] = 0; - /* For consistency with snprintf, return the number of bytes that would have - * been written, excluding '\0' */ - while(*src) { - if(isupper(*src) && prev_lower) { - l++; - } - prev_lower = islower(*src); - src++; l++; - } - return l; -} - -/* Similar to strcmp(str, strcamel, n) except that: - * it compares CamelCase in strcamel with space separated values in str; - * it takes char**s and updates them - * n counts bytes of strcamel, not str - */ -static int strpncmp_camel(const char **strp, const char **strcamelp, size_t n) -{ - const char *str = *strp, *strcamel = *strcamelp; - int prev_lower = 0; - - for( ; (*str || *strcamel) && n; n--) { - char b = tolower(*strcamel); - if(isupper(*strcamel) && prev_lower) { - if(*str != ' ') - break; - str++; - if(*str != b) - break; - } - else - if(*str != b) - break; - - prev_lower = islower(*strcamel); - - str++; - strcamel++; - } - - *strp = str; - *strcamelp = strcamel; - return *str - *strcamel; -} - -static TermKey *termkey_alloc(void) -{ - TermKey *tk = malloc(sizeof(TermKey)); - if(!tk) - return NULL; - - /* Default all the object fields but don't allocate anything */ - - tk->fd = -1; - tk->flags = 0; - tk->canonflags = 0; - - tk->buffer = NULL; - tk->buffstart = 0; - tk->buffcount = 0; - tk->buffsize = 256; /* bytes */ - tk->hightide = 0; - -#ifdef HAVE_TERMIOS - tk->restore_termios_valid = 0; -#endif - - tk->ti_getstr_hook = NULL; - tk->ti_getstr_hook_data = NULL; - - tk->waittime = 50; /* msec */ - - tk->is_closed = 0; - tk->is_started = 0; - - tk->nkeynames = 64; - tk->keynames = NULL; - - for(int i = 0; i < 32; i++) - tk->c0[i].sym = TERMKEY_SYM_NONE; - - tk->drivers = NULL; - - tk->method.emit_codepoint = &emit_codepoint; - tk->method.peekkey_simple = &peekkey_simple; - tk->method.peekkey_mouse = &peekkey_mouse; - - return tk; -} - -static int termkey_init(TermKey *tk, const char *term) -{ - tk->buffer = malloc(tk->buffsize); - if(!tk->buffer) - return 0; - - tk->keynames = malloc(sizeof(tk->keynames[0]) * tk->nkeynames); - if(!tk->keynames) - goto abort_free_buffer; - - int i; - for(i = 0; i < tk->nkeynames; i++) - tk->keynames[i] = NULL; - - for(i = 0; keynames[i].name; i++) - if(termkey_register_keyname(tk, keynames[i].sym, keynames[i].name) == -1) - goto abort_free_keynames; - - register_c0(tk, TERMKEY_SYM_TAB, 0x09, NULL); - register_c0(tk, TERMKEY_SYM_ENTER, 0x0d, NULL); - register_c0(tk, TERMKEY_SYM_ESCAPE, 0x1b, NULL); - - struct TermKeyDriverNode *tail = NULL; - - for(i = 0; drivers[i]; i++) { - void *info = (*drivers[i]->new_driver)(tk, term); - if(!info) - continue; - -#ifdef DEBUG - fprintf(stderr, "Loading the %s driver...\n", drivers[i]->name); -#endif - - struct TermKeyDriverNode *thisdrv = malloc(sizeof(*thisdrv)); - if(!thisdrv) - goto abort_free_drivers; - - thisdrv->driver = drivers[i]; - thisdrv->info = info; - thisdrv->next = NULL; - - if(!tail) - tk->drivers = thisdrv; - else - tail->next = thisdrv; - - tail = thisdrv; - -#ifdef DEBUG - fprintf(stderr, "Loaded %s driver\n", drivers[i]->name); -#endif - } - - if(!tk->drivers) { - errno = ENOENT; - goto abort_free_keynames; - } - - return 1; - -abort_free_drivers: - for(struct TermKeyDriverNode *p = tk->drivers; p; ) { - (*p->driver->free_driver)(p->info); - struct TermKeyDriverNode *next = p->next; - free(p); - p = next; - } - -abort_free_keynames: - free(tk->keynames); - -abort_free_buffer: - free(tk->buffer); - - return 0; -} - -TermKey *termkey_new(int fd, int flags) -{ - TermKey *tk = termkey_alloc(); - if(!tk) - return NULL; - - tk->fd = fd; - - if(!(flags & (TERMKEY_FLAG_RAW|TERMKEY_FLAG_UTF8))) { - char *e; - - /* Most OSes will set .UTF-8. Some will set .utf8. Try to be fairly - * generous in parsing these - */ - if(((e = getenv("LANG")) || (e = getenv("LC_MESSAGES")) || (e = getenv("LC_ALL"))) && - (e = strchr(e, '.')) && e++ && - (strcaseeq(e, "UTF-8") || strcaseeq(e, "UTF8"))) - flags |= TERMKEY_FLAG_UTF8; - else - flags |= TERMKEY_FLAG_RAW; - } - - termkey_set_flags(tk, flags); - - const char *term = getenv("TERM"); - - if(!termkey_init(tk, term)) - goto abort; - - if(!(flags & TERMKEY_FLAG_NOSTART) && !termkey_start(tk)) - goto abort; - - return tk; - -abort: - free(tk); - return NULL; -} - -TermKey *termkey_new_abstract(const char *term, int flags) -{ - TermKey *tk = termkey_alloc(); - if(!tk) - return NULL; - - tk->fd = -1; - - termkey_set_flags(tk, flags); - - if(!termkey_init(tk, term)) { - free(tk); - return NULL; - } - - if(!(flags & TERMKEY_FLAG_NOSTART) && !termkey_start(tk)) - goto abort; - - return tk; - -abort: - free(tk); - return NULL; -} - -void termkey_free(TermKey *tk) -{ - free(tk->buffer); tk->buffer = NULL; - free(tk->keynames); tk->keynames = NULL; - - struct TermKeyDriverNode *p; - for(p = tk->drivers; p; ) { - (*p->driver->free_driver)(p->info); - struct TermKeyDriverNode *next = p->next; - free(p); - p = next; - } - - free(tk); -} - -void termkey_destroy(TermKey *tk) -{ - if(tk->is_started) - termkey_stop(tk); - - termkey_free(tk); -} - -void termkey_hook_terminfo_getstr(TermKey *tk, TermKey_Terminfo_Getstr_Hook *hookfn, void *data) -{ - tk->ti_getstr_hook = hookfn; - tk->ti_getstr_hook_data = data; -} - -int termkey_start(TermKey *tk) -{ - if(tk->is_started) - return 1; - -#ifdef HAVE_TERMIOS - if(tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) { - struct termios termios; - if(tcgetattr(tk->fd, &termios) == 0) { - tk->restore_termios = termios; - tk->restore_termios_valid = 1; - - termios.c_iflag &= ~(IXON|INLCR|ICRNL); - termios.c_lflag &= ~(ICANON|ECHO -#ifdef IEXTEN - | IEXTEN -#endif - ); - termios.c_cc[VMIN] = 1; - termios.c_cc[VTIME] = 0; - - if(tk->flags & TERMKEY_FLAG_CTRLC) - /* want no signal keys at all, so just disable ISIG */ - termios.c_lflag &= ~ISIG; - else { - /* Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT */ - termios.c_cc[VQUIT] = _POSIX_VDISABLE; - termios.c_cc[VSUSP] = _POSIX_VDISABLE; - /* Some OSes have Ctrl-Y==VDSUSP */ -#ifdef VDSUSP - termios.c_cc[VDSUSP] = _POSIX_VDISABLE; -#endif - } - -#ifdef DEBUG - fprintf(stderr, "Setting termios(3) flags\n"); -#endif - tcsetattr(tk->fd, TCSANOW, &termios); - } - } -#endif - - struct TermKeyDriverNode *p; - for(p = tk->drivers; p; p = p->next) - if(p->driver->start_driver) - if(!(*p->driver->start_driver)(tk, p->info)) - return 0; - -#ifdef DEBUG - fprintf(stderr, "Drivers started; termkey instance %p is ready\n", tk); -#endif - - tk->is_started = 1; - return 1; -} - -int termkey_stop(TermKey *tk) -{ - if(!tk->is_started) - return 1; - - struct TermKeyDriverNode *p; - for(p = tk->drivers; p; p = p->next) - if(p->driver->stop_driver) - (*p->driver->stop_driver)(tk, p->info); - -#ifdef HAVE_TERMIOS - if(tk->restore_termios_valid) - tcsetattr(tk->fd, TCSANOW, &tk->restore_termios); -#endif - - tk->is_started = 0; - - return 1; -} - -int termkey_is_started(TermKey *tk) -{ - return tk->is_started; -} - -int termkey_get_fd(TermKey *tk) -{ - return tk->fd; -} - -int termkey_get_flags(TermKey *tk) -{ - return tk->flags; -} - -void termkey_set_flags(TermKey *tk, int newflags) -{ - tk->flags = newflags; - - if(tk->flags & TERMKEY_FLAG_SPACESYMBOL) - tk->canonflags |= TERMKEY_CANON_SPACESYMBOL; - else - tk->canonflags &= ~TERMKEY_CANON_SPACESYMBOL; -} - -void termkey_set_waittime(TermKey *tk, int msec) -{ - tk->waittime = msec; -} - -int termkey_get_waittime(TermKey *tk) -{ - return tk->waittime; -} - -int termkey_get_canonflags(TermKey *tk) -{ - return tk->canonflags; -} - -void termkey_set_canonflags(TermKey *tk, int flags) -{ - tk->canonflags = flags; - - if(tk->canonflags & TERMKEY_CANON_SPACESYMBOL) - tk->flags |= TERMKEY_FLAG_SPACESYMBOL; - else - tk->flags &= ~TERMKEY_FLAG_SPACESYMBOL; -} - -size_t termkey_get_buffer_size(TermKey *tk) -{ - return tk->buffsize; -} - -int termkey_set_buffer_size(TermKey *tk, size_t size) -{ - unsigned char *buffer = realloc(tk->buffer, size); - if(!buffer) - return 0; - - tk->buffer = buffer; - tk->buffsize = size; - - return 1; -} - -size_t termkey_get_buffer_remaining(TermKey *tk) -{ - /* Return the total number of free bytes in the buffer, because that's what - * is available to the user. */ - return tk->buffsize - tk->buffcount; -} - -static void eat_bytes(TermKey *tk, size_t count) -{ - if(count >= tk->buffcount) { - tk->buffstart = 0; - tk->buffcount = 0; - return; - } - - tk->buffstart += count; - tk->buffcount -= count; -} - -static inline unsigned int utf8_seqlen(long codepoint) -{ - if(codepoint < 0x0000080) return 1; - if(codepoint < 0x0000800) return 2; - if(codepoint < 0x0010000) return 3; - if(codepoint < 0x0200000) return 4; - if(codepoint < 0x4000000) return 5; - return 6; -} - -static void fill_utf8(TermKeyKey *key) -{ - long codepoint = key->code.codepoint; - int nbytes = utf8_seqlen(codepoint); - - key->utf8[nbytes] = 0; - - // This is easier done backwards - int b = nbytes; - while(b > 1) { - b--; - key->utf8[b] = 0x80 | (codepoint & 0x3f); - codepoint >>= 6; - } - - switch(nbytes) { - case 1: key->utf8[0] = (codepoint & 0x7f); break; - case 2: key->utf8[0] = 0xc0 | (codepoint & 0x1f); break; - case 3: key->utf8[0] = 0xe0 | (codepoint & 0x0f); break; - case 4: key->utf8[0] = 0xf0 | (codepoint & 0x07); break; - case 5: key->utf8[0] = 0xf8 | (codepoint & 0x03); break; - case 6: key->utf8[0] = 0xfc | (codepoint & 0x01); break; - } -} - -#define UTF8_INVALID 0xFFFD -static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, long *cp, size_t *nbytep) -{ - unsigned int nbytes; - - unsigned char b0 = bytes[0]; - - if(b0 < 0x80) { - // Single byte ASCII - *cp = b0; - *nbytep = 1; - return TERMKEY_RES_KEY; - } - else if(b0 < 0xc0) { - // Starts with a continuation byte - that's not right - *cp = UTF8_INVALID; - *nbytep = 1; - return TERMKEY_RES_KEY; - } - else if(b0 < 0xe0) { - nbytes = 2; - *cp = b0 & 0x1f; - } - else if(b0 < 0xf0) { - nbytes = 3; - *cp = b0 & 0x0f; - } - else if(b0 < 0xf8) { - nbytes = 4; - *cp = b0 & 0x07; - } - else if(b0 < 0xfc) { - nbytes = 5; - *cp = b0 & 0x03; - } - else if(b0 < 0xfe) { - nbytes = 6; - *cp = b0 & 0x01; - } - else { - *cp = UTF8_INVALID; - *nbytep = 1; - return TERMKEY_RES_KEY; - } - - for(unsigned int b = 1; b < nbytes; b++) { - unsigned char cb; - - if(b >= len) - return TERMKEY_RES_AGAIN; - - cb = bytes[b]; - if(cb < 0x80 || cb >= 0xc0) { - *cp = UTF8_INVALID; - *nbytep = b; - return TERMKEY_RES_KEY; - } - - *cp <<= 6; - *cp |= cb & 0x3f; - } - - // Check for overlong sequences - if(nbytes > utf8_seqlen(*cp)) - *cp = UTF8_INVALID; - - // Check for UTF-16 surrogates or invalid *cps - if((*cp >= 0xD800 && *cp <= 0xDFFF) || - *cp == 0xFFFE || - *cp == 0xFFFF) - *cp = UTF8_INVALID; - - *nbytep = nbytes; - return TERMKEY_RES_KEY; -} - -static void emit_codepoint(TermKey *tk, long codepoint, TermKeyKey *key) -{ - if(codepoint == 0) { - // ASCII NUL = Ctrl-Space - key->type = TERMKEY_TYPE_KEYSYM; - key->code.sym = TERMKEY_SYM_SPACE; - key->modifiers = TERMKEY_KEYMOD_CTRL; - } - else if(codepoint < 0x20) { - // C0 range - key->code.codepoint = 0; - key->modifiers = 0; - - if(!(tk->flags & TERMKEY_FLAG_NOINTERPRET) && tk->c0[codepoint].sym != TERMKEY_SYM_UNKNOWN) { - key->code.sym = tk->c0[codepoint].sym; - key->modifiers |= tk->c0[codepoint].modifier_set; - } - - if(!key->code.sym) { - key->type = TERMKEY_TYPE_UNICODE; - /* Generically modified Unicode ought not report the SHIFT state, or else - * we get into complications trying to report Shift-; vs : and so on... - * In order to be able to represent Ctrl-Shift-A as CTRL modified - * unicode A, we need to call Ctrl-A simply 'a', lowercase - */ - if(codepoint+0x40 >= 'A' && codepoint+0x40 <= 'Z') - // it's a letter - use lowercase instead - key->code.codepoint = codepoint + 0x60; - else - key->code.codepoint = codepoint + 0x40; - key->modifiers = TERMKEY_KEYMOD_CTRL; - } - else { - key->type = TERMKEY_TYPE_KEYSYM; - } - } - else if(codepoint == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) { - // ASCII DEL - key->type = TERMKEY_TYPE_KEYSYM; - key->code.sym = TERMKEY_SYM_DEL; - key->modifiers = 0; - } - else if(codepoint >= 0x20 && codepoint < 0x80) { - // ASCII lowbyte range - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint; - key->modifiers = 0; - } - else if(codepoint >= 0x80 && codepoint < 0xa0) { - // UTF-8 never starts with a C1 byte. So we can be sure of these - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint - 0x40; - key->modifiers = TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT; - } - else { - // UTF-8 codepoint - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = codepoint; - key->modifiers = 0; - } - - termkey_canonicalise(tk, key); - - if(key->type == TERMKEY_TYPE_UNICODE) - fill_utf8(key); -} - -void termkey_canonicalise(TermKey *tk, TermKeyKey *key) -{ - int flags = tk->canonflags; - - if(flags & TERMKEY_CANON_SPACESYMBOL) { - if(key->type == TERMKEY_TYPE_UNICODE && key->code.codepoint == 0x20) { - key->type = TERMKEY_TYPE_KEYSYM; - key->code.sym = TERMKEY_SYM_SPACE; - } - } - else { - if(key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_SPACE) { - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = 0x20; - fill_utf8(key); - } - } - - if(flags & TERMKEY_CANON_DELBS) { - if(key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_DEL) { - key->code.sym = TERMKEY_SYM_BACKSPACE; - } - } -} - -static TermKeyResult peekkey(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep) -{ - int again = 0; - - if(!tk->is_started) { - errno = EINVAL; - return TERMKEY_RES_ERROR; - } - -#ifdef DEBUG - fprintf(stderr, "getkey(force=%d): buffer ", force); - print_buffer(tk); - fprintf(stderr, "\n"); -#endif - - if(tk->hightide) { - tk->buffstart += tk->hightide; - tk->buffcount -= tk->hightide; - tk->hightide = 0; - } - - TermKeyResult ret; - struct TermKeyDriverNode *p; - for(p = tk->drivers; p; p = p->next) { - ret = (p->driver->peekkey)(tk, p->info, key, force, nbytep); - -#ifdef DEBUG - fprintf(stderr, "Driver %s yields %s\n", p->driver->name, res2str(ret)); -#endif - - switch(ret) { - case TERMKEY_RES_KEY: -#ifdef DEBUG - print_key(tk, key); fprintf(stderr, "\n"); -#endif - // Slide the data down to stop it running away - { - size_t halfsize = tk->buffsize / 2; - - if(tk->buffstart > halfsize) { - memcpy(tk->buffer, tk->buffer + halfsize, halfsize); - tk->buffstart -= halfsize; - } - } - - /* fallthrough */ - case TERMKEY_RES_EOF: - case TERMKEY_RES_ERROR: - return ret; - - case TERMKEY_RES_AGAIN: - if(!force) - again = 1; - - /* fallthrough */ - case TERMKEY_RES_NONE: - break; - } - } - - if(again) - return TERMKEY_RES_AGAIN; - - ret = peekkey_simple(tk, key, force, nbytep); - -#ifdef DEBUG - fprintf(stderr, "getkey_simple(force=%d) yields %s\n", force, res2str(ret)); - if(ret == TERMKEY_RES_KEY) { - print_key(tk, key); fprintf(stderr, "\n"); - } -#endif - - return ret; -} - -static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep) -{ - if(tk->buffcount == 0) - return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE; - - unsigned char b0 = CHARAT(0); - - if(b0 == 0x1b) { - // Escape-prefixed value? Might therefore be Alt+key - if(tk->buffcount == 1) { - // This might be an press, or it may want to be part of a longer - // sequence - if(!force) - return TERMKEY_RES_AGAIN; - - (*tk->method.emit_codepoint)(tk, b0, key); - *nbytep = 1; - return TERMKEY_RES_KEY; - } - - // Try another key there - tk->buffstart++; - tk->buffcount--; - - // Run the full driver - TermKeyResult metakey_result = peekkey(tk, key, force, nbytep); - - tk->buffstart--; - tk->buffcount++; - - switch(metakey_result) { - case TERMKEY_RES_KEY: - key->modifiers |= TERMKEY_KEYMOD_ALT; - (*nbytep)++; - break; - - case TERMKEY_RES_NONE: - case TERMKEY_RES_EOF: - case TERMKEY_RES_AGAIN: - case TERMKEY_RES_ERROR: - break; - } - - return metakey_result; - } - else if(b0 < 0xa0) { - // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte - (*tk->method.emit_codepoint)(tk, b0, key); - *nbytep = 1; - return TERMKEY_RES_KEY; - } - else if(tk->flags & TERMKEY_FLAG_UTF8) { - // Some UTF-8 - long codepoint; - TermKeyResult res = parse_utf8(tk->buffer + tk->buffstart, tk->buffcount, &codepoint, nbytep); - - if(res == TERMKEY_RES_AGAIN && force) { - /* There weren't enough bytes for a complete UTF-8 sequence but caller - * demands an answer. About the best thing we can do here is eat as many - * bytes as we have, and emit a UTF8_INVALID. If the remaining bytes - * arrive later, they'll be invalid too. - */ - codepoint = UTF8_INVALID; - *nbytep = tk->buffcount; - res = TERMKEY_RES_KEY; - } - - key->type = TERMKEY_TYPE_UNICODE; - key->modifiers = 0; - (*tk->method.emit_codepoint)(tk, codepoint, key); - return res; - } - else { - // Non UTF-8 case - just report the raw byte - key->type = TERMKEY_TYPE_UNICODE; - key->code.codepoint = b0; - key->modifiers = 0; - - key->utf8[0] = key->code.codepoint; - key->utf8[1] = 0; - - *nbytep = 1; - - return TERMKEY_RES_KEY; - } -} - -static TermKeyResult peekkey_mouse(TermKey *tk, TermKeyKey *key, size_t *nbytep) -{ - if(tk->buffcount < 3) - return TERMKEY_RES_AGAIN; - - key->type = TERMKEY_TYPE_MOUSE; - key->code.mouse[0] = CHARAT(0) - 0x20; - key->code.mouse[1] = CHARAT(1) - 0x20; - key->code.mouse[2] = CHARAT(2) - 0x20; - key->code.mouse[3] = 0; - - key->modifiers = (key->code.mouse[0] & 0x1c) >> 2; - key->code.mouse[0] &= ~0x1c; - - *nbytep = 3; - return TERMKEY_RES_KEY; -} - -TermKeyResult termkey_getkey(TermKey *tk, TermKeyKey *key) -{ - size_t nbytes = 0; - TermKeyResult ret = peekkey(tk, key, 0, &nbytes); - - if(ret == TERMKEY_RES_KEY) - eat_bytes(tk, nbytes); - - if(ret == TERMKEY_RES_AGAIN) - /* Call peekkey() again in force mode to obtain whatever it can */ - (void)peekkey(tk, key, 1, &nbytes); - /* Don't eat it yet though */ - - return ret; -} - -TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key) -{ - size_t nbytes = 0; - TermKeyResult ret = peekkey(tk, key, 1, &nbytes); - - if(ret == TERMKEY_RES_KEY) - eat_bytes(tk, nbytes); - - return ret; -} - -#ifndef _WIN32 -TermKeyResult termkey_waitkey(TermKey *tk, TermKeyKey *key) -{ - if(tk->fd == -1) { - errno = EBADF; - return TERMKEY_RES_ERROR; - } - - while(1) { - TermKeyResult ret = termkey_getkey(tk, key); - - switch(ret) { - case TERMKEY_RES_KEY: - case TERMKEY_RES_EOF: - case TERMKEY_RES_ERROR: - return ret; - - case TERMKEY_RES_NONE: - ret = termkey_advisereadable(tk); - if(ret == TERMKEY_RES_ERROR) - return ret; - break; - - case TERMKEY_RES_AGAIN: - { - if(tk->is_closed) - // We're closed now. Never going to get more bytes so just go with - // what we have - return termkey_getkey_force(tk, key); - - struct pollfd fd; - -retry: - fd.fd = tk->fd; - fd.events = POLLIN; - - int pollret = poll(&fd, 1, tk->waittime); - if(pollret == -1) { - if(errno == EINTR && !(tk->flags & TERMKEY_FLAG_EINTR)) - goto retry; - - return TERMKEY_RES_ERROR; - } - - if(fd.revents & (POLLIN|POLLHUP|POLLERR)) - ret = termkey_advisereadable(tk); - else - ret = TERMKEY_RES_NONE; - - if(ret == TERMKEY_RES_ERROR) - return ret; - if(ret == TERMKEY_RES_NONE) - return termkey_getkey_force(tk, key); - } - break; - } - } - - /* UNREACHABLE */ -} -#endif - -TermKeyResult termkey_advisereadable(TermKey *tk) -{ - ssize_t len; - - if(tk->fd == -1) { - errno = EBADF; - return TERMKEY_RES_ERROR; - } - - if(tk->buffstart) { - memmove(tk->buffer, tk->buffer + tk->buffstart, tk->buffcount); - tk->buffstart = 0; - } - - /* Not expecting it ever to be greater but doesn't hurt to handle that */ - if(tk->buffcount >= tk->buffsize) { - errno = ENOMEM; - return TERMKEY_RES_ERROR; - } - -retry: - len = read(tk->fd, tk->buffer + tk->buffcount, tk->buffsize - tk->buffcount); - - if(len == -1) { - if(errno == EAGAIN) - return TERMKEY_RES_NONE; - else if(errno == EINTR && !(tk->flags & TERMKEY_FLAG_EINTR)) - goto retry; - else - return TERMKEY_RES_ERROR; - } - else if(len < 1) { - tk->is_closed = 1; - return TERMKEY_RES_NONE; - } - else { - tk->buffcount += len; - return TERMKEY_RES_AGAIN; - } -} - -size_t termkey_push_bytes(TermKey *tk, const char *bytes, size_t len) -{ - if(tk->buffstart) { - memmove(tk->buffer, tk->buffer + tk->buffstart, tk->buffcount); - tk->buffstart = 0; - } - - /* Not expecting it ever to be greater but doesn't hurt to handle that */ - if(tk->buffcount >= tk->buffsize) { - errno = ENOMEM; - return (size_t)-1; - } - - if(len > tk->buffsize - tk->buffcount) - len = tk->buffsize - tk->buffcount; - - // memcpy(), not strncpy() in case of null bytes in input - memcpy(tk->buffer + tk->buffcount, bytes, len); - tk->buffcount += len; - - return len; -} - -TermKeySym termkey_register_keyname(TermKey *tk, TermKeySym sym, const char *name) -{ - if(!sym) - sym = tk->nkeynames; - - if(sym >= tk->nkeynames) { - const char **new_keynames = realloc(tk->keynames, sizeof(new_keynames[0]) * (sym + 1)); - if(!new_keynames) - return -1; - - tk->keynames = new_keynames; - - // Fill in the hole - for(int i = tk->nkeynames; i < sym; i++) - tk->keynames[i] = NULL; - - tk->nkeynames = sym + 1; - } - - tk->keynames[sym] = name; - - return sym; -} - -const char *termkey_get_keyname(TermKey *tk, TermKeySym sym) -{ - if(sym == TERMKEY_SYM_UNKNOWN) - return "UNKNOWN"; - - if(sym < tk->nkeynames) - return tk->keynames[sym]; - - return "UNKNOWN"; -} - -static const char *termkey_lookup_keyname_format(TermKey *tk, const char *str, TermKeySym *sym, TermKeyFormat format) -{ - /* We store an array, so we can't do better than a linear search. Doesn't - * matter because user won't be calling this too often */ - - for(*sym = 0; *sym < tk->nkeynames; (*sym)++) { - const char *thiskey = tk->keynames[*sym]; - if(!thiskey) - continue; - size_t len = strlen(thiskey); - if(format & TERMKEY_FORMAT_LOWERSPACE) { - const char *thisstr = str; - if(strpncmp_camel(&thisstr, &thiskey, len) == 0) - return thisstr; - } - else { - if(strncmp(str, thiskey, len) == 0) - return (char *)str + len; - } - } - - return NULL; -} - -const char *termkey_lookup_keyname(TermKey *tk, const char *str, TermKeySym *sym) -{ - return termkey_lookup_keyname_format(tk, str, sym, 0); -} - -TermKeySym termkey_keyname2sym(TermKey *tk, const char *keyname) -{ - TermKeySym sym; - const char *endp = termkey_lookup_keyname(tk, keyname, &sym); - if(!endp || endp[0]) - return TERMKEY_SYM_UNKNOWN; - return sym; -} - -static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name) -{ - return register_c0_full(tk, sym, 0, 0, ctrl, name); -} - -static TermKeySym register_c0_full(TermKey *tk, TermKeySym sym, int modifier_set, int modifier_mask, unsigned char ctrl, const char *name) -{ - if(ctrl >= 0x20) { - errno = EINVAL; - return -1; - } - - if(name) - sym = termkey_register_keyname(tk, sym, name); - - tk->c0[ctrl].sym = sym; - tk->c0[ctrl].modifier_set = modifier_set; - tk->c0[ctrl].modifier_mask = modifier_mask; - - return sym; -} - -/* Previous name for this function - * No longer declared in termkey.h but it remains in the compiled library for - * backward-compatibility reasons. - */ -size_t termkey_snprint_key(TermKey *tk, char *buffer, size_t len, TermKeyKey *key, TermKeyFormat format) -{ - return termkey_strfkey(tk, buffer, len, key, format); -} - -static struct modnames { - const char *shift, *alt, *ctrl; -} -modnames[] = { - { "S", "A", "C" }, // 0 - { "Shift", "Alt", "Ctrl" }, // LONGMOD - { "S", "M", "C" }, // ALTISMETA - { "Shift", "Meta", "Ctrl" }, // ALTISMETA+LONGMOD - { "s", "a", "c" }, // LOWERMOD - { "shift", "alt", "ctrl" }, // LOWERMOD+LONGMOD - { "s", "m", "c" }, // LOWERMOD+ALTISMETA - { "shift", "meta", "ctrl" }, // LOWERMOD+ALTISMETA+LONGMOD -}; - -size_t termkey_strfkey(TermKey *tk, char *buffer, size_t len, TermKeyKey *key, TermKeyFormat format) -{ - size_t pos = 0; - size_t l = 0; - - struct modnames *mods = &modnames[!!(format & TERMKEY_FORMAT_LONGMOD) + - !!(format & TERMKEY_FORMAT_ALTISMETA) * 2 + - !!(format & TERMKEY_FORMAT_LOWERMOD) * 4]; - - int wrapbracket = (format & TERMKEY_FORMAT_WRAPBRACKET) && - (key->type != TERMKEY_TYPE_UNICODE || key->modifiers != 0); - - char sep = (format & TERMKEY_FORMAT_SPACEMOD) ? ' ' : '-'; - - if(format & TERMKEY_FORMAT_CARETCTRL && - key->type == TERMKEY_TYPE_UNICODE && - key->modifiers == TERMKEY_KEYMOD_CTRL) { - long codepoint = key->code.codepoint; - - // Handle some of the special cases first - if(codepoint >= 'a' && codepoint <= 'z') { - l = snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c", (char)codepoint - 0x20); - if(l <= 0) return pos; - pos += l; - return pos; - } - else if((codepoint >= '@' && codepoint < 'A') || - (codepoint > 'Z' && codepoint <= '_')) { - l = snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c", (char)codepoint); - if(l <= 0) return pos; - pos += l; - return pos; - } - } - - if(wrapbracket) { - l = snprintf(buffer + pos, len - pos, "<"); - if(l <= 0) return pos; - pos += l; - } - - if(key->modifiers & TERMKEY_KEYMOD_ALT) { - l = snprintf(buffer + pos, len - pos, "%s%c", mods->alt, sep); - if(l <= 0) return pos; - pos += l; - } - - if(key->modifiers & TERMKEY_KEYMOD_CTRL) { - l = snprintf(buffer + pos, len - pos, "%s%c", mods->ctrl, sep); - if(l <= 0) return pos; - pos += l; - } - - if(key->modifiers & TERMKEY_KEYMOD_SHIFT) { - l = snprintf(buffer + pos, len - pos, "%s%c", mods->shift, sep); - if(l <= 0) return pos; - pos += l; - } - - switch(key->type) { - case TERMKEY_TYPE_UNICODE: - if(!key->utf8[0]) // In case of user-supplied key structures - fill_utf8(key); - l = snprintf(buffer + pos, len - pos, "%s", key->utf8); - break; - case TERMKEY_TYPE_KEYSYM: - { - const char *name = termkey_get_keyname(tk, key->code.sym); - if(format & TERMKEY_FORMAT_LOWERSPACE) - l = snprint_cameltospaces(buffer + pos, len - pos, name); - else - l = snprintf(buffer + pos, len - pos, "%s", name); - } - break; - case TERMKEY_TYPE_FUNCTION: - l = snprintf(buffer + pos, len - pos, "%c%d", - (format & TERMKEY_FORMAT_LOWERSPACE ? 'f' : 'F'), key->code.number); - break; - case TERMKEY_TYPE_MOUSE: - { - TermKeyMouseEvent ev; - int button; - int line, col; - termkey_interpret_mouse(tk, key, &ev, &button, &line, &col); - - l = snprintf(buffer + pos, len - pos, "Mouse%s(%d)", - evnames[ev], button); - - if(format & TERMKEY_FORMAT_MOUSE_POS) { - if(l <= 0) return pos; - pos += l; - - l = snprintf(buffer + pos, len - pos, " @ (%u,%u)", col, line); - } - } - break; - case TERMKEY_TYPE_POSITION: - l = snprintf(buffer + pos, len - pos, "Position"); - break; - case TERMKEY_TYPE_MODEREPORT: - { - int initial, mode, value; - termkey_interpret_modereport(tk, key, &initial, &mode, &value); - if(initial) - l = snprintf(buffer + pos, len - pos, "Mode(%c%d=%d)", initial, mode, value); - else - l = snprintf(buffer + pos, len - pos, "Mode(%d=%d)", mode, value); - } - case TERMKEY_TYPE_DCS: - l = snprintf(buffer + pos, len - pos, "DCS"); - break; - case TERMKEY_TYPE_OSC: - l = snprintf(buffer + pos, len - pos, "OSC"); - break; - case TERMKEY_TYPE_UNKNOWN_CSI: - l = snprintf(buffer + pos, len - pos, "CSI %c", key->code.number & 0xff); - break; - } - - if(l <= 0) return pos; - pos += l; - - if(wrapbracket) { - l = snprintf(buffer + pos, len - pos, ">"); - if(l <= 0) return pos; - pos += l; - } - - return pos; -} - -const char *termkey_strpkey(TermKey *tk, const char *str, TermKeyKey *key, TermKeyFormat format) -{ - struct modnames *mods = &modnames[!!(format & TERMKEY_FORMAT_LONGMOD) + - !!(format & TERMKEY_FORMAT_ALTISMETA) * 2 + - !!(format & TERMKEY_FORMAT_LOWERMOD) * 4]; - - key->modifiers = 0; - - if((format & TERMKEY_FORMAT_CARETCTRL) && str[0] == '^' && str[1]) { - str = termkey_strpkey(tk, str+1, key, format & ~TERMKEY_FORMAT_CARETCTRL); - - if(!str || - key->type != TERMKEY_TYPE_UNICODE || - key->code.codepoint < '@' || key->code.codepoint > '_' || - key->modifiers != 0) - return NULL; - - if(key->code.codepoint >= 'A' && key->code.codepoint <= 'Z') - key->code.codepoint += 0x20; - key->modifiers = TERMKEY_KEYMOD_CTRL; - fill_utf8(key); - return (char *)str; - } - - const char *sep_at; - - while((sep_at = strchr(str, (format & TERMKEY_FORMAT_SPACEMOD) ? ' ' : '-'))) { - size_t n = sep_at - str; - - if(n == strlen(mods->alt) && strncmp(mods->alt, str, n) == 0) - key->modifiers |= TERMKEY_KEYMOD_ALT; - else if(n == strlen(mods->ctrl) && strncmp(mods->ctrl, str, n) == 0) - key->modifiers |= TERMKEY_KEYMOD_CTRL; - else if(n == strlen(mods->shift) && strncmp(mods->shift, str, n) == 0) - key->modifiers |= TERMKEY_KEYMOD_SHIFT; - - else - break; - - str = sep_at + 1; - } - - size_t nbytes; - ssize_t snbytes; - const char *endstr; - int button; - char event_name[32]; - - if((endstr = termkey_lookup_keyname_format(tk, str, &key->code.sym, format))) { - key->type = TERMKEY_TYPE_KEYSYM; - str = endstr; - } - else if(sscanf(str, "F%d%zn", &key->code.number, &snbytes) == 1) { - key->type = TERMKEY_TYPE_FUNCTION; - str += snbytes; - } - else if(sscanf(str, "Mouse%31[^(](%d)%zn", event_name, &button, &snbytes) == 2) { - str += snbytes; - key->type = TERMKEY_TYPE_MOUSE; - - TermKeyMouseEvent ev = TERMKEY_MOUSE_UNKNOWN; - for(size_t i = 0; i < sizeof(evnames)/sizeof(evnames[0]); i++) { - if(strcmp(evnames[i], event_name) == 0) { - ev = TERMKEY_MOUSE_UNKNOWN + i; - break; - } - } - - int code; - switch(ev) { - case TERMKEY_MOUSE_PRESS: - case TERMKEY_MOUSE_DRAG: - code = button - 1; - if(ev == TERMKEY_MOUSE_DRAG) { - code |= 0x20; - } - break; - case TERMKEY_MOUSE_RELEASE: - code = 3; - break; - default: - code = 128; - break; - } - key->code.mouse[0] = code; - - unsigned int line = 0, col = 0; - if((format & TERMKEY_FORMAT_MOUSE_POS) && sscanf(str, " @ (%u,%u)%zn", &col, &line, &snbytes) == 2) { - str += snbytes; - } - termkey_key_set_linecol(key, col, line); - } - // Unicode must be last - else if(parse_utf8((unsigned const char *)str, strlen(str), &key->code.codepoint, &nbytes) == TERMKEY_RES_KEY) { - key->type = TERMKEY_TYPE_UNICODE; - fill_utf8(key); - str += nbytes; - } - else - return NULL; - - termkey_canonicalise(tk, key); - - return (char *)str; -} - -int termkey_keycmp(TermKey *tk, const TermKeyKey *key1p, const TermKeyKey *key2p) -{ - /* Copy the key structs since we'll be modifying them */ - TermKeyKey key1 = *key1p, key2 = *key2p; - - termkey_canonicalise(tk, &key1); - termkey_canonicalise(tk, &key2); - - if(key1.type != key2.type) - return key1.type - key2.type; - - switch(key1.type) { - case TERMKEY_TYPE_UNICODE: - if(key1.code.codepoint != key2.code.codepoint) - return key1.code.codepoint - key2.code.codepoint; - break; - case TERMKEY_TYPE_KEYSYM: - if(key1.code.sym != key2.code.sym) - return key1.code.sym - key2.code.sym; - break; - case TERMKEY_TYPE_FUNCTION: - case TERMKEY_TYPE_UNKNOWN_CSI: - if(key1.code.number != key2.code.number) - return key1.code.number - key2.code.number; - break; - case TERMKEY_TYPE_MOUSE: - { - int cmp = strncmp(key1.code.mouse, key2.code.mouse, 4); - if(cmp != 0) - return cmp; - } - break; - case TERMKEY_TYPE_POSITION: - { - int line1, col1, line2, col2; - termkey_interpret_position(tk, &key1, &line1, &col1); - termkey_interpret_position(tk, &key2, &line2, &col2); - if(line1 != line2) - return line1 - line2; - return col1 - col2; - } - break; - case TERMKEY_TYPE_DCS: - case TERMKEY_TYPE_OSC: - return key1p - key2p; - case TERMKEY_TYPE_MODEREPORT: - { - int initial1, initial2, mode1, mode2, value1, value2; - termkey_interpret_modereport(tk, &key1, &initial1, &mode1, &value1); - termkey_interpret_modereport(tk, &key2, &initial2, &mode2, &value2); - if(initial1 != initial2) - return initial1 - initial2; - if(mode1 != mode2) - return mode1 - mode2; - return value1 - value2; - } - } - - return key1.modifiers - key2.modifiers; -} diff --git a/src/termkey/termkey.h b/src/termkey/termkey.h deleted file mode 100644 index 94405f6516..0000000000 --- a/src/termkey/termkey.h +++ /dev/null @@ -1,256 +0,0 @@ -#ifdef __cplusplus -extern "C" { -#endif - -#ifndef GUARD_TERMKEY_H_ -#define GUARD_TERMKEY_H_ - -#include -#include - -#define TERMKEY_VERSION_MAJOR 0 -#define TERMKEY_VERSION_MINOR 22 - -#define TERMKEY_CHECK_VERSION \ - termkey_check_version(TERMKEY_VERSION_MAJOR, TERMKEY_VERSION_MINOR) - -typedef enum { - TERMKEY_SYM_UNKNOWN = -1, - TERMKEY_SYM_NONE = 0, - - /* Special names in C0 */ - TERMKEY_SYM_BACKSPACE, - TERMKEY_SYM_TAB, - TERMKEY_SYM_ENTER, - TERMKEY_SYM_ESCAPE, - - /* Special names in G0 */ - TERMKEY_SYM_SPACE, - TERMKEY_SYM_DEL, - - /* Special keys */ - TERMKEY_SYM_UP, - TERMKEY_SYM_DOWN, - TERMKEY_SYM_LEFT, - TERMKEY_SYM_RIGHT, - TERMKEY_SYM_BEGIN, - TERMKEY_SYM_FIND, - TERMKEY_SYM_INSERT, - TERMKEY_SYM_DELETE, - TERMKEY_SYM_SELECT, - TERMKEY_SYM_PAGEUP, - TERMKEY_SYM_PAGEDOWN, - TERMKEY_SYM_HOME, - TERMKEY_SYM_END, - - /* Special keys from terminfo */ - TERMKEY_SYM_CANCEL, - TERMKEY_SYM_CLEAR, - TERMKEY_SYM_CLOSE, - TERMKEY_SYM_COMMAND, - TERMKEY_SYM_COPY, - TERMKEY_SYM_EXIT, - TERMKEY_SYM_HELP, - TERMKEY_SYM_MARK, - TERMKEY_SYM_MESSAGE, - TERMKEY_SYM_MOVE, - TERMKEY_SYM_OPEN, - TERMKEY_SYM_OPTIONS, - TERMKEY_SYM_PRINT, - TERMKEY_SYM_REDO, - TERMKEY_SYM_REFERENCE, - TERMKEY_SYM_REFRESH, - TERMKEY_SYM_REPLACE, - TERMKEY_SYM_RESTART, - TERMKEY_SYM_RESUME, - TERMKEY_SYM_SAVE, - TERMKEY_SYM_SUSPEND, - TERMKEY_SYM_UNDO, - - /* Numeric keypad special keys */ - TERMKEY_SYM_KP0, - TERMKEY_SYM_KP1, - TERMKEY_SYM_KP2, - TERMKEY_SYM_KP3, - TERMKEY_SYM_KP4, - TERMKEY_SYM_KP5, - TERMKEY_SYM_KP6, - TERMKEY_SYM_KP7, - TERMKEY_SYM_KP8, - TERMKEY_SYM_KP9, - TERMKEY_SYM_KPENTER, - TERMKEY_SYM_KPPLUS, - TERMKEY_SYM_KPMINUS, - TERMKEY_SYM_KPMULT, - TERMKEY_SYM_KPDIV, - TERMKEY_SYM_KPCOMMA, - TERMKEY_SYM_KPPERIOD, - TERMKEY_SYM_KPEQUALS, - - /* et cetera ad nauseum */ - TERMKEY_N_SYMS -} TermKeySym; - -typedef enum { - TERMKEY_TYPE_UNICODE, - TERMKEY_TYPE_FUNCTION, - TERMKEY_TYPE_KEYSYM, - TERMKEY_TYPE_MOUSE, - TERMKEY_TYPE_POSITION, - TERMKEY_TYPE_MODEREPORT, - TERMKEY_TYPE_DCS, - TERMKEY_TYPE_OSC, - /* add other recognised types here */ - - TERMKEY_TYPE_UNKNOWN_CSI = -1 -} TermKeyType; - -typedef enum { - TERMKEY_RES_NONE, - TERMKEY_RES_KEY, - TERMKEY_RES_EOF, - TERMKEY_RES_AGAIN, - TERMKEY_RES_ERROR -} TermKeyResult; - -typedef enum { - TERMKEY_MOUSE_UNKNOWN, - TERMKEY_MOUSE_PRESS, - TERMKEY_MOUSE_DRAG, - TERMKEY_MOUSE_RELEASE -} TermKeyMouseEvent; - -enum { - TERMKEY_KEYMOD_SHIFT = 1 << 0, - TERMKEY_KEYMOD_ALT = 1 << 1, - TERMKEY_KEYMOD_CTRL = 1 << 2 -}; - -typedef struct { - TermKeyType type; - union { - long codepoint; /* TERMKEY_TYPE_UNICODE */ - int number; /* TERMKEY_TYPE_FUNCTION */ - TermKeySym sym; /* TERMKEY_TYPE_KEYSYM */ - char mouse[4]; /* TERMKEY_TYPE_MOUSE */ - /* opaque. see termkey_interpret_mouse */ - } code; - - int modifiers; - - /* Any Unicode character can be UTF-8 encoded in no more than 6 bytes, plus - * terminating NUL */ - char utf8[7]; -} TermKeyKey; - -typedef struct { - const unsigned char *param; - size_t length; -} TermKeyCsiParam; - -typedef struct TermKey TermKey; - -enum { - TERMKEY_FLAG_NOINTERPRET = 1 << 0, /* Do not interpret C0//DEL codes if possible */ - TERMKEY_FLAG_CONVERTKP = 1 << 1, /* Convert KP codes to regular keypresses */ - TERMKEY_FLAG_RAW = 1 << 2, /* Input is raw bytes, not UTF-8 */ - TERMKEY_FLAG_UTF8 = 1 << 3, /* Input is definitely UTF-8 */ - TERMKEY_FLAG_NOTERMIOS = 1 << 4, /* Do not make initial termios calls on construction */ - TERMKEY_FLAG_SPACESYMBOL = 1 << 5, /* Sets TERMKEY_CANON_SPACESYMBOL */ - TERMKEY_FLAG_CTRLC = 1 << 6, /* Allow Ctrl-C to be read as normal, disabling SIGINT */ - TERMKEY_FLAG_EINTR = 1 << 7, /* Return ERROR on signal (EINTR) rather than retry */ - TERMKEY_FLAG_NOSTART = 1 << 8 /* Do not call termkey_start() in constructor */ -}; - -enum { - TERMKEY_CANON_SPACESYMBOL = 1 << 0, /* Space is symbolic rather than Unicode */ - TERMKEY_CANON_DELBS = 1 << 1 /* Del is converted to Backspace */ -}; - -void termkey_check_version(int major, int minor); - -TermKey *termkey_new(int fd, int flags); -TermKey *termkey_new_abstract(const char *term, int flags); -void termkey_free(TermKey *tk); -void termkey_destroy(TermKey *tk); - -/* Mostly-undocumented hooks for doing evil evil things */ -typedef const char *TermKey_Terminfo_Getstr_Hook(const char *name, const char *value, void *data); -void termkey_hook_terminfo_getstr(TermKey *tk, TermKey_Terminfo_Getstr_Hook *hookfn, void *data); - -int termkey_start(TermKey *tk); -int termkey_stop(TermKey *tk); -int termkey_is_started(TermKey *tk); - -int termkey_get_fd(TermKey *tk); - -int termkey_get_flags(TermKey *tk); -void termkey_set_flags(TermKey *tk, int newflags); - -int termkey_get_waittime(TermKey *tk); -void termkey_set_waittime(TermKey *tk, int msec); - -int termkey_get_canonflags(TermKey *tk); -void termkey_set_canonflags(TermKey *tk, int); - -size_t termkey_get_buffer_size(TermKey *tk); -int termkey_set_buffer_size(TermKey *tk, size_t size); - -size_t termkey_get_buffer_remaining(TermKey *tk); - -void termkey_canonicalise(TermKey *tk, TermKeyKey *key); - -TermKeyResult termkey_getkey(TermKey *tk, TermKeyKey *key); -TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key); -TermKeyResult termkey_waitkey(TermKey *tk, TermKeyKey *key); - -TermKeyResult termkey_advisereadable(TermKey *tk); - -size_t termkey_push_bytes(TermKey *tk, const char *bytes, size_t len); - -TermKeySym termkey_register_keyname(TermKey *tk, TermKeySym sym, const char *name); -const char *termkey_get_keyname(TermKey *tk, TermKeySym sym); -const char *termkey_lookup_keyname(TermKey *tk, const char *str, TermKeySym *sym); - -TermKeySym termkey_keyname2sym(TermKey *tk, const char *keyname); - -TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKeyMouseEvent *event, int *button, int *line, int *col); - -TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int *line, int *col); - -TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, int *initial, int *mode, int *value); - -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); - -typedef enum { - TERMKEY_FORMAT_LONGMOD = 1 << 0, /* Shift-... instead of S-... */ - TERMKEY_FORMAT_CARETCTRL = 1 << 1, /* ^X instead of C-X */ - TERMKEY_FORMAT_ALTISMETA = 1 << 2, /* Meta- or M- instead of Alt- or A- */ - TERMKEY_FORMAT_WRAPBRACKET = 1 << 3, /* Wrap special keys in brackets like */ - TERMKEY_FORMAT_SPACEMOD = 1 << 4, /* M Foo instead of M-Foo */ - TERMKEY_FORMAT_LOWERMOD = 1 << 5, /* meta or m instead of Meta or M */ - TERMKEY_FORMAT_LOWERSPACE = 1 << 6, /* page down instead of PageDown */ - - TERMKEY_FORMAT_MOUSE_POS = 1 << 8 /* Include mouse position if relevant; @ col,line */ -} TermKeyFormat; - -/* Some useful combinations */ - -#define TERMKEY_FORMAT_VIM (TermKeyFormat)(TERMKEY_FORMAT_ALTISMETA|TERMKEY_FORMAT_WRAPBRACKET) -#define TERMKEY_FORMAT_URWID (TermKeyFormat)(TERMKEY_FORMAT_LONGMOD|TERMKEY_FORMAT_ALTISMETA| \ - TERMKEY_FORMAT_LOWERMOD|TERMKEY_FORMAT_SPACEMOD|TERMKEY_FORMAT_LOWERSPACE) - -size_t termkey_strfkey(TermKey *tk, char *buffer, size_t len, TermKeyKey *key, TermKeyFormat format); -const char *termkey_strpkey(TermKey *tk, const char *str, TermKeyKey *key, TermKeyFormat format); - -int termkey_keycmp(TermKey *tk, const TermKeyKey *key1, const TermKeyKey *key2); - -#endif - -#ifdef __cplusplus -} -#endif diff --git a/src/vterm/keyboard.c b/src/vterm/keyboard.c index d31c8be12d..7e062c8c02 100644 --- a/src/vterm/keyboard.c +++ b/src/vterm/keyboard.c @@ -1,8 +1,7 @@ #include "vterm_internal.h" - #include -#include "utf8.h" +#include "nvim/tui/termkey/termkey.h" void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) { diff --git a/src/vterm/mouse.c b/src/vterm/mouse.c index f74abc3d9d..a9d3fe4ca9 100644 --- a/src/vterm/mouse.c +++ b/src/vterm/mouse.c @@ -1,6 +1,6 @@ #include "vterm_internal.h" -#include "utf8.h" +#include "nvim/tui/termkey/termkey.h" static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row) { diff --git a/src/vterm/screen.c b/src/vterm/screen.c index bd3cbd6bd0..e63db483f9 100644 --- a/src/vterm/screen.c +++ b/src/vterm/screen.c @@ -2,9 +2,10 @@ #include #include +#include "nvim/mbyte.h" +#include "nvim/tui/termkey/termkey.h" #include "rect.h" -#include "utf8.h" #define UNICODE_SPACE 0x20 #define UNICODE_LINEFEED 0x0a @@ -922,7 +923,7 @@ static size_t _get_chars(const VTermScreen *screen, const int utf8, void *buffer #define PUT(c) \ if(utf8) { \ - size_t thislen = utf8_seqlen(c); \ + size_t thislen = utf_char2len(c); \ if(buffer && outpos + thislen <= len) \ outpos += fill_utf8((c), (char *)buffer + outpos); \ else \ diff --git a/src/vterm/utf8.h b/src/vterm/utf8.h deleted file mode 100644 index 9a336d357f..0000000000 --- a/src/vterm/utf8.h +++ /dev/null @@ -1,39 +0,0 @@ -/* The following functions copied and adapted from libtermkey - * - * http://www.leonerd.org.uk/code/libtermkey/ - */ -static inline unsigned int utf8_seqlen(long codepoint) -{ - if(codepoint < 0x0000080) return 1; - if(codepoint < 0x0000800) return 2; - if(codepoint < 0x0010000) return 3; - if(codepoint < 0x0200000) return 4; - if(codepoint < 0x4000000) return 5; - return 6; -} - -/* Does NOT NUL-terminate the buffer */ -static int fill_utf8(long codepoint, char *str) -{ - int nbytes = utf8_seqlen(codepoint); - - // This is easier done backwards - int b = nbytes; - while(b > 1) { - b--; - str[b] = 0x80 | (codepoint & 0x3f); - codepoint >>= 6; - } - - switch(nbytes) { - case 1: str[0] = (codepoint & 0x7f); break; - case 2: str[0] = 0xc0 | (codepoint & 0x1f); break; - case 3: str[0] = 0xe0 | (codepoint & 0x0f); break; - case 4: str[0] = 0xf0 | (codepoint & 0x07); break; - case 5: str[0] = 0xf8 | (codepoint & 0x03); break; - case 6: str[0] = 0xfc | (codepoint & 0x01); break; - } - - return nbytes; -} -/* end copy */