aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authordundargoc <gocdundar@gmail.com>2025-01-03 15:40:46 +0100
committerdundargoc <33953936+dundargoc@users.noreply.github.com>2025-01-07 12:35:24 +0100
commitd8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8 (patch)
tree3dd3eb1954d36db51a1eab82ea890d18cf085189 /src
parent4d9405991963a53ed31089ed456d9f4cfebc325d (diff)
downloadrneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.tar.gz
rneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.tar.bz2
rneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.zip
refactor: adopt vterm
We have changed too much to consider it a mere bundled dependency (such as unicode handling in e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d), and can consider it our own at this point.
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt5
-rw-r--r--src/nvim/mbyte.h1
-rw-r--r--src/nvim/mbyte_defs.h3
-rw-r--r--src/nvim/terminal.c10
-rw-r--r--src/nvim/vterm/LICENSE (renamed from src/vterm/LICENSE)0
-rw-r--r--src/nvim/vterm/README.md1
-rw-r--r--src/nvim/vterm/encoding.c278
-rw-r--r--src/nvim/vterm/encoding.h10
-rw-r--r--src/nvim/vterm/keyboard.c252
-rw-r--r--src/nvim/vterm/keyboard.h10
-rw-r--r--src/nvim/vterm/mouse.c113
-rw-r--r--src/nvim/vterm/mouse.h10
-rw-r--r--src/nvim/vterm/parser.c411
-rw-r--r--src/nvim/vterm/parser.h9
-rw-r--r--src/nvim/vterm/pen.c644
-rw-r--r--src/nvim/vterm/pen.h9
-rw-r--r--src/nvim/vterm/screen.c1103
-rw-r--r--src/nvim/vterm/screen.h9
-rw-r--r--src/nvim/vterm/state.c2310
-rw-r--r--src/nvim/vterm/state.h7
-rw-r--r--src/nvim/vterm/vterm.c335
-rw-r--r--src/nvim/vterm/vterm.h161
-rw-r--r--src/nvim/vterm/vterm_defs.h319
-rw-r--r--src/nvim/vterm/vterm_internal_defs.h266
-rw-r--r--src/nvim/vterm/vterm_keycodes_defs.h (renamed from src/vterm/vterm_keycodes.h)13
-rw-r--r--src/vterm/encoding.c230
-rw-r--r--src/vterm/encoding/DECdrawing.inc36
-rw-r--r--src/vterm/encoding/uk.inc6
-rw-r--r--src/vterm/keyboard.c225
-rw-r--r--src/vterm/mouse.c99
-rw-r--r--src/vterm/parser.c408
-rw-r--r--src/vterm/pen.c678
-rw-r--r--src/vterm/rect.h56
-rw-r--r--src/vterm/screen.c1174
-rw-r--r--src/vterm/state.c2281
-rw-r--r--src/vterm/vterm.c432
-rw-r--r--src/vterm/vterm.h638
-rw-r--r--src/vterm/vterm_internal.h298
38 files changed, 6276 insertions, 6574 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index cce8ab5b25..767f5087c1 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -361,8 +361,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 ../vterm/*.c)
-file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../vterm/*.h)
+file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c)
+file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h)
file(GLOB NLUA0_SOURCES CONFIGURE_DEPENDS ../mpack/*.c)
@@ -391,6 +391,7 @@ foreach(subdir
msgpack_rpc
tui
tui/termkey
+ vterm
event
eval
lua
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index 2da051fca2..674e0a2638 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -12,7 +12,6 @@
#include "nvim/mbyte_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
-typedef utf8proc_int32_t GraphemeState;
#define GRAPHEME_STATE_INIT 0
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/mbyte_defs.h b/src/nvim/mbyte_defs.h
index 7f2d1ba6ce..e308a81a5d 100644
--- a/src/nvim/mbyte_defs.h
+++ b/src/nvim/mbyte_defs.h
@@ -2,6 +2,7 @@
#include <stdbool.h>
#include <stdint.h>
+#include <utf8proc.h>
#include "nvim/iconv_defs.h"
@@ -71,3 +72,5 @@ typedef struct {
int8_t begin_off; ///< Offset to the first byte of the codepoint.
int8_t end_off; ///< Offset to one past the end byte of the codepoint.
} CharBoundsOff;
+
+typedef utf8proc_int32_t GraphemeState;
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 0743eda374..d7ed709906 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -93,9 +93,15 @@
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/vim_defs.h"
+#include "nvim/vterm/keyboard.h"
+#include "nvim/vterm/mouse.h"
+#include "nvim/vterm/parser.h"
+#include "nvim/vterm/pen.h"
+#include "nvim/vterm/screen.h"
+#include "nvim/vterm/state.h"
+#include "nvim/vterm/vterm.h"
+#include "nvim/vterm/vterm_keycodes_defs.h"
#include "nvim/window.h"
-#include "vterm/vterm.h"
-#include "vterm/vterm_keycodes.h"
typedef struct {
VimState state;
diff --git a/src/vterm/LICENSE b/src/nvim/vterm/LICENSE
index 0d051634b2..0d051634b2 100644
--- a/src/vterm/LICENSE
+++ b/src/nvim/vterm/LICENSE
diff --git a/src/nvim/vterm/README.md b/src/nvim/vterm/README.md
new file mode 100644
index 0000000000..4cc43bfb94
--- /dev/null
+++ b/src/nvim/vterm/README.md
@@ -0,0 +1 @@
+Adopted from [libvterm](https://www.leonerd.org.uk/code/libvterm/)
diff --git a/src/nvim/vterm/encoding.c b/src/nvim/vterm/encoding.c
new file mode 100644
index 0000000000..cc3208cfa2
--- /dev/null
+++ b/src/nvim/vterm/encoding.c
@@ -0,0 +1,278 @@
+#include "nvim/vterm/encoding.h"
+#include "nvim/vterm/vterm_internal_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/encoding.c.generated.h"
+#endif
+
+#define UNICODE_INVALID 0xFFFD
+
+#if defined(DEBUG) && DEBUG > 1
+# define DEBUG_PRINT_UTF8
+#endif
+
+struct UTF8DecoderData {
+ // number of bytes remaining in this codepoint
+ int bytes_remaining;
+
+ // number of bytes total in this codepoint once it's finished
+ // (for detecting overlongs)
+ int bytes_total;
+
+ int this_cp;
+};
+
+static void init_utf8(VTermEncoding *enc, void *data_)
+{
+ struct UTF8DecoderData *data = data_;
+
+ data->bytes_remaining = 0;
+ data->bytes_total = 0;
+}
+
+static void decode_utf8(VTermEncoding *enc, void *data_, uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct UTF8DecoderData *data = data_;
+
+#ifdef DEBUG_PRINT_UTF8
+ printf("BEGIN UTF-8\n");
+#endif
+
+ for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ uint8_t c = (uint8_t)bytes[*pos];
+
+#ifdef DEBUG_PRINT_UTF8
+ printf(" pos=%zd c=%02x rem=%d\n", *pos, c, data->bytes_remaining);
+#endif
+
+ if (c < 0x20) { // C0
+ return;
+ } else if (c >= 0x20 && c < 0x7f) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ cp[(*cpi)++] = c;
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 char: U+%04x\n", c);
+#endif
+ data->bytes_remaining = 0;
+ } else if (c == 0x7f) { // DEL
+ return;
+ } else if (c >= 0x80 && c < 0xc0) {
+ if (!data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ continue;
+ }
+
+ data->this_cp <<= 6;
+ data->this_cp |= c & 0x3f;
+ data->bytes_remaining--;
+
+ if (!data->bytes_remaining) {
+#ifdef DEBUG_PRINT_UTF8
+ printf(" UTF-8 raw char U+%04x bytelen=%d ", data->this_cp, data->bytes_total);
+#endif
+ // Check for overlong sequences
+ switch (data->bytes_total) {
+ case 2:
+ if (data->this_cp < 0x0080) {
+ data->this_cp = UNICODE_INVALID;
+ }
+ break;
+ case 3:
+ if (data->this_cp < 0x0800) {
+ data->this_cp = UNICODE_INVALID;
+ }
+ break;
+ case 4:
+ if (data->this_cp < 0x10000) {
+ data->this_cp = UNICODE_INVALID;
+ }
+ break;
+ case 5:
+ if (data->this_cp < 0x200000) {
+ data->this_cp = UNICODE_INVALID;
+ }
+ break;
+ case 6:
+ if (data->this_cp < 0x4000000) {
+ data->this_cp = UNICODE_INVALID;
+ }
+ break;
+ }
+ // Now look for plain invalid ones
+ if ((data->this_cp >= 0xD800 && data->this_cp <= 0xDFFF)
+ || data->this_cp == 0xFFFE
+ || data->this_cp == 0xFFFF) {
+ data->this_cp = UNICODE_INVALID;
+ }
+#ifdef DEBUG_PRINT_UTF8
+ printf(" char: U+%04x\n", data->this_cp);
+#endif
+ cp[(*cpi)++] = (uint32_t)data->this_cp;
+ }
+ } else if (c >= 0xc0 && c < 0xe0) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ data->this_cp = c & 0x1f;
+ data->bytes_total = 2;
+ data->bytes_remaining = 1;
+ } else if (c >= 0xe0 && c < 0xf0) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ data->this_cp = c & 0x0f;
+ data->bytes_total = 3;
+ data->bytes_remaining = 2;
+ } else if (c >= 0xf0 && c < 0xf8) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ data->this_cp = c & 0x07;
+ data->bytes_total = 4;
+ data->bytes_remaining = 3;
+ } else if (c >= 0xf8 && c < 0xfc) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ data->this_cp = c & 0x03;
+ data->bytes_total = 5;
+ data->bytes_remaining = 4;
+ } else if (c >= 0xfc && c < 0xfe) {
+ if (data->bytes_remaining) {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+
+ data->this_cp = c & 0x01;
+ data->bytes_total = 6;
+ data->bytes_remaining = 5;
+ } else {
+ cp[(*cpi)++] = UNICODE_INVALID;
+ }
+ }
+}
+
+static VTermEncoding encoding_utf8 = {
+ .init = &init_utf8,
+ .decode = &decode_utf8,
+};
+
+static void decode_usascii(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ int is_gr = bytes[*pos] & 0x80;
+
+ for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ uint8_t c = (uint8_t)(bytes[*pos] ^ is_gr);
+
+ if (c < 0x20 || c == 0x7f || c >= 0x80) {
+ return;
+ }
+
+ cp[(*cpi)++] = c;
+ }
+}
+
+static VTermEncoding encoding_usascii = {
+ .decode = &decode_usascii,
+};
+
+struct StaticTableEncoding {
+ const VTermEncoding enc;
+ const uint32_t chars[128];
+};
+
+static void decode_table(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen,
+ const char bytes[], size_t *pos, size_t bytelen)
+{
+ struct StaticTableEncoding *table = (struct StaticTableEncoding *)enc;
+ int is_gr = bytes[*pos] & 0x80;
+
+ for (; *pos < bytelen && *cpi < cplen; (*pos)++) {
+ uint8_t c = (uint8_t)(bytes[*pos] ^ is_gr);
+
+ if (c < 0x20 || c == 0x7f || c >= 0x80) {
+ return;
+ }
+
+ if (table->chars[c]) {
+ cp[(*cpi)++] = table->chars[c];
+ } else {
+ cp[(*cpi)++] = c;
+ }
+ }
+}
+
+static const struct StaticTableEncoding encoding_DECdrawing = {
+ { .decode = &decode_table },
+ {
+ [0x60] = 0x25C6, // BLACK DIAMOND
+ [0x61] = 0x2592, // MEDIUM SHADE (checkerboard)
+ [0x62] = 0x2409, // SYMBOL FOR HORIZONTAL TAB
+ [0x63] = 0x240C, // SYMBOL FOR FORM FEED
+ [0x64] = 0x240D, // SYMBOL FOR CARRIAGE RETURN
+ [0x65] = 0x240A, // SYMBOL FOR LINE FEED
+ [0x66] = 0x00B0, // DEGREE SIGN
+ [0x67] = 0x00B1, // PLUS-MINUS SIGN (plus or minus)
+ [0x68] = 0x2424, // SYMBOL FOR NEW LINE
+ [0x69] = 0x240B, // SYMBOL FOR VERTICAL TAB
+ [0x6a] = 0x2518, // BOX DRAWINGS LIGHT UP AND LEFT (bottom-right corner)
+ [0x6b] = 0x2510, // BOX DRAWINGS LIGHT DOWN AND LEFT (top-right corner)
+ [0x6c] = 0x250C, // BOX DRAWINGS LIGHT DOWN AND RIGHT (top-left corner)
+ [0x6d] = 0x2514, // BOX DRAWINGS LIGHT UP AND RIGHT (bottom-left corner)
+ [0x6e] = 0x253C, // BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL (crossing lines)
+ [0x6f] = 0x23BA, // HORIZONTAL SCAN LINE-1
+ [0x70] = 0x23BB, // HORIZONTAL SCAN LINE-3
+ [0x71] = 0x2500, // BOX DRAWINGS LIGHT HORIZONTAL
+ [0x72] = 0x23BC, // HORIZONTAL SCAN LINE-7
+ [0x73] = 0x23BD, // HORIZONTAL SCAN LINE-9
+ [0x74] = 0x251C, // BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+ [0x75] = 0x2524, // BOX DRAWINGS LIGHT VERTICAL AND LEFT
+ [0x76] = 0x2534, // BOX DRAWINGS LIGHT UP AND HORIZONTAL
+ [0x77] = 0x252C, // BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+ [0x78] = 0x2502, // BOX DRAWINGS LIGHT VERTICAL
+ [0x79] = 0x2A7D, // LESS-THAN OR SLANTED EQUAL-TO
+ [0x7a] = 0x2A7E, // GREATER-THAN OR SLANTED EQUAL-TO
+ [0x7b] = 0x03C0, // GREEK SMALL LETTER PI
+ [0x7c] = 0x2260, // NOT EQUAL TO
+ [0x7d] = 0x00A3, // POUND SIGN
+ [0x7e] = 0x00B7, // MIDDLE DOT
+ }
+};
+
+static const struct StaticTableEncoding encoding_uk = {
+ { .decode = &decode_table },
+ {
+ [0x23] = 0x00a3, // £
+ }
+};
+
+static struct {
+ VTermEncodingType type;
+ char designation;
+ VTermEncoding *enc;
+}
+encodings[] = {
+ { ENC_UTF8, 'u', &encoding_utf8 },
+ { ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing },
+ { ENC_SINGLE_94, 'A', (VTermEncoding *)&encoding_uk },
+ { ENC_SINGLE_94, 'B', &encoding_usascii },
+ { 0 },
+};
+
+VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation)
+{
+ for (int i = 0; encodings[i].designation; i++) {
+ if (encodings[i].type == type && encodings[i].designation == designation) {
+ return encodings[i].enc;
+ }
+ }
+ return NULL;
+}
diff --git a/src/nvim/vterm/encoding.h b/src/nvim/vterm/encoding.h
new file mode 100644
index 0000000000..204b6d90c9
--- /dev/null
+++ b/src/nvim/vterm/encoding.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stddef.h>
+
+#include "nvim/vterm/vterm_defs.h"
+#include "nvim/vterm/vterm_internal_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/encoding.h.generated.h"
+#endif
diff --git a/src/nvim/vterm/keyboard.c b/src/nvim/vterm/keyboard.c
new file mode 100644
index 0000000000..696b09157e
--- /dev/null
+++ b/src/nvim/vterm/keyboard.c
@@ -0,0 +1,252 @@
+#include <stdio.h>
+
+#include "nvim/ascii_defs.h"
+#include "nvim/tui/termkey/termkey.h"
+#include "nvim/vterm/keyboard.h"
+#include "nvim/vterm/vterm.h"
+#include "nvim/vterm/vterm_internal_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/keyboard.c.generated.h"
+#endif
+
+void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod)
+{
+ // The shift modifier is never important for Unicode characters apart from Space
+ if (c != ' ') {
+ mod &= (unsigned)~VTERM_MOD_SHIFT;
+ }
+
+ if (mod == 0) {
+ // Normal text - ignore just shift
+ char str[6];
+ int seqlen = fill_utf8((int)c, str);
+ vterm_push_output_bytes(vt, str, (size_t)seqlen);
+ return;
+ }
+
+ int needs_CSIu;
+ switch (c) {
+ // Special Ctrl- letters that can't be represented elsewise
+ case 'i':
+ case 'j':
+ case 'm':
+ case '[':
+ needs_CSIu = 1;
+ break;
+ // Ctrl-\ ] ^ _ don't need CSUu
+ case '\\':
+ case ']':
+ case '^':
+ case '_':
+ needs_CSIu = 0;
+ break;
+ // Shift-space needs CSIu
+ case ' ':
+ needs_CSIu = !!(mod & VTERM_MOD_SHIFT);
+ break;
+ // All other characters needs CSIu except for letters a-z
+ default:
+ needs_CSIu = (c < 'a' || c > 'z');
+ }
+
+ // ALT we can just prefix with ESC; anything else requires CSI u
+ if (needs_CSIu && (mod & (unsigned)~VTERM_MOD_ALT)) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1);
+ return;
+ }
+
+ if (mod & VTERM_MOD_CTRL) {
+ c &= 0x1f;
+ }
+
+ vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c);
+}
+
+typedef struct {
+ enum {
+ KEYCODE_NONE,
+ KEYCODE_LITERAL,
+ KEYCODE_TAB,
+ KEYCODE_ENTER,
+ KEYCODE_SS3,
+ KEYCODE_CSI,
+ KEYCODE_CSI_CURSOR,
+ KEYCODE_CSINUM,
+ KEYCODE_KEYPAD,
+ } type;
+ char literal;
+ int csinum;
+} keycodes_s;
+
+static keycodes_s keycodes[] = {
+ { KEYCODE_NONE, NUL, 0 }, // NONE
+
+ { KEYCODE_ENTER, '\r', 0 }, // ENTER
+ { KEYCODE_TAB, '\t', 0 }, // TAB
+ { KEYCODE_LITERAL, '\x7f', 0 }, // BACKSPACE == ASCII DEL
+ { KEYCODE_LITERAL, '\x1b', 0 }, // ESCAPE
+
+ { KEYCODE_CSI_CURSOR, 'A', 0 }, // UP
+ { KEYCODE_CSI_CURSOR, 'B', 0 }, // DOWN
+ { KEYCODE_CSI_CURSOR, 'D', 0 }, // LEFT
+ { KEYCODE_CSI_CURSOR, 'C', 0 }, // RIGHT
+
+ { KEYCODE_CSINUM, '~', 2 }, // INS
+ { KEYCODE_CSINUM, '~', 3 }, // DEL
+ { KEYCODE_CSI_CURSOR, 'H', 0 }, // HOME
+ { KEYCODE_CSI_CURSOR, 'F', 0 }, // END
+ { KEYCODE_CSINUM, '~', 5 }, // PAGEUP
+ { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN
+};
+
+static keycodes_s keycodes_fn[] = {
+ { KEYCODE_NONE, NUL, 0 }, // F0 - shouldn't happen
+ { KEYCODE_SS3, 'P', 0 }, // F1
+ { KEYCODE_SS3, 'Q', 0 }, // F2
+ { KEYCODE_SS3, 'R', 0 }, // F3
+ { KEYCODE_SS3, 'S', 0 }, // F4
+ { KEYCODE_CSINUM, '~', 15 }, // F5
+ { KEYCODE_CSINUM, '~', 17 }, // F6
+ { KEYCODE_CSINUM, '~', 18 }, // F7
+ { KEYCODE_CSINUM, '~', 19 }, // F8
+ { KEYCODE_CSINUM, '~', 20 }, // F9
+ { KEYCODE_CSINUM, '~', 21 }, // F10
+ { KEYCODE_CSINUM, '~', 23 }, // F11
+ { KEYCODE_CSINUM, '~', 24 }, // F12
+};
+
+static keycodes_s keycodes_kp[] = {
+ { KEYCODE_KEYPAD, '0', 'p' }, // KP_0
+ { KEYCODE_KEYPAD, '1', 'q' }, // KP_1
+ { KEYCODE_KEYPAD, '2', 'r' }, // KP_2
+ { KEYCODE_KEYPAD, '3', 's' }, // KP_3
+ { KEYCODE_KEYPAD, '4', 't' }, // KP_4
+ { KEYCODE_KEYPAD, '5', 'u' }, // KP_5
+ { KEYCODE_KEYPAD, '6', 'v' }, // KP_6
+ { KEYCODE_KEYPAD, '7', 'w' }, // KP_7
+ { KEYCODE_KEYPAD, '8', 'x' }, // KP_8
+ { KEYCODE_KEYPAD, '9', 'y' }, // KP_9
+ { KEYCODE_KEYPAD, '*', 'j' }, // KP_MULT
+ { KEYCODE_KEYPAD, '+', 'k' }, // KP_PLUS
+ { KEYCODE_KEYPAD, ',', 'l' }, // KP_COMMA
+ { KEYCODE_KEYPAD, '-', 'm' }, // KP_MINUS
+ { KEYCODE_KEYPAD, '.', 'n' }, // KP_PERIOD
+ { KEYCODE_KEYPAD, '/', 'o' }, // KP_DIVIDE
+ { KEYCODE_KEYPAD, '\n', 'M' }, // KP_ENTER
+ { KEYCODE_KEYPAD, '=', 'X' }, // KP_EQUAL
+};
+
+void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod)
+{
+ if (key == VTERM_KEY_NONE) {
+ return;
+ }
+
+ keycodes_s k;
+ if (key < VTERM_KEY_FUNCTION_0) {
+ if (key >= sizeof(keycodes)/sizeof(keycodes[0])) {
+ return;
+ }
+ k = keycodes[key];
+ } else if (key >= VTERM_KEY_FUNCTION_0 && key <= VTERM_KEY_FUNCTION_MAX) {
+ if ((key - VTERM_KEY_FUNCTION_0) >= sizeof(keycodes_fn)/sizeof(keycodes_fn[0])) {
+ return;
+ }
+ k = keycodes_fn[key - VTERM_KEY_FUNCTION_0];
+ } else if (key >= VTERM_KEY_KP_0) {
+ if ((key - VTERM_KEY_KP_0) >= sizeof(keycodes_kp)/sizeof(keycodes_kp[0])) {
+ return;
+ }
+ k = keycodes_kp[key - VTERM_KEY_KP_0];
+ }
+
+ switch (k.type) {
+ case KEYCODE_NONE:
+ break;
+
+ case KEYCODE_TAB:
+ // Shift-Tab is CSI Z but plain Tab is 0x09
+ if (mod == VTERM_MOD_SHIFT) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z");
+ } else if (mod & VTERM_MOD_SHIFT) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1);
+ } else {
+ goto case_LITERAL;
+ }
+ break;
+
+ case KEYCODE_ENTER:
+ // Enter is CRLF in newline mode, but just LF in linefeed
+ if (vt->state->mode.newline) {
+ vterm_push_output_sprintf(vt, "\r\n");
+ } else {
+ goto case_LITERAL;
+ }
+ break;
+
+ case KEYCODE_LITERAL:
+ case_LITERAL:
+ if (mod & (VTERM_MOD_SHIFT|VTERM_MOD_CTRL)) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1);
+ } else {
+ vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal);
+ }
+ break;
+
+ case KEYCODE_SS3:
+ case_SS3:
+ if (mod == 0) {
+ vterm_push_output_sprintf_ctrl(vt, C1_SS3, "%c", k.literal);
+ } else {
+ goto case_CSI;
+ }
+ break;
+
+ case KEYCODE_CSI:
+ case_CSI:
+ if (mod == 0) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%c", k.literal);
+ } else {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%d%c", mod + 1, k.literal);
+ }
+ break;
+
+ case KEYCODE_CSINUM:
+ if (mod == 0) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d%c", k.csinum, k.literal);
+ } else {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%d%c", k.csinum, mod + 1, k.literal);
+ }
+ break;
+
+ case KEYCODE_CSI_CURSOR:
+ if (vt->state->mode.cursor) {
+ goto case_SS3;
+ } else {
+ goto case_CSI;
+ }
+
+ case KEYCODE_KEYPAD:
+ if (vt->state->mode.keypad) {
+ k.literal = (char)k.csinum;
+ goto case_SS3;
+ } else {
+ goto case_LITERAL;
+ }
+ }
+}
+
+void vterm_keyboard_start_paste(VTerm *vt)
+{
+ if (vt->state->mode.bracketpaste) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "200~");
+ }
+}
+
+void vterm_keyboard_end_paste(VTerm *vt)
+{
+ if (vt->state->mode.bracketpaste) {
+ vterm_push_output_sprintf_ctrl(vt, C1_CSI, "201~");
+ }
+}
diff --git a/src/nvim/vterm/keyboard.h b/src/nvim/vterm/keyboard.h
new file mode 100644
index 0000000000..af5f4a3ed1
--- /dev/null
+++ b/src/nvim/vterm/keyboard.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stddef.h>
+
+#include "nvim/vterm/vterm_defs.h"
+#include "nvim/vterm/vterm_keycodes_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/keyboard.h.generated.h"
+#endif
diff --git a/src/nvim/vterm/mouse.c b/src/nvim/vterm/mouse.c
new file mode 100644
index 0000000000..9b8be4b60a
--- /dev/null
+++ b/src/nvim/vterm/mouse.c
@@ -0,0 +1,113 @@
+#include "nvim/tui/termkey/termkey.h"
+#include "nvim/vterm/mouse.h"
+#include "nvim/vterm/vterm.h"
+#include "nvim/vterm/vterm_internal_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/mouse.c.generated.h"
+#endif
+
+static void output_mouse(VTermState *state, int code, int pressed, int modifiers, int col, int row)
+{
+ modifiers <<= 2;
+
+ switch (state->mouse_protocol) {
+ case MOUSE_X10:
+ if (col + 0x21 > 0xff) {
+ col = 0xff - 0x21;
+ }
+ if (row + 0x21 > 0xff) {
+ row = 0xff - 0x21;
+ }
+
+ if (!pressed) {
+ code = 3;
+ }
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%c%c%c",
+ (code | modifiers) + 0x20, col + 0x21, row + 0x21);
+ break;
+
+ case MOUSE_UTF8: {
+ char utf8[18];
+ size_t len = 0;
+
+ if (!pressed) {
+ code = 3;
+ }
+
+ len += (size_t)fill_utf8((code | modifiers) + 0x20, utf8 + len);
+ len += (size_t)fill_utf8(col + 0x21, utf8 + len);
+ len += (size_t)fill_utf8(row + 0x21, utf8 + len);
+ utf8[len] = 0;
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "M%s", utf8);
+ }
+ break;
+
+ case MOUSE_SGR:
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "<%d;%d;%d%c",
+ code | modifiers, col + 1, row + 1, pressed ? 'M' : 'm');
+ break;
+
+ case MOUSE_RXVT:
+ if (!pressed) {
+ code = 3;
+ }
+
+ vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%d;%d;%dM",
+ code | modifiers, col + 1, row + 1);
+ break;
+ }
+}
+
+void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ if (col == state->mouse_col && row == state->mouse_row) {
+ return;
+ }
+
+ state->mouse_col = col;
+ state->mouse_row = row;
+
+ if ((state->mouse_flags & MOUSE_WANT_DRAG && state->mouse_buttons)
+ || (state->mouse_flags & MOUSE_WANT_MOVE)) {
+ int button = state->mouse_buttons & 0x01 ? 1
+ : state->mouse_buttons & 0x02 ? 2
+ : state->mouse_buttons &
+ 0x04 ? 3 : 4;
+ output_mouse(state, button - 1 + 0x20, 1, (int)mod, col, row);
+ }
+}
+
+void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod)
+{
+ VTermState *state = vt->state;
+
+ int old_buttons = state->mouse_buttons;
+
+ if (button > 0 && button <= 3) {
+ if (pressed) {
+ state->mouse_buttons |= (1 << (button - 1));
+ } else {
+ state->mouse_buttons &= ~(1 << (button - 1));
+ }
+ }
+
+ // Most of the time we don't get button releases from 4/5
+ if (state->mouse_buttons == old_buttons && button < 4) {
+ return;
+ }
+
+ if (!state->mouse_flags) {
+ return;
+ }
+
+ if (button < 4) {
+ output_mouse(state, button - 1, pressed, (int)mod, state->mouse_col, state->mouse_row);
+ } else if (button < 8) {
+ output_mouse(state, button - 4 + 0x40, pressed, (int)mod, state->mouse_col, state->mouse_row);
+ }
+}
diff --git a/src/nvim/vterm/mouse.h b/src/nvim/vterm/mouse.h
new file mode 100644
index 0000000000..477f4028a2
--- /dev/null
+++ b/src/nvim/vterm/mouse.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stddef.h>
+
+#include "nvim/vterm/vterm_defs.h"
+#include "nvim/vterm/vterm_keycodes_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "vterm/mouse.h.generated.h"
+#endif
diff --git a/src/nvim/vterm/parser.c b/src/nvim/vterm/parser.c
new file mode 100644
index 0000000000..79d348f2c1
--- /dev/null
+++ b/src/nvim/vterm/parser.c
@@ -0,0 +1,411 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/vterm/parser.h"
+#include "nvim/vterm/vterm.h"
+#include "nvim/vterm