diff options
| author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
|---|---|---|
| committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
| commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
| tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /src/nvim/vterm | |
| parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
| parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
| download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip | |
Diffstat (limited to 'src/nvim/vterm')
| -rw-r--r-- | src/nvim/vterm/LICENSE | 23 | ||||
| -rw-r--r-- | src/nvim/vterm/README.md | 1 | ||||
| -rw-r--r-- | src/nvim/vterm/encoding.c | 271 | ||||
| -rw-r--r-- | src/nvim/vterm/encoding.h | 10 | ||||
| -rw-r--r-- | src/nvim/vterm/keyboard.c | 318 | ||||
| -rw-r--r-- | src/nvim/vterm/keyboard.h | 10 | ||||
| -rw-r--r-- | src/nvim/vterm/mouse.c | 124 | ||||
| -rw-r--r-- | src/nvim/vterm/mouse.h | 10 | ||||
| -rw-r--r-- | src/nvim/vterm/parser.c | 411 | ||||
| -rw-r--r-- | src/nvim/vterm/parser.h | 9 | ||||
| -rw-r--r-- | src/nvim/vterm/pen.c | 644 | ||||
| -rw-r--r-- | src/nvim/vterm/pen.h | 9 | ||||
| -rw-r--r-- | src/nvim/vterm/screen.c | 1115 | ||||
| -rw-r--r-- | src/nvim/vterm/screen.h | 9 | ||||
| -rw-r--r-- | src/nvim/vterm/state.c | 2467 | ||||
| -rw-r--r-- | src/nvim/vterm/state.h | 7 | ||||
| -rw-r--r-- | src/nvim/vterm/vterm.c | 335 | ||||
| -rw-r--r-- | src/nvim/vterm/vterm.h | 161 | ||||
| -rw-r--r-- | src/nvim/vterm/vterm_defs.h | 322 | ||||
| -rw-r--r-- | src/nvim/vterm/vterm_internal_defs.h | 292 | ||||
| -rw-r--r-- | src/nvim/vterm/vterm_keycodes_defs.h | 58 |
21 files changed, 6606 insertions, 0 deletions
diff --git a/src/nvim/vterm/LICENSE b/src/nvim/vterm/LICENSE new file mode 100644 index 0000000000..0d051634b2 --- /dev/null +++ b/src/nvim/vterm/LICENSE @@ -0,0 +1,23 @@ + + +The MIT License + +Copyright (c) 2008 Paul Evans <leonerd@leonerd.org.uk> + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. 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..f9061e8e50 --- /dev/null +++ b/src/nvim/vterm/encoding.c @@ -0,0 +1,271 @@ +#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; + } + } +} + +// https://en.wikipedia.org/wiki/DEC_Special_Graphics +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 struct { + VTermEncodingType type; + char designation; + VTermEncoding *enc; +} +encodings[] = { + { ENC_UTF8, 'u', &encoding_utf8 }, + { ENC_SINGLE_94, '0', (VTermEncoding *)&encoding_DECdrawing }, + { 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..dd088ac40e --- /dev/null +++ b/src/nvim/vterm/keyboard.c @@ -0,0 +1,318 @@ +#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 + +static VTermKeyEncodingFlags vterm_state_get_key_encoding_flags(const VTermState *state) +{ + int screen = state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY; + const struct VTermKeyEncodingStack *stack = &state->key_encoding_stacks[screen]; + assert(stack->size > 0); + return stack->items[stack->size - 1]; +} + +void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod) +{ + bool passthru = false; + if (c == ' ') { + // Space is passed through only when there are no modifiers (including shift) + passthru = mod == VTERM_MOD_NONE; + } else { + // Otherwise pass through when there are no modifiers (ignoring shift) + passthru = (mod & (unsigned)~VTERM_MOD_SHIFT) == 0; + } + + if (passthru) { + char str[6]; + int seqlen = fill_utf8((int)c, str); + vterm_push_output_bytes(vt, str, (size_t)seqlen); + return; + } + + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + if (flags.disambiguate) { + // Always use unshifted codepoint + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; + mod |= VTERM_MOD_SHIFT; + } + + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", c, mod + 1); + return; + } + + if (mod & VTERM_MOD_CTRL) { + // Handle special cases. These are taken from kitty, but seem mostly + // consistent across terminals. + switch (c) { + case '2': + case ' ': + // Ctrl+2 is NUL to match Ctrl+@ (which is Shift+2 on US keyboards) + // Ctrl+Space is also NUL for some reason + c = 0x00; + break; + case '3': + case '4': + case '5': + case '6': + case '7': + // Ctrl+3 through Ctrl+7 are sequential starting from 0x1b. Importantly, + // this means that Ctrl+6 emits 0x1e (the same as Ctrl+^ on US keyboards) + c = 0x1b + c - '3'; + break; + case '8': + // Ctrl+8 is DEL + c = 0x7f; + break; + case '/': + // Ctrl+/ is equivalent to Ctrl+_ for historic reasons + c = 0x1f; + break; + default: + if (c >= '@' && c <= 0x7f) { + c &= 0x1f; + } + break; + } + } + + vterm_push_output_sprintf(vt, "%s%c", mod & VTERM_MOD_ALT ? ESC_S : "", c); +} + +typedef struct { + enum { + KEYCODE_NONE, + KEYCODE_LITERAL, + KEYCODE_TAB, + KEYCODE_ENTER, + KEYCODE_SS3, + KEYCODE_CSI, + KEYCODE_CSI_CURSOR, + KEYCODE_CSINUM, + KEYCODE_KEYPAD, + } type; + int 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 +}; + +static keycodes_s keycodes_kp_csiu[] = { + { KEYCODE_KEYPAD, 57399, 'p' }, // KP_0 + { KEYCODE_KEYPAD, 57400, 'q' }, // KP_1 + { KEYCODE_KEYPAD, 57401, 'r' }, // KP_2 + { KEYCODE_KEYPAD, 57402, 's' }, // KP_3 + { KEYCODE_KEYPAD, 57403, 't' }, // KP_4 + { KEYCODE_KEYPAD, 57404, 'u' }, // KP_5 + { KEYCODE_KEYPAD, 57405, 'v' }, // KP_6 + { KEYCODE_KEYPAD, 57406, 'w' }, // KP_7 + { KEYCODE_KEYPAD, 57407, 'x' }, // KP_8 + { KEYCODE_KEYPAD, 57408, 'y' }, // KP_9 + { KEYCODE_KEYPAD, 57411, 'j' }, // KP_MULT + { KEYCODE_KEYPAD, 57413, 'k' }, // KP_PLUS + { KEYCODE_KEYPAD, 57416, 'l' }, // KP_COMMA + { KEYCODE_KEYPAD, 57412, 'm' }, // KP_MINUS + { KEYCODE_KEYPAD, 57409, 'n' }, // KP_PERIOD + { KEYCODE_KEYPAD, 57410, 'o' }, // KP_DIVIDE + { KEYCODE_KEYPAD, 57414, 'M' }, // KP_ENTER + { KEYCODE_KEYPAD, 57415, 'X' }, // KP_EQUAL +}; + +void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod) +{ + if (key == VTERM_KEY_NONE) { + return; + } + + VTermKeyEncodingFlags flags = vterm_state_get_key_encoding_flags(vt->state); + + keycodes_s k; + if (key < VTERM_KEY_FUNCTION_0) { + if (key >= sizeof(keycodes)/sizeof(keycodes[0])) { + 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; + } + + if (flags.disambiguate) { + k = keycodes_kp_csiu[key - VTERM_KEY_KP_0]; + } else { + 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 (flags.disambiguate) { + goto case_LITERAL; + } else if (mod == VTERM_MOD_SHIFT) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "Z"); + } else if (mod & VTERM_MOD_SHIFT) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "1;%dZ", mod + 1); + } 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 (flags.disambiguate) { + switch (key) { + case VTERM_KEY_TAB: + case VTERM_KEY_ENTER: + case VTERM_KEY_BACKSPACE: + // If there are no mods then leave these as-is + flags.disambiguate = mod != VTERM_MOD_NONE; + break; + default: + break; + } + } + + if (flags.disambiguate) { + vterm_push_output_sprintf_ctrl(vt, C1_CSI, "%d;%du", k.literal, mod + 1); + } else { + vterm_push_output_sprintf(vt, mod & VTERM_MOD_ALT ? ESC_S "%c" : "%c", k.literal); + } + 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 = 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..2f3b1d9e2f --- /dev/null +++ b/src/nvim/vterm/mouse.c @@ -0,0 +1,124 @@ +#include "nvim/math.h" +#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; + } + + if (code & 0x80) { + break; + } + 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)) { + if (state->mouse_buttons) { + int button = xctz((uint64_t)state->mouse_buttons) + 1; + if (button < 4) { + output_mouse(state, button - 1 + 0x20, 1, (int)mod, col, row); + } else if (button >= 8 && button < 12) { + output_mouse(state, button - 8 + 0x80 + 0x20, 1, (int)mod, col, row); + } + } else { + output_mouse(state, 3 + 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) || (button >= 8 && button <= 11)) { + 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/6/7 + if (state->mouse_buttons == old_buttons && (button < 4 || button > 7)) { + 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); + } else if (button < 12) { + output_mouse(state, button - 8 + 0x80, 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/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/parser.c.generated.h" +#endif + +#undef DEBUG_PARSER + +static bool is_intermed(uint8_t c) +{ + return c >= 0x20 && c <= 0x2f; +} + +static void do_control(VTerm *vt, uint8_t control) +{ + if (vt->parser.callbacks && vt->parser.callbacks->control) { + if ((*vt->parser.callbacks->control)(control, vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled control 0x%02x\n", control); +} + +static void do_csi(VTerm *vt, char command) +{ +#ifdef DEBUG_PARSER + printf("Parsed CSI args as:\n", arglen, args); + printf(" leader: %s\n", vt->parser.v.csi.leader); + for (int argi = 0; argi < vt->parser.v.csi.argi; argi++) { + printf(" %lu", CSI_ARG(vt->parser.v.csi.args[argi])); + if (!CSI_ARG_HAS_MORE(vt->parser.v.csi.args[argi])) { + printf("\n"); + } + printf(" intermed: %s\n", vt->parser.intermed); + } +#endif + + if (vt->parser.callbacks && vt->parser.callbacks->csi) { + if ((*vt->parser.callbacks->csi)(vt->parser.v.csi.leaderlen ? vt->parser.v.csi.leader : NULL, + vt->parser.v.csi.args, + vt->parser.v.csi.argi, + vt->parser.intermedlen ? vt->parser.intermed : NULL, + command, + vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled CSI %c\n", command); +} + +static void do_escape(VTerm *vt, char command) +{ + char seq[INTERMED_MAX + 1]; + + size_t len = (size_t)vt->parser.intermedlen; + strncpy(seq, vt->parser.intermed, len); // NOLINT(runtime/printf) + seq[len++] = command; + seq[len] = 0; + + if (vt->parser.callbacks && vt->parser.callbacks->escape) { + if ((*vt->parser.callbacks->escape)(seq, len, vt->parser.cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled escape ESC 0x%02x\n", command); +} + +static void string_fragment(VTerm *vt, const char *str, size_t len, bool final) +{ + VTermStringFragment frag = { + .str = str, + .len = len, + .initial = vt->parser.string_initial, + .final = final, + }; + + switch (vt->parser.state) { + case OSC: + if (vt->parser.callbacks && vt->parser.callbacks->osc) { + (*vt->parser.callbacks->osc)(vt->parser.v.osc.command, frag, vt->parser.cbdata); + } + break; + + case DCS_VTERM: + if (vt->parser.callbacks && vt->parser.callbacks->dcs) { + (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, (size_t)vt->parser.v.dcs.commandlen, + frag, + vt->parser.cbdata); + } + break; + + case APC: + if (vt->parser.callbacks && vt->parser.callbacks->apc) { + (*vt->parser.callbacks->apc)(frag, vt->parser.cbdata); + } + break; + + case PM: + if (vt->parser.callbacks && vt->parser.callbacks->pm) { + (*vt->parser.callbacks->pm)(frag, vt->parser.cbdata); + } + break; + + case SOS: + if (vt->parser.callbacks && vt->parser.callbacks->sos) { + (*vt->parser.callbacks->sos)(frag, vt->parser.cbdata); + } + break; + + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + break; + } + + vt->parser.string_initial = false; +} + +size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len) +{ + size_t pos = 0; + const char *string_start; + + switch (vt->parser.state) { + case NORMAL: + case CSI_LEADER: + case CSI_ARGS: + case CSI_INTERMED: + case OSC_COMMAND: + case DCS_COMMAND: + string_start = NULL; + break; + case OSC: + case DCS_VTERM: + case APC: + case PM: + case SOS: + string_start = bytes; + break; |