diff options
author | dundargoc <gocdundar@gmail.com> | 2025-01-03 15:40:46 +0100 |
---|---|---|
committer | dundargoc <33953936+dundargoc@users.noreply.github.com> | 2025-01-07 12:35:24 +0100 |
commit | d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8 (patch) | |
tree | 3dd3eb1954d36db51a1eab82ea890d18cf085189 | |
parent | 4d9405991963a53ed31089ed456d9f4cfebc325d (diff) | |
download | rneovim-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.
42 files changed, 6569 insertions, 6595 deletions
diff --git a/MAINTAIN.md b/MAINTAIN.md index 1442faeff8..eb58664ed2 100644 --- a/MAINTAIN.md +++ b/MAINTAIN.md @@ -148,8 +148,6 @@ These dependencies are "vendored" (inlined), we must update the sources manually * `src/xdiff/`: [xdiff](https://github.com/git/git/tree/master/xdiff) * `src/cjson/`: [lua-cjson](https://github.com/openresty/lua-cjson) * `src/klib/`: [Klib](https://github.com/attractivechaos/klib) -* `src/vterm/`: [libvterm](https://www.leonerd.org.uk/code/libvterm/), - [mirror](https://github.com/neovim/libvterm) * `runtime/lua/vim/inspect.lua`: [inspect.lua](https://github.com/kikito/inspect.lua) * `src/nvim/tui/terminfo_defs.h`: terminfo definitions * Run `scripts/update_terminfo.sh` to update these definitions. 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/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; + } + +#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while (0) +#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) + +#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) + + for (; pos < len; pos++) { + uint8_t c = (uint8_t)bytes[pos]; + bool c1_allowed = !vt->mode.utf8; + + if (c == 0x00 || c == 0x7f) { // NUL, DEL + if (IS_STRING_STATE()) { + string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false); + string_start = bytes + pos + 1; + } + if (vt->parser.emit_nul) { + do_control(vt, c); + } + continue; + } + if (c == 0x18 || c == 0x1a) { // CAN, SUB + vt->parser.in_esc = false; + ENTER_NORMAL_STATE(); + if (vt->parser.emit_nul) { + do_control(vt, c); + } + continue; + } else if (c == 0x1b) { // ESC + vt->parser.intermedlen = 0; + if (!IS_STRING_STATE()) { + vt->parser.state = NORMAL; + } + vt->parser.in_esc = true; + continue; + } else if (c == 0x07 // BEL, can stand for ST in OSC or DCS state + && IS_STRING_STATE()) {} else if (c < 0x20) { // other C0 + if (vt->parser.state == SOS) { + continue; // All other C0s permitted in SOS + } + if (IS_STRING_STATE()) { + string_fragment(vt, string_start, (size_t)(bytes + pos - string_start), false); + } + do_control(vt, c); + if (IS_STRING_STATE()) { + string_start = bytes + pos + 1; + } + continue; + } + + size_t string_len = (size_t)(bytes + pos - string_start); + + if (vt->parser.in_esc) { + // Hoist an ESC letter into a C1 if we're not in a string mode + // Always accept ESC \ == ST even in string mode + if (!vt->parser.intermedlen + && c >= 0x40 && c < 0x60 + && ((!IS_STRING_STATE() || c == 0x5c))) { + c += 0x40; + c1_allowed = true; + if (string_len) { + assert(string_len > 0); + string_len -= 1; + } + vt->parser.in_esc = false; + } else { + string_start = NULL; + vt->parser.state = NORMAL; + } + } + + switch (vt->parser.state) { + case CSI_LEADER: + // Extract leader bytes 0x3c to 0x3f + if (c >= 0x3c && c <= 0x3f) { + if (vt->parser.v.csi.leaderlen < CSI_LEADER_MAX - 1) { + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = (char)c; + } + break; + } + + vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; + + vt->parser.v.csi.argi = 0; + vt->parser.v.csi.args[0] = CSI_ARG_MISSING; + vt->parser.state = CSI_ARGS; + + FALLTHROUGH; + case CSI_ARGS: + // Numerical value of argument + if (c >= '0' && c <= '9') { + if (vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) { + vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; + } + vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; + vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; + break; + } + if (c == ':') { + vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; + c = ';'; + } + if (c == ';') { + vt->parser.v.csi.argi++; + vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; + break; + } + + vt->parser.v.csi.argi++; + vt->parser.intermedlen = 0; + vt->parser.state = CSI_INTERMED; + FALLTHROUGH; + case CSI_INTERMED: + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) { + vt->parser.intermed[vt->parser.intermedlen++] = (char)c; + } + break; + } else if (c == 0x1b) { + // ESC in CSI cancels + } else if (c >= 0x40 && c <= 0x7e) { + vt->parser.intermed[vt->parser.intermedlen] = 0; + do_csi(vt, (char)c); + } + // else was invalid CSI + + ENTER_NORMAL_STATE(); + break; + + case OSC_COMMAND: + // Numerical value of command + if (c >= '0' && c <= '9') { + if (vt->parser.v.osc.command == -1) { + vt->parser.v.osc.command = 0; + } else { + vt->parser.v.osc.command *= 10; + } + vt->parser.v.osc.command += c - '0'; + break; + } + if (c == ';') { + vt->parser.state = OSC; + string_start = bytes + pos + 1; + break; + } + + string_start = bytes + pos; + string_len = 0; + vt->parser.state = OSC; + goto string_state; + + case DCS_COMMAND: + if (vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) { + vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = (char)c; + } + + if (c >= 0x40 && c <= 0x7e) { + string_start = bytes + pos + 1; + vt->parser.state = DCS_VTERM; + } + break; + +string_state: + case OSC: + case DCS_VTERM: + case APC: + case PM: + case SOS: + if (c == 0x07 || (c1_allowed && c == 0x9c)) { + string_fragment(vt, string_start, string_len, true); + ENTER_NORMAL_STATE(); + } + break; + + case NORMAL: + if (vt->parser.in_esc) { + if (is_intermed(c)) { + if (vt->parser.intermedlen < INTERMED_MAX - 1) { + vt->parser.intermed[vt->parser.intermedlen++] = (char)c; + } + } else if (c >= 0x30 && c < 0x7f) { + do_escape(vt, (char)c); + vt->parser.in_esc = 0; + ENTER_NORMAL_STATE(); + } else { + DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); + } + break; + } + if (c1_allowed && c >= 0x80 && c < 0xa0) { + switch (c) { + case 0x90: // DCS + vt->parser.string_initial = true; + vt->parser.v.dcs.commandlen = 0; + ENTER_STATE(DCS_COMMAND); + break; + case 0x98: // SOS + vt->parser.string_initial = true; + ENTER_STATE(SOS); + string_start = bytes + pos + 1; + break; + case 0x9b: // CSI + vt->parser.v.csi.leaderlen = 0; + ENTER_STATE(CSI_LEADER); + break; + case 0x9d: // OSC + vt->parser.v.osc.command = -1; + vt->parser.string_initial = true; + ENTER_STATE(OSC_COMMAND); + break; + case 0x9e: // PM + vt->parser.string_initial = true; + ENTER_STATE(PM); + string_start = bytes + pos + 1; + break; + case 0x9f: // APC + vt->parser.string_initial = true; + ENTER_STATE(APC); + string_start = bytes + pos + 1; + break; + default: + do_control(vt, c); + break; + } + } else { + size_t eaten = 0; + if (vt->parser.callbacks && vt->parser.callbacks->text) { + eaten = (size_t)(*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); + } + + if (!eaten) { + DEBUG_LOG("libvterm: Text callback did not consume any input\n"); + // force it to make progress + eaten = 1; + } + + pos += (eaten - 1); // we'll ++ it again in a moment + } + break; + } + } + + if (string_start) { + size_t string_len = (size_t)(bytes + pos - string_start); + if (string_len > 0) { + if (vt->parser.in_esc) { + string_len -= 1; + } + string_fragment(vt, string_start, string_len, false); + } + } + + return len; +} + +void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) +{ + vt->parser.callbacks = callbacks; + vt->parser.cbdata = user; +} diff --git a/src/nvim/vterm/parser.h b/src/nvim/vterm/parser.h new file mode 100644 index 0000000000..168be830c0 --- /dev/null +++ b/src/nvim/vterm/parser.h @@ -0,0 +1,9 @@ +#pragma once + +#include <stddef.h> + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/parser.h.generated.h" +#endif diff --git a/src/nvim/vterm/pen.c b/src/nvim/vterm/pen.c new file mode 100644 index 0000000000..e7f50078ae --- /dev/null +++ b/src/nvim/vterm/pen.c @@ -0,0 +1,644 @@ +#include <stdio.h> + +#include "nvim/vterm/pen.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/pen.c.generated.h" +#endif + +// Structure used to store RGB triples without the additional metadata stored in VTermColor. +typedef struct { + uint8_t red, green, blue; +} VTermRGB; + +static const VTermRGB ansi_colors[] = { + // R G B + { 0, 0, 0 }, // black + { 224, 0, 0 }, // red + { 0, 224, 0 }, // green + { 224, 224, 0 }, // yellow + { 0, 0, 224 }, // blue + { 224, 0, 224 }, // magenta + { 0, 224, 224 }, // cyan + { 224, 224, 224 }, // white == light grey + + // high intensity + { 128, 128, 128 }, // black + { 255, 64, 64 }, // red + { 64, 255, 64 }, // green + { 255, 255, 64 }, // yellow + { 64, 64, 255 }, // blue + { 255, 64, 255 }, // magenta + { 64, 255, 255 }, // cyan + { 255, 255, 255 }, // white for real +}; + +static uint8_t ramp6[] = { + 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, +}; + +static uint8_t ramp24[] = { + 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, + 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, +}; + +static void lookup_default_colour_ansi(long idx, VTermColor *col) +{ + if (idx >= 0 && idx < 16) { + vterm_color_rgb(col, + ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); + } +} + +static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) +{ + if (index >= 0 && index < 16) { + *col = state->colors[index]; + return true; + } + + return false; +} + +static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) +{ + if (index >= 0 && index < 16) { + // Normal 8 colours or high intensity - parse as palette 0 + return lookup_colour_ansi(state, index, col); + } else if (index >= 16 && index < 232) { + // 216-colour cube + index -= 16; + + vterm_color_rgb(col, ramp6[index/6/6 % 6], + ramp6[index/6 % 6], + ramp6[index % 6]); + + return true; + } else if (index >= 232 && index < 256) { + // 24 greyscales + index -= 232; + + vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); + + return true; + } + + return false; +} + +static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, + VTermColor *col) +{ + switch (palette) { + case 2: // RGB mode - 3 args contain colour values directly + if (argcount < 3) { + return argcount; + } + + vterm_color_rgb(col, (uint8_t)CSI_ARG(args[0]), (uint8_t)CSI_ARG(args[1]), + (uint8_t)CSI_ARG(args[2])); + + return 3; + + case 5: // XTerm 256-colour mode + if (!argcount || CSI_ARG_IS_MISSING(args[0])) { + return argcount ? 1 : 0; + } + + vterm_color_indexed(col, (uint8_t)args[0]); + + return argcount ? 1 : 0; + + default: + DEBUG_LOG("Unrecognised colour palette %d\n", palette); + return 0; + } +} + +// Some conveniences + +static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ +#ifdef DEBUG + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return; + } +#endif + if (state->callbacks && state->callbacks->setpenattr) { + (*state->callbacks->setpenattr)(attr, val, state->cbdata); + } +} + +static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) +{ + VTermValue val = { .boolean = boolean }; + setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); +} + +static void setpenattr_int(VTermState *state, VTermAttr attr, int number) +{ + VTermValue val = { .number = number }; + setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); +} + +static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) +{ + VTermValue val = { .color = color }; + setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); +} + +static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) +{ + VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; + + vterm_color_indexed(colp, (uint8_t)col); + + setpenattr_col(state, attr, *colp); +} + +void vterm_state_newpen(VTermState *state) +{ + // 90% grey so that pure white is brighter + vterm_color_rgb(&state->default_fg, 240, 240, 240); + vterm_color_rgb(&state->default_bg, 0, 0, 0); + vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); + + for (int col = 0; col < 16; col++) { + lookup_default_colour_ansi(col, &state->colors[col]); + } +} + +void vterm_state_resetpen(VTermState *state) +{ + state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + state->pen.underline = 0; setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + state->pen.font = 0; setpenattr_int(state, VTERM_ATTR_FONT, 0); + state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); + state->pen.baseline = 0; setpenattr_int(state, VTERM_ATTR_BASELINE, 0); + + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); + + state->pen.uri = 0; setpenattr_int(state, VTERM_ATTR_URI, 0); +} + +void vterm_state_savepen(VTermState *state, int save) +{ + if (save) { + state->saved.pen = state->pen; + } else { + state->pen = state->saved.pen; + + setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); + setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); + setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); + setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); + setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); + + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + + setpenattr_int(state, VTERM_ATTR_URI, state->pen.uri); + } +} + +void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, + const VTermColor *default_bg) +{ + if (default_fg) { + state->default_fg = *default_fg; + state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_FG; + } + + if (default_bg) { + state->default_bg = *default_bg; + state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) + | VTERM_COLOR_DEFAULT_BG; + } +} + +void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) +{ + if (index >= 0 && index < 16) { + state->colors[index] = *col; + } +} + +/// Makes sure that the given color `col` is indeed an RGB colour. After this +/// function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other +/// flags stored in `col->type` will have been reset. +/// +/// @param state is the VTermState instance from which the colour palette should +/// be extracted. +/// @param col is a pointer at the VTermColor instance that should be converted +/// to an RGB colour. +void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) +{ + if (VTERM_COLOR_IS_INDEXED(col)) { // Convert indexed colors to RGB + lookup_colour_palette(state, col->indexed.idx, col); + } + col->type &= VTERM_COLOR_TYPE_MASK; // Reset any metadata but the type +} + +void vterm_state_setpen(VTermState *state, const long args[], int argcount) +{ + // SGR - ECMA-48 8.3.117 + + int argi = 0; + int value; + + while (argi < argcount) { + // This logic is easier to do 'done' backwards; set it true, and make it + // false again in the 'default' case + int done = 1; + + long arg; + switch (arg = CSI_ARG(args[argi])) { + case CSI_ARG_MISSING: + case 0: // Reset + vterm_state_resetpen(state); + break; + + case 1: { // Bold on + const VTermColor *fg = &state->pen.fg; + state->pen.bold = 1; + setpenattr_bool(state, VTERM_ATTR_BOLD, 1); + if (!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 + && state->bold_is_highbright) { + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); + } + break; + } + + case 3: // Italic on + state->pen.italic = 1; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); + break; + + case 4: // Underline + state->pen.underline = VTERM_UNDERLINE_SINGLE; + if (CSI_ARG_HAS_MORE(args[argi])) { + argi++; + switch (CSI_ARG(args[argi])) { + case 0: + state->pen.underline = 0; + break; + case 1: + state->pen.underline = VTERM_UNDERLINE_SINGLE; + break; + case 2: + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + break; + case 3: + state->pen.underline = VTERM_UNDERLINE_CURLY; + break; + } + } + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 5: // Blink + state->pen.blink = 1; + setpenattr_bool(state, VTERM_ATTR_BLINK, 1); + break; + + case 7: // Reverse on + state->pen.reverse = 1; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); + break; + + case 8: // Conceal on + state->pen.conceal = 1; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); + break; + + case 9: // Strikethrough on + state->pen.strike = 1; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); + break; + + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: // Select font + state->pen.font = CSI_ARG(args[argi]) - 10; + setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); + break; + + case 21: // Underline double + state->pen.underline = VTERM_UNDERLINE_DOUBLE; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); + break; + + case 22: // Bold off + state->pen.bold = 0; + setpenattr_bool(state, VTERM_ATTR_BOLD, 0); + break; + + case 23: // Italic and Gothic (currently unsupported) off + state->pen.italic = 0; + setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); + break; + + case 24: // Underline off + state->pen.underline = 0; + setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); + break; + + case 25: // Blink off + state->pen.blink = 0; + setpenattr_bool(state, VTERM_ATTR_BLINK, 0); + break; + + case 27: // Reverse off + state->pen.reverse = 0; + setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); + break; + + case 28: // Conceal off (Reveal) + state->pen.conceal = 0; + setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); + break; + + case 29: // Strikethrough off + state->pen.strike = 0; + setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); + break; + + case 30: + case 31: + case 32: + case 33: + case 34: + case 35: + case 36: + case 37: // Foreground colour palette + value = CSI_ARG(args[argi]) - 30; + if (state->pen.bold && state->bold_is_highbright) { + value += 8; + } + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 38: // Foreground colour alternative palette + if (argcount - argi < 1) { + return; + } + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, + argcount - argi - 2, &state->pen.fg); + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 39: // Foreground colour default + state->pen.fg = state->default_fg; + setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); + break; + + case 40: + case 41: + case 42: + case 43: + case 44: + case 45: + case 46: + case 47: // Background colour palette + value = CSI_ARG(args[argi]) - 40; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + case 48: // Background colour alternative palette + if (argcount - argi < 1) { + return; + } + argi += 1 + lookup_colour(state, CSI_ARG(args[argi + 1]), args + argi + 2, + argcount - argi - 2, &state->pen.bg); + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 49: // Default background + state->pen.bg = state->default_bg; + setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); + break; + + case 73: // Superscript + case 74: // Subscript + case 75: // Superscript/subscript off + state->pen.small = (arg != 75); + state->pen.baseline = + (arg == 73) ? VTERM_BASELINE_RAISE + : (arg == 74) ? VTERM_BASELINE_LOWER + : VTERM_BASELINE_NORMAL; + setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); + setpenattr_int(state, VTERM_ATTR_BASELINE, state->pen.baseline); + break; + + case 90: + case 91: + case 92: + case 93: + case 94: + case 95: + case 96: + case 97: // Foreground colour high-intensity palette + value = CSI_ARG(args[argi]) - 90 + 8; + set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); + break; + + case 100: + case 101: + case 102: + case 103: + case 104: + case 105: + case 106: + case 107: // Background colour high-intensity palette + value = CSI_ARG(args[argi]) - 100 + 8; + set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); + break; + + default: + done = 0; + break; + } + + if (!done) { + DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); + } + + while (CSI_ARG_HAS_MORE(args[argi++])) {} + } +} + +static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) +{ + // Do nothing if the given color is the default color + if ((fg && VTERM_COLOR_IS_DEFAULT_FG(col)) + || (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { + return argi; + } + + // Decide whether to send an indexed color or an RGB color + if (VTERM_COLOR_IS_INDEXED(col)) { + const uint8_t idx = col->indexed.idx; + if (idx < 8) { + args[argi++] = (idx + (fg ? 30 : 40)); + } else if (idx < 16) { + args[argi++] = (idx - 8 + (fg ? 90 : 100)); + } else { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 5; + args[argi++] = idx; + } + } else if (VTERM_COLOR_IS_RGB(col)) { + args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); + args[argi++] = CSI_ARG_FLAG_MORE | 2; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; + args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; + args[argi++] = col->rgb.blue; + } + return argi; +} + +int vterm_state_getpen(VTermState *state, long args[], int argcount) +{ + int argi = 0; + + if (state->pen.bold) { + args[argi++] = 1; + } + + if (state->pen.italic) { + args[argi++] = 3; + } + + if (state->pen.underline == VTERM_UNDERLINE_SINGLE) { + args[argi++] = 4; + } + if (state->pen.underline == VTERM_UNDERLINE_CURLY) { + args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; + } + + if (state->pen.blink) { + args[argi++] = 5; + } + + if (state->pen.reverse) { + args[argi++] = 7; + } + + if (state->pen.conceal) { + args[argi++] = 8; + } + + if (state->pen.strike) { + args[argi++] = 9; + } + + if (state->pen.font) { + args[argi++] = 10 + state->pen.font; + } + + if (state->pen.underline == VTERM_UNDERLINE_DOUBLE) { + args[argi++] = 21; + } + + argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); + + argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); + + if (state->pen.small) { + if (state->pen.baseline == VTERM_BASELINE_RAISE) { + args[argi++] = 73; + } else if (state->pen.baseline == VTERM_BASELINE_LOWER) { + args[argi++] = 74; + } + } + + return argi; +} + +int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) +{ + if (!val) { + return 0; + } + + if (type != vterm_get_attr_type(attr)) { + DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", + attr, vterm_get_attr_type(attr), type); + return 0; + } + + switch (attr) { + case VTERM_ATTR_BOLD: + state->pen.bold = (unsigned)val->boolean; + break; + case VTERM_ATTR_UNDERLINE: + state->pen.underline = (unsigned)val->number; + break; + case VTERM_ATTR_ITALIC: + state->pen.italic = (unsigned)val->boolean; + break; + case VTERM_ATTR_BLINK: + state->pen.blink = (unsigned)val->boolean; + break; + case VTERM_ATTR_REVERSE: + state->pen.reverse = (unsigned)val->boolean; + break; + case VTERM_ATTR_CONCEAL: + state->pen.conceal = (unsigned)val->boolean; + break; + case VTERM_ATTR_STRIKE: + state->pen.strike = (unsigned)val->boolean; + break; + case VTERM_ATTR_FONT: + state->pen.font = (unsigned)val->number; + break; + case VTERM_ATTR_FOREGROUND: + state->pen.fg = val->color; + break; + case VTERM_ATTR_BACKGROUND: + state->pen.bg = val->color; + break; + case VTERM_ATTR_SMALL: + state->pen.small = (unsigned)val->boolean; + break; + case VTERM_ATTR_BASELINE: + state->pen.baseline = (unsigned)val->number; + break; + case VTERM_ATTR_URI: + state->pen.uri = val->number; + break; + default: + return 0; + } + + if (state->callbacks && state->callbacks->setpenattr) { + (*state->callbacks->setpenattr)(attr, val, state->cbdata); + } + + return 1; +} diff --git a/src/nvim/vterm/pen.h b/src/nvim/vterm/pen.h new file mode 100644 index 0000000000..c5f5217420 --- /dev/null +++ b/src/nvim/vterm/pen.h @@ -0,0 +1,9 @@ +#pragma once + +#include <stddef.h> + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/pen.h.generated.h" +#endif diff --git a/src/nvim/vterm/screen.c b/src/nvim/vterm/screen.c new file mode 100644 index 0000000000..f24e47e543 --- /dev/null +++ b/src/nvim/vterm/screen.c @@ -0,0 +1,1103 @@ +#include <stdio.h> +#include <string.h> + +#include "nvim/grid.h" +#include "nvim/mbyte.h" +#include "nvim/tui/termkey/termkey.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_defs.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/screen.c.generated.h" +#endif + +#define UNICODE_SPACE 0x20 +#define UNICODE_LINEFEED 0x0a + +#undef DEBUG_REFLOW + +static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) +{ + cell->schar = 0; + cell->pen = screen->pen; +} + +ScreenCell *getcell(const VTermScreen *screen, int row, int col) +{ + if (row < 0 || row >= screen->rows) { + return NULL; + } + if (col < 0 || col >= screen->cols) { + return NULL; + } + return screen->buffer + (screen->cols * row) + col; +} + +static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) +{ + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, + sizeof(ScreenCell) * (size_t)rows * (size_t)cols); + + for (int row = 0; row < rows; row++) { + for (int col = 0; col < cols; col++) { + clearcell(screen, &new_buffer[row * cols + col]); + } + } + + return new_buffer; +} + +static void damagerect(VTermScreen *screen, VTermRect rect) +{ + VTermRect emit; + + switch (screen->damage_merge) { + case VTERM_DAMAGE_CELL: + // Always emit damage event + emit = rect; + break; + + case VTERM_DAMAGE_ROW: + // Emit damage longer than one row. Try to merge with existing damage in the same row + if (rect.end_row > rect.start_row + 1) { + // Bigger than 1 line - flush existing, emit this + vterm_screen_flush_damage(screen); + emit = rect; + } else if (screen->damaged.start_row == -1) { + // None stored yet + screen->damaged = rect; + return; + } else if (rect.start_row == screen->damaged.start_row) { + // Merge with the stored line + if (screen->damaged.start_col > rect.start_col) { + screen->damaged.start_col = rect.start_col; + } + if (screen->damaged.end_col < rect.end_col) { + screen->damaged.end_col = rect.end_col; + } + return; + } else { + // Emit the currently stored line, store a new one + emit = screen->damaged; + screen->damaged = rect; + } + break; + + case VTERM_DAMAGE_SCREEN: + case VTERM_DAMAGE_SCROLL: + // Never emit damage event + if (screen->damaged.start_row == -1) { + screen->damaged = rect; + } else { + rect_expand(&screen->damaged, &rect); + } + return; + + default: + DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); + return; + } + + if (screen->callbacks && screen->callbacks->damage) { + (*screen->callbacks->damage)(emit, screen->cbdata); + } +} + +static void damagescreen(VTermScreen *screen) +{ + VTermRect rect = { + .start_row = 0, + .end_row = screen->rows, + .start_col = 0, + .end_col = screen->cols, + }; + + damagerect(screen, rect); +} + +static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) +{ + VTermScreen *screen = user; + ScreenCell *cell = getcell(screen, pos.row, pos.col); + + if (!cell) { + return 0; + } + + cell->schar = info->schar; + if (info->schar != 0) { + cell->pen = screen->pen; + } + + for (int col = 1; col < info->width; col++) { + getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1; + } + + VTermRect rect = { + .start_row = pos.row, + .end_row = pos.row + 1, + .start_col = pos.col, + .end_col = pos.col + info->width, + }; + + cell->pen.protected_cell = info->protected_cell; + cell->pen.dwl = info->dwl; + cell->pen.dhl = info->dhl; + + damagerect(screen, rect); + + return 1; +} + +static void sb_pushline_from_row(VTermScreen *screen, int row) +{ + VTermPos pos = { .row = row }; + for (pos.col = 0; pos.col < screen->cols; pos.col++) { + vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); + } + + (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); +} + +static int moverect_internal(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_pushline + && dest.start_row == 0 && dest.start_col == 0 // starts top-left corner + && dest.end_col == screen->cols // full width + && screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen + for (int row = 0; row < src.start_row; row++) { + sb_pushline_from_row(screen, row); + } + } + + int cols = src.end_col - src.start_col; + int downward = src.start_row - dest.start_row; + + int init_row, test_row, inc_row; + if (downward < 0) { + init_row = dest.end_row - 1; + test_row = dest.start_row - 1; + inc_row = -1; + } else { + init_row = dest.start_row; + test_row = dest.end_row; + inc_row = +1; + } + + for (int row = init_row; row != test_row; row += inc_row) { + memmove(getcell(screen, row, dest.start_col), + getcell(screen, row + downward, src.start_col), + (size_t)cols * sizeof(ScreenCell)); + } + + return 1; +} + +static int moverect_user(VTermRect dest, VTermRect src, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->moverect) { + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + // Avoid an infinite loop + vterm_screen_flush_damage(screen); + } + + if ((*screen->callbacks->moverect)(dest, src, screen->cbdata)) { + return 1; + } + } + + damagerect(screen, dest); + + return 1; +} + +static int erase_internal(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + for (int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { + const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); + + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (selective && cell->pen.protected_cell) { + continue; + } + + cell->schar = 0; + cell->pen = (ScreenPen){ + // Only copy .fg and .bg; leave things like rv in reset state + .fg = screen->pen.fg, + .bg = screen->pen.bg, + }; + cell->pen.dwl = info->doublewidth; + cell->pen.dhl = info->doubleheight; + } + } + + return 1; +} + +static int erase_user(VTermRect rect, int selective, void *user) +{ + VTermScreen *screen = user; + + damagerect(screen, rect); + + return 1; +} + +static int erase(VTermRect rect, int selective, void *user) +{ + erase_internal(rect, selective, user); + return erase_user(rect, 0, user); +} + +static int scrollrect(VTermRect rect, int downward, int rightward, void *user) +{ + VTermScreen *screen = user; + + if (screen->damage_merge != VTERM_DAMAGE_SCROLL) { + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + vterm_screen_flush_damage(screen); + + vterm_scroll_rect(rect, downward, rightward, + moverect_user, erase_user, screen); + + return 1; + } + + if (screen->damaged.start_row != -1 + && !rect_intersects(&rect, &screen->damaged)) { + vterm_screen_flush_damage(screen); + } + + if (screen->pending_scrollrect.start_row == -1) { + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } else if (rect_equal(&screen->pending_scrollrect, &rect) + && ((screen->pending_scroll_downward == 0 && downward == 0) + || (screen->pending_scroll_rightward == 0 && rightward == 0))) { + screen->pending_scroll_downward += downward; + screen->pending_scroll_rightward += rightward; + } else { + vterm_screen_flush_damage(screen); + + screen->pending_scrollrect = rect; + screen->pending_scroll_downward = downward; + screen->pending_scroll_rightward = rightward; + } + + vterm_scroll_rect(rect, downward, rightward, + moverect_internal, erase_internal, screen); + + if (screen->damaged.start_row == -1) { + return 1; + } + + if (rect_contains(&rect, &screen->damaged)) { + // Scroll region entirely contains the damage; just move it + vterm_rect_move(&screen->damaged, -downward, -rightward); + rect_clip(&screen->damaged, &rect); + } + // There are a number of possible cases here, but lets restrict this to only the common case where + // we might actually gain some performance by optimising it. Namely, a vertical scroll that neatly + // cuts the damage region in half. + else if (rect.start_col <= screen->damaged.start_col + && rect.end_col >= screen->damaged.end_col + && rightward == 0) { + if (screen->damaged.start_row >= rect.start_row + && screen->damaged.start_row < rect.end_row) { + screen->damaged.start_row -= downward; + if (screen->damaged.start_row < rect.start_row) { + screen->damaged.start_row = rect.start_row; + } + if (screen->damaged.start_row > rect.end_row) { + screen->damaged.start_row = rect.end_row; + } + } + if (screen->damaged.end_row >= rect.start_row + && screen->damaged.end_row < rect.end_row) { + screen->damaged.end_row -= downward; + if (screen->damaged.end_row < rect.start_row) { + screen->damaged.end_row = rect.start_row; + } + if (screen->damaged.end_row > rect.end_row) { + screen->damaged.end_row = rect.end_row; + } + } + } else { + DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", + ARGSrect(screen->damaged), ARGSrect(rect)); + } + + return 1; +} + +static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->movecursor) { + return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); + } + + return 0; +} + +static int setpenattr(VTermAttr attr, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch (attr) { + case VTERM_ATTR_BOLD: + screen->pen.bold = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_UNDERLINE: + screen->pen.underline = (unsigned)val->number; + return 1; + case VTERM_ATTR_ITALIC: + screen->pen.italic = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_BLINK: + screen->pen.blink = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_REVERSE: + screen->pen.reverse = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_CONCEAL: + screen->pen.conceal = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_STRIKE: + screen->pen.strike = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_FONT: + screen->pen.font = (unsigned)val->number; + return 1; + case VTERM_ATTR_FOREGROUND: + screen->pen.fg = val->color; + return 1; + case VTERM_ATTR_BACKGROUND: + screen->pen.bg = val->color; + return 1; + case VTERM_ATTR_SMALL: + screen->pen.small = (unsigned)val->boolean; + return 1; + case VTERM_ATTR_BASELINE: + screen->pen.baseline = (unsigned)val->number; + return 1; + case VTERM_ATTR_URI: + screen->pen.uri = val->number; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int settermprop(VTermProp prop, VTermValue *val, void *user) +{ + VTermScreen *screen = user; + + switch (prop) { + case VTERM_PROP_ALTSCREEN: + if (val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) { + return 0; + } + + screen->buffer = + val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + // only send a damage event on disable; because during enable there's an erase that sends a + // damage anyway + if (!val->boolean) { + damagescreen(screen); + } + break; + case VTERM_PROP_REVERSE: + screen->global_reverse = (unsigned)val->boolean; + damagescreen(screen); + break; + default: + ; // ignore + } + + if (screen->callbacks && screen->callbacks->settermprop) { + return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); + } + + return 1; +} + +static int bell(void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->bell) { + return (*screen->callbacks->bell)(screen->cbdata); + } + + return 0; +} + +/// How many cells are non-blank Returns the position of the first blank cell in the trailing blank +/// end +static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) +{ + int col = cols - 1; + while (col >= 0 && buffer[row * cols + col].schar == 0) { + col--; + } + return col + 1; +} + +static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, + VTermStateFields *statefields) +{ + int old_rows = screen->rows; + int old_cols = screen->cols; + + ScreenCell *old_buffer = screen->buffers[bufidx]; + VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; + + ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, + sizeof(ScreenCell) * (size_t)new_rows * + (size_t)new_cols); + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, + sizeof(new_lineinfo[0]) * (size_t)new_rows); + + int old_row = old_rows - 1; + int new_row = new_rows - 1; + + VTermPos old_cursor = statefields->pos; + VTermPos new_cursor = { -1, -1 }; + +#ifdef DEBUG_REFLOW + fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", + old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); +#endif + + // Keep track of the final row that is knonw to be blank, so we know what spare space we have for + // scrolling into + int final_blank_row = new_rows; + + while (old_row >= 0) { + int old_row_end = old_row; + // TODO(vterm): Stop if dwl or dhl + while (screen->reflow && old_lineinfo && old_row > 0 && old_lineinfo[old_row].continuation) { + old_row--; + } + int old_row_start = old_row; + + int width = 0; + for (int row = old_row_start; row <= old_row_end; row++) { + if (screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) { + width += old_cols; + } else { + width += line_popcount(old_buffer, row, old_rows, old_cols); + } + } + + if (final_blank_row == (new_row + 1) && width == 0) { + final_blank_row = new_row; + } + + int new_height = screen->reflow + ? width ? (width + new_cols - 1) / new_cols : 1 + : 1; + + int new_row_end = new_row; + int new_row_start = new_row - new_height + 1; + + old_row = old_row_start; + int old_col = 0; + + int spare_rows = new_rows - final_blank_row; + + if (new_row_start < 0 // we'd fall off the top + && spare_rows >= 0 // we actually have spare rows + && (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) { + // Attempt to scroll content down into the blank rows at the bottom to make it fit + int downwards = -new_row_start; + if (downwards > spare_rows) { + downwards = spare_rows; + } + int rowcount = new_rows - downwards; + +#ifdef DEBUG_REFLOW + fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); +#endif + + memmove(&new_buffer[downwards * new_cols], &new_buffer[0], + (size_t)rowcount * (size_t)new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[downwards], &new_lineinfo[0], + (size_t)rowcount * sizeof(new_lineinfo[0])); + + new_row += downwards; + new_row_start += downwards; + new_row_end += downwards; + + if (new_cursor.row >= 0) { + new_cursor.row += downwards; + } + + final_blank_row += downwards; + } + +#ifdef DEBUG_REFLOW + fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", + new_row_start, new_row_end, old_row_start, old_row_end, width); +#endif + + if (new_row_start < 0) { + if (old_row_start <= old_cursor.row && old_cursor.row <= old_row_end) { + new_cursor.row = 0; + new_cursor.col = old_cursor.col; + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + break; + } + + for (new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { + int count = width >= new_cols ? new_cols : width; + width -= count; + + int new_col = 0; + + while (count) { + // TODO(vterm): This could surely be done a lot faster by memcpy()'ing the entire range + new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; + + if (old_cursor.row == old_row && old_cursor.col == old_col) { + new_cursor.row = new_row, new_cursor.col = new_col; + } + + old_col++; + if (old_col == old_cols) { + old_row++; + + if (!screen->reflow) { + new_col++; + break; + } + old_col = 0; + } + + new_col++; + count--; + } + + if (old_cursor.row == old_row && old_cursor.col >= old_col) { + new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + + while (new_col < new_cols) { + clearcell(screen, &new_buffer[new_row * new_cols + new_col]); + new_col++; + } + + new_lineinfo[new_row].continuation = (new_row > new_row_start); + } + + old_row = old_row_start - 1; + new_row = new_row_start - 1; + } + + if (old_cursor.row <= old_row) { + // cursor would have moved entirely off the top of the screen; lets just bring it within range + new_cursor.row = 0, new_cursor.col = old_cursor.col; + if (new_cursor.col >= new_cols) { + new_cursor.col = new_cols - 1; + } + } + + // We really expect the cursor position to be set by now + if (active && (new_cursor.row == -1 || new_cursor.col == -1)) { + fprintf(stderr, "screen_resize failed to update cursor position\n"); + abort(); + } + + if (old_row >= 0 && bufidx == BUFIDX_PRIMARY) { + // Push spare lines to scrollback buffer + if (screen->callbacks && screen->callbacks->sb_pushline) { + for (int row = 0; row <= old_row; row++) { + sb_pushline_from_row(screen, row); + } + } + if (active) { + statefields->pos.row -= (old_row + 1); + } + } + if (new_row >= 0 && bufidx == BUFIDX_PRIMARY + && screen->callbacks && screen->callbacks->sb_popline) { + // Try to backfill rows by popping scrollback buffer + while (new_row >= 0) { + if (!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) { + break; + } + + VTermPos pos = { .row = new_row }; + for (pos.col = 0; pos.col < old_cols && pos.col < new_cols; + pos.col += screen->sb_buffer[pos.col].width) { + VTermScreenCell *src = &screen->sb_buffer[pos.col]; + ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; + + dst->schar = src->schar; + + dst->pen.bold = src->attrs.bold; + dst->pen.underline = src->attrs.underline; + dst->pen.italic = src->attrs.italic; + dst->pen.blink = src->attrs.blink; + dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; + dst->pen.conceal = src->attrs.conceal; + dst->pen.strike = src->attrs.strike; + dst->pen.font = src->attrs.font; + dst->pen.small = src->attrs.small; + dst->pen.baseline = src->attrs.baseline; + + dst->pen.fg = src->fg; + dst->pen.bg = src->bg; + + dst->pen.uri = src->uri; + + if (src->width == 2 && pos.col < (new_cols - 1)) { + (dst + 1)->schar = (uint32_t)-1; + } + } + for (; pos.col < new_cols; pos.col++) { + clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); + } + new_row--; + + if (active) { + statefields->pos.row++; + } + } + } + if (new_row >= 0) { + // Scroll new rows back up to the top and fill in blanks at the bottom + int moverows = new_rows - new_row - 1; + memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], + (size_t)moverows * (size_t)new_cols * sizeof(ScreenCell)); + memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], + (size_t)moverows * sizeof(new_lineinfo[0])); + + new_cursor.row -= (new_row + 1); + + for (new_row = moverows; new_row < new_rows; new_row++) { + for (int col = 0; col < new_cols; col++) { + clearcell(screen, &new_buffer[new_row * new_cols + col]); + } + new_lineinfo[new_row] = (VTermLineInfo){ 0 }; + } + } + + vterm_allocator_free(screen->vt, old_buffer); + screen->buffers[bufidx] = new_buffer; + + vterm_allocator_free(screen->vt, old_lineinfo); + statefields->lineinfos[bufidx] = new_lineinfo; + + if (active) { + statefields->pos = new_cursor; + } +} + +static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) +{ + VTermScreen *screen = user; + + int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] + && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); + + int old_rows = screen->rows; + int old_cols = screen->cols; + + if (new_cols > old_cols) { + // Ensure that ->sb_buffer is large enough for a new or and old row + if (screen->sb_buffer) { + vterm_allocator_free(screen->vt, screen->sb_buffer); + } + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, + sizeof(VTermScreenCell) * (size_t)new_cols); + } + + resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); + if (screen->buffers[BUFIDX_ALTSCREEN]) { + resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); + } else if (new_rows != old_rows) { + // We don't need a full resize of the altscreen because it isn't enabled but we should at least + // keep the lineinfo the right size + vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); + + VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, + sizeof(new_lineinfo[0]) * + (size_t)new_rows); + for (int row = 0; row < new_rows; row++) { + new_lineinfo[row] = (VTermLineInfo){ 0 }; + } + + fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; + } + + screen->buffer = + altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; + + screen->rows = new_rows; + screen->cols = new_cols; + + if (new_cols <= old_cols) { + if (screen->sb_buffer) { + vterm_allocator_free(screen->vt, screen->sb_buffer); + } + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, + sizeof(VTermScreenCell) * (size_t)new_cols); + } + + // TODO(vterm): Maaaaybe we can optimise this if there's no reflow happening + damagescreen(screen); + + if (screen->callbacks && screen->callbacks->resize) { + return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); + } + + return 1; +} + +static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, + void *user) +{ + VTermScreen *screen = user; + + if (newinfo->doublewidth != oldinfo->doublewidth + || newinfo->doubleheight != oldinfo->doubleheight) { + for (int col = 0; col < screen->cols; col++) { + ScreenCell *cell = getcell(screen, row, col); + cell->pen.dwl = newinfo->doublewidth; + cell->pen.dhl = newinfo->doubleheight; + } + + VTermRect rect = { + .start_row = row, + .end_row = row + 1, + .start_col = 0, + .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, + }; + damagerect(screen, rect); + + if (newinfo->doublewidth) { + rect.start_col = screen->cols / 2; + rect.end_col = screen->cols; + + erase_internal(rect, 0, user); + } + } + + return 1; +} + +static int sb_clear(void *user) +{ + VTermScreen *screen = user; + + if (screen->callbacks && screen->callbacks->sb_clear) { + if ((*screen->callbacks->sb_clear)(screen->cbdata)) { + return 1; + } + } + + return 0; +} + +static VTermStateCallbacks state_cbs = { + .putglyph = &putglyph, + .movecursor = &movecursor, + .scrollrect = &scrollrect, + .erase = &erase, + .setpenattr = &setpenattr, + .settermprop = &settermprop, + .bell = &bell, + .resize = &resize, + .setlineinfo = &setlineinfo, + .sb_clear = &sb_clear, +}; + +static VTermScreen *screen_new(VTerm *vt) +{ + VTermState *state = vterm_obtain_state(vt); + if (!state) { + return NULL; + } + + VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); + int rows, cols; + + vterm_get_size(vt, &rows, &cols); + + screen->vt = vt; + screen->state = state; + + screen->damage_merge = VTERM_DAMAGE_CELL; + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + + screen->rows = rows; + screen->cols = cols; + + screen->global_reverse = false; + screen->reflow = false; + + screen->callbacks = NULL; + screen->cbdata = NULL; + + screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); + + screen->buffer = screen->buffers[BUFIDX_PRIMARY]; + + screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * (size_t)cols); + + vterm_state_set_callbacks(screen->state, &state_cbs, screen); + + return screen; +} + +void vterm_screen_free(VTermScreen *screen) +{ + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); + if (screen->buffers[BUFIDX_ALTSCREEN]) { + vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); + } + + vterm_allocator_free(screen->vt, screen->sb_buffer); + + vterm_allocator_free(screen->vt, screen); +} + +void vterm_screen_reset(VTermScreen *screen, int hard) +{ + screen->damaged.start_row = -1; + screen->pending_scrollrect.start_row = -1; + vterm_state_reset(screen->state, hard); + vterm_screen_flush_damage(screen); +} + +// Copy internal to external representation of a screen cell +int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) +{ + ScreenCell *intcell = getcell(screen, pos.row, pos.col); + if (!intcell) { + return 0; + } + + cell->schar = intcell->schar; + + cell->attrs.bold = intcell->pen.bold; + cell->attrs.underline = intcell->pen.underline; + cell->attrs.italic = intcell->pen.italic; + cell->attrs.blink = intcell->pen.blink; + cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; + cell->attrs.conceal = intcell->pen.conceal; + cell->attrs.strike = intcell->pen.strike; + cell->attrs.font = intcell->pen.font; + cell->attrs.small = intcell->pen.small; + cell->attrs.baseline = intcell->pen.baseline; + + cell->attrs.dwl = intcell->pen.dwl; + cell->attrs.dhl = intcell->pen.dhl; + + cell->fg = intcell->pen.fg; + cell->bg = intcell->pen.bg; + + cell->uri = intcell->pen.uri; + + if (pos.col < (screen->cols - 1) + && getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1) { + cell->width = 2; + } else { + cell->width = 1; + } + + return 1; +} + +VTermScreen *vterm_obtain_screen(VTerm *vt) +{ + if (vt->screen) { + return vt->screen; + } + + VTermScreen *screen = screen_new(vt); + vt->screen = screen; + + return screen; +} + +void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) +{ + screen->reflow = reflow; +} + +#undef vterm_screen_set_reflow +void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) +{ + vterm_screen_enable_reflow(screen, reflow); +} + +void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) +{ + if (!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { + int rows, cols; + vterm_get_size(screen->vt, &rows, &cols); + + screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); + } +} + +void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, + void *user) +{ + screen->callbacks = callbacks; + screen->cbdata = user; +} + +void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, + const VTermStateFallbacks *fallbacks, void *user) +{ + vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); +} + +void vterm_screen_flush_damage(VTermScreen *screen) +{ + if (screen->pending_scrollrect.start_row != -1) { + vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, + screen->pending_scroll_rightward, + moverect_user, erase_user, screen); + + screen->pending_scrollrect.start_row = -1; + } + + if (screen->damaged.start_row != -1) { + if (screen->callbacks && screen->callbacks->damage) { + (*screen->callbacks->damage)(screen->damaged, screen->cbdata); + } + + screen->damaged.start_row = -1; + } +} + +void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) +{ + vterm_screen_flush_damage(screen); + screen->damage_merge = size; +} + +/// Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` instance. +void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) +{ + vterm_state_convert_color_to_rgb(screen->state, col); +} + +// Some utility functions on VTermRect structures + +#define STRFrect "(%d,%d-%d,%d)" +#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col + +// Expand dst to contain src as well +void rect_expand(VTermRect *dst, VTermRect *src) +{ + if (dst->start_row > src->start_row) { + dst->start_row = src->start_row; + } + if (dst->start_col > src->start_col) { + dst->start_col = src->start_col; + } + if (dst->end_row < src->end_row) { + dst->end_row = src->end_row; + } + if (dst->end_col < src->end_col) { + dst->end_col = src->end_col; + } +} + +// Clip the dst to ensure it does not step outside of bounds +void rect_clip(VTermRect *dst, VTermRect *bounds) +{ + if (dst->start_row < bounds->start_row) { + dst->start_row = bounds->start_row; + } + if (dst->start_col < bounds->start_col) { + dst->start_col = bounds->start_col; + } + if (dst->end_row > bounds->end_row) { + dst->end_row = bounds->end_row; + } + if (dst->end_col > bounds->end_col) { + dst->end_col = bounds->end_col; + } + // Ensure it doesn't end up negatively-sized + if (dst->end_row < dst->start_row) { + dst->end_row = dst->start_row; + } + if (dst->end_col < dst->start_col) { + dst->end_col = dst->start_col; + } +} + +// True if the two rectangles are equal +int rect_equal(VTermRect *a, VTermRect *b) +{ + return (a->start_row == b->start_row) + && (a->start_col == b->start_col) + && (a->end_row == b->end_row) + && (a->end_col == b->end_col); +} + +// True if small is contained entirely within big +int rect_contains(VTermRect *big, VTermRect *small) +{ + if (small->start_row < big->start_row) { + return 0; + } + if (small->start_col < big->start_col) { + return 0; + } + if (small->end_row > big->end_row) { + return 0; + } + if (small->end_col > big->end_col) { + return 0; + } + return 1; +} + +// True if the rectangles overlap at all +int rect_intersects(VTermRect *a, VTermRect *b) +{ + if (a->start_row > b->end_row || b->start_row > a->end_row) { + return 0; + } + if (a->start_col > b->end_col || b->start_col > a->end_col) { + return 0; + } + return 1; +} diff --git a/src/nvim/vterm/screen.h b/src/nvim/vterm/screen.h new file mode 100644 index 0000000000..fa7520d75a --- /dev/null +++ b/src/nvim/vterm/screen.h @@ -0,0 +1,9 @@ +#pragma once + +#include <stddef.h> + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/screen.h.generated.h" +#endif diff --git a/src/nvim/vterm/state.c b/src/nvim/vterm/state.c new file mode 100644 index 0000000000..9e787acd9b --- /dev/null +++ b/src/nvim/vterm/state.c @@ -0,0 +1,2310 @@ +#include <stdio.h> +#include <string.h> + +#include "nvim/grid.h" +#include "nvim/mbyte.h" +#include "nvim/vterm/encoding.h" +#include "nvim/vterm/parser.h" +#include "nvim/vterm/pen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/state.c.generated.h" +#endif + +#define strneq(a, b, n) (strncmp(a, b, n) == 0) + +// Some convenient wrappers to make callback functions easier + +static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos) +{ + VTermGlyphInfo info = { + .schar = schar, + .width = width, + .protected_cell = state->protected_cell, + .dwl = state->lineinfo[pos.row].doublewidth, + .dhl = state->lineinfo[pos.row].doubleheight, + }; + + if (state->callbacks && state->callbacks->putglyph) { + if ((*state->callbacks->putglyph)(&info, pos, state->cbdata)) { + return; + } + } + + DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); +} + +static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) +{ + if (state->pos.col == oldpos->col && state->pos.row == oldpos->row) { + return; + } + + if (cancel_phantom) { + state->at_phantom = 0; + } + + if (state->callbacks && state->callbacks->movecursor) { + if ((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, + state->cbdata)) { + return; + } + } +} + +static void erase(VTermState *state, VTermRect rect, int selective) +{ + if (rect.end_col == state->cols) { + // If we're erasing the final cells of any lines, cancel the continuation marker on the + // subsequent line + for (int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) { + state->lineinfo[row].continuation = 0; + } + } + + if (state->callbacks && state->callbacks->erase) { + if ((*state->callbacks->erase)(rect, selective, state->cbdata)) { + return; + } + } +} + +static VTermState *vterm_state_new(VTerm *vt) +{ + VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); + + state->vt = vt; + + state->rows = vt->rows; + state->cols = vt->cols; + + state->mouse_col = 0; + state->mouse_row = 0; + state->mouse_buttons = 0; + + state->mouse_protocol = MOUSE_X10; + + state->callbacks = NULL; + state->cbdata = NULL; + + state->selection.callbacks = NULL; + state->selection.user = NULL; + state->selection.buffer = NULL; + + vterm_state_newpen(state); + + state->bold_is_highbright = 0; + + state->combine_pos.row = -1; + + state->tabstops = vterm_allocator_malloc(state->vt, ((size_t)state->cols + 7) / 8); + + state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, + (size_t)state->rows * + sizeof(VTermLineInfo)); + // TODO(vterm): Make an 'enable' function + state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, + (size_t)state->rows * + sizeof(VTermLineInfo)); + state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; + + state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); + if (*state->encoding_utf8.enc->init) { + (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); + } + + return state; +} + +void vterm_state_free(VTermState *state) +{ + vterm_allocator_free(state->vt, state->tabstops); + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); + if (state->lineinfos[BUFIDX_ALTSCREEN]) { + vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); + } + vterm_allocator_free(state->vt, state); +} + +static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) +{ + if (!downward && !rightward) { + return; + } + + int rows = rect.end_row - rect.start_row; + if (downward > rows) { + downward = rows; + } else if (downward < -rows) { + downward = -rows; + } + + int cols = rect.end_col - rect.start_col; + if (rightward > cols) { + rightward = cols; + } else if (rightward < -cols) { + rightward = -cols; + } + + // Update lineinfo if full line + if (rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { + int height = rect.end_row - rect.start_row - abs(downward); + + if (downward > 0) { + memmove(state->lineinfo + rect.start_row, + state->lineinfo + rect.start_row + downward, + (size_t)height * sizeof(state->lineinfo[0])); + for (int row = rect.end_row - downward; row < rect.end_row; row++) { + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } else { + memmove(state->lineinfo + rect.start_row - downward, + state->lineinfo + rect.start_row, + (size_t)height * sizeof(state->lineinfo[0])); + for (int row = rect.start_row; row < rect.start_row - downward; row++) { + state->lineinfo[row] = (VTermLineInfo){ 0 }; + } + } + } + + if (state->callbacks && state->callbacks->scrollrect) { + if ((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) { + return; + } + } + + if (state->callbacks) { + vterm_scroll_rect(rect, downward, rightward, + state->callbacks->moverect, state->callbacks->erase, state->cbdata); + } +} + +static void linefeed(VTermState *state) +{ + if (state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, 1, 0); + } else if (state->pos.row < state->rows - 1) { + state->pos.row++; + } +} + +static void set_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + state->tabstops[col >> 3] |= mask; +} + +static void clear_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + state->tabstops[col >> 3] &= ~mask; +} + +static int is_col_tabstop(VTermState *state, int col) +{ + uint8_t mask = (uint8_t)(1 << (col & 7)); + return state->tabstops[col >> 3] & mask; +} + +static int is_cursor_in_scrollregion(const VTermState *state) +{ + if (state->pos.row < state->scrollregion_top + || state->pos.row >= SCROLLREGION_BOTTOM(state)) { + return 0; + } + if (state->pos.col < SCROLLREGION_LEFT(state) + || state->pos.col >= SCROLLREGION_RIGHT(state)) { + return 0; + } + + return 1; +} + +static void tab(VTermState *state, int count, int direction) +{ + while (count > 0) { + if (direction > 0) { + if (state->pos.col >= THISROWWIDTH(state) - 1) { + return; + } + + state->pos.col++; + } else if (direction < 0) { + if (state->pos.col < 1) { + return; + } + + state->pos.col--; + } + + if (is_col_tabstop(state, state->pos.col)) { + count--; + } + } +} + +#define NO_FORCE 0 +#define FORCE 1 + +#define DWL_OFF 0 +#define DWL_ON 1 + +#define DHL_OFF 0 +#define DHL_TOP 1 +#define DHL_BOTTOM 2 + +static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) +{ + VTermLineInfo info = state->lineinfo[row]; + + if (dwl == DWL_OFF) { + info.doublewidth = DWL_OFF; + } else if (dwl == DWL_ON) { + info.doublewidth = DWL_ON; + } + // else -1 to ignore + + if (dhl == DHL_OFF) { + info.doubleheight = DHL_OFF; + } else if (dhl == DHL_TOP) { + info.doubleheight = DHL_TOP; + } else if (dhl == DHL_BOTTOM) { + info.doubleheight = DHL_BOTTOM; + } + + if ((state->callbacks + && state->callbacks->setlineinfo + && (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) + || force) { + state->lineinfo[row] = info; + } +} + +static int on_text(const char bytes[], size_t len, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); + size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); + + int npoints = 0; + size_t eaten = 0; + + VTermEncodingInstance *encoding = + state->gsingle_set ? &state->encoding[state->gsingle_set] + : !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] + : state->vt->mode.utf8 ? &state->encoding_utf8 + : &state->encoding[state-> + gr_set]; + + (*encoding->enc->decode)(encoding->enc, encoding->data, + codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints, + bytes, &eaten, len); + + // There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet for even a single codepoint + if (!npoints) { + return (int)eaten; + } + + if (state->gsingle_set && npoints) { + state->gsingle_set = 0; + } + + int i = 0; + GraphemeState grapheme_state = GRAPHEME_STATE_INIT; + size_t grapheme_len = 0; + bool recombine = false; + + // See if the cursor has moved since + if (state->pos.row == state->combine_pos.row + && state->pos.col == state->combine_pos.col + state->combine_width) { + // This is a combining char. that needs to be merged with the previous glyph output + if (utf_iscomposing((int)state->grapheme_last, (int)codepoints[i], &state->grapheme_state)) { + // Find where we need to append these combining chars + grapheme_len = state->grapheme_len; + grapheme_state = state->grapheme_state; + state->pos.col = state->combine_pos.col; + recombine = true; + } else { + DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); + } + } + + while (i < npoints) { + // Try to find combining characters following this + do { + if (grapheme_len < sizeof(state->grapheme_buf) - 4) { + grapheme_len += (size_t)utf_char2bytes((int)codepoints[i], + state->grapheme_buf + grapheme_len); + } + i++; + } while (i < npoints && utf_iscomposing((int)codepoints[i - 1], (int)codepoints[i], + &grapheme_state)); + + int width = utf_ptr2cells_len(state->grapheme_buf, (int)grapheme_len); + + if (state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { + linefeed(state); + state->pos.col = 0; + state->at_phantom = 0; + state->lineinfo[state->pos.row].continuation = 1; + } + + if (state->mode.insert && !recombine) { + // TODO(vterm): This will be a little inefficient for large bodies of text, as it'll have to + // 'ICH' effectively before every glyph. We should scan ahead and ICH as many times as + // required + VTermRect rect = { + .start_row = state->pos.row, + .end_row = state->pos.row + 1, + .start_col = state->pos.col, + .end_col = THISROWWIDTH(state), + }; + scroll(state, rect, 0, -1); + } + + schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len); + putglyph(state, sc, width, state->pos); + + if (i == npoints) { + // End of the buffer. Save the chars in case we have to combine with more on the next call + state->grapheme_len = grapheme_len; + state->grapheme_last = codepoints[i - 1]; + state->grapheme_state = grapheme_state; + state->combine_width = width; + state->combine_pos = state->pos; + } else { + grapheme_len = 0; + recombine = false; + } + + if (state->pos.col + width >= THISROWWIDTH(state)) { + if (state->mode.autowrap) { + state->at_phantom = 1; + } + } else { + state->pos.col += width; + } + } + + updatecursor(state, &oldpos, 0); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", + state->pos.row, state->pos.col); + abort(); + } +#endif + + return (int)eaten; +} + +static int on_control(uint8_t control, void *user) +{ + VTermState *state = user; + + VTermPos oldpos = state->pos; + + switch (control) { + case 0x07: // BEL - ECMA-48 8.3.3 + if (state->callbacks && state->callbacks->bell) { + (*state->callbacks->bell)(state->cbdata); + } + break; + + case 0x08: // BS - ECMA-48 8.3.5 + if (state->pos.col > 0) { + state->pos.col--; + } + break; + + case 0x09: // HT - ECMA-48 8.3.60 + tab(state, 1, +1); + break; + + case 0x0a: // LF - ECMA-48 8.3.74 + case 0x0b: // VT + case 0x0c: // FF + linefeed(state); + if (state->mode.newline) { + state->pos.col = 0; + } + break; + + case 0x0d: // CR - ECMA-48 8.3.15 + state->pos.col = 0; + break; + + case 0x0e: // LS1 - ECMA-48 8.3.76 + state->gl_set = 1; + break; + + case 0x0f: // LS0 - ECMA-48 8.3.75 + state->gl_set = 0; + break; + + case 0x84: // IND - DEPRECATED but implemented for completeness + linefeed(state); + break; + + case 0x85: // NEL - ECMA-48 8.3.86 + linefeed(state); + state->pos.col = 0; + break; + + case 0x88: // HTS - ECMA-48 8.3.62 + set_col_tabstop(state, state->pos.col); + break; + + case 0x8d: // RI - ECMA-48 8.3.104 + if (state->pos.row == state->scrollregion_top) { + VTermRect rect = { + .start_row = state->scrollregion_top, + .end_row = SCROLLREGION_BOTTOM(state), + .start_col = SCROLLREGION_LEFT(state), + .end_col = SCROLLREGION_RIGHT(state), + }; + + scroll(state, rect, -1, 0); + } else if (state->pos.row > 0) { + state->pos.row--; + } + break; + + case 0x8e: // SS2 - ECMA-48 8.3.141 + state->gsingle_set = 2; + break; + + case 0x8f: // SS3 - ECMA-48 8.3.142 + state->gsingle_set = 3; + break; + + default: + if (state->fallbacks && state->fallbacks->control) { + if ((*state->fallbacks->control)(control, state->fbdata)) { + return 1; + } + } + + return 0; + } + + updatecursor(state, &oldpos, 1); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", + control, state->pos.row, state->pos.col); + abort(); + } +#endif + + return 1; +} + +static int settermprop_bool(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .boolean = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_int(VTermState *state, VTermProp prop, int v) +{ + VTermValue val = { .number = v }; + return vterm_state_set_termprop(state, prop, &val); +} + +static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) +{ + VTermValue val = { .string = frag }; + return vterm_state_set_termprop(state, prop, &val); +} + +static void savecursor(VTermState *state, int save) +{ + if (save) { + state->saved.pos = state->pos; + state->saved.mode.cursor_visible = state->mode.cursor_visible; + state->saved.mode.cursor_blink = state->mode.cursor_blink; + state->saved.mode.cursor_shape = state->mode.cursor_shape; + + vterm_state_savepen(state, 1); + } else { + VTermPos oldpos = state->pos; + + state->pos = state->saved.pos; + + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); + + vterm_state_savepen(state, 0); + + updatecursor(state, &oldpos, 1); + } +} + +static int on_escape(const char *bytes, size_t len, void *user) +{ + VTermState *state = user; + + // Easier to decode this from the first byte, even though the final byte terminates it + switch (bytes[0]) { + case ' ': + if (len != 2) { + return 0; + } + + switch (bytes[1]) { + case 'F': // S7C1T + state->vt->mode.ctrl8bit = 0; + break; + + case 'G': // S8C1T + state->vt->mode.ctrl8bit = 1; + break; + + default: + return 0; + } + return 2; + + case '#': + if (len != 2) { + return 0; + } + + switch (bytes[1]) { + case '3': // DECDHL top + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); + break; + + case '4': // DECDHL bottom + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); + break; + + case '5': // DECSWL + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); + break; + + case '6': // DECDWL + if (state->mode.leftrightmargin) { + break; + } + set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); + break; + + case '8': // DECALN + { + VTermPos pos; + schar_T E = schar_from_ascii('E'); // E + for (pos.row = 0; pos.row < state->rows; pos.row++) { + for (pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) { + putglyph(state, E, 1, pos); + } + } + break; + } + + default: + return 0; + } + return 2; + + case '(': + case ')': + case '*': + case '+': // SCS + if (len != 2) { + return 0; + } + + { + int setnum = bytes[0] - 0x28; + VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); + + if (newenc) { + state->encoding[setnum].enc = newenc; + + if (newenc->init) { + (*newenc->init)(newenc, state->encoding[setnum].data); + } + } + } + + return 2; + + case '7': // DECSC + savecursor(state, 1); + return 1; + + case '8': // DECRC + savecursor(state, 0); + return 1; + + case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 + return 1; + + case '=': // DECKPAM + state->mode.keypad = 1; + return 1; + + case '>': // DECKPNM + state->mode.keypad = 0; + return 1; + + case 'c': // RIS - ECMA-48 8.3.105 + { + VTermPos oldpos = state->pos; + vterm_state_reset(state, 1); + if (state->callbacks && state->callbacks->movecursor) { + (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, + state->cbdata); + } + return 1; + } + + case 'n': // LS2 - ECMA-48 8.3.78 + state->gl_set = 2; + return 1; + + case 'o': // LS3 - ECMA-48 8.3.80 + state->gl_set = 3; + return 1; + + case '~': // LS1R - ECMA-48 8.3.77 + state->gr_set = 1; + return 1; + + case '}': // LS2R - ECMA-48 8.3.79 + state->gr_set = 2; + return 1; + + case '|': // LS3R - ECMA-48 8.3.81 + state->gr_set = 3; + return 1; + + default: + return 0; + } +} + +static void set_mode(VTermState *state, int num, int val) +{ + switch (num) { + case 4: // IRM - ECMA-48 7.2.10 + state->mode.insert = (unsigned)val; + break; + + case 20: // LNM - ANSI X3.4-1977 + state->mode.newline = (unsigned)val; + break; + + default: + DEBUG_LOG("libvterm: Unknown mode %d\n", num); + return; + } +} + +static void set_dec_mode(VTermState *state, int num, int val) +{ + switch (num) { + case 1: + state->mode.cursor = (unsigned)val; + break; + + case 5: // DECSCNM - screen mode + settermprop_bool(state, VTERM_PROP_REVERSE, val); + break; + + case 6: // DECOM - origin mode + { + VTermPos oldpos = state->pos; + state->mode.origin = (unsigned)val; + state->pos.row = state->mode.origin ? state->scrollregion_top : 0; + state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; + updatecursor(state, &oldpos, 1); + } + break; + + case 7: + state->mode.autowrap = (unsigned)val; + break; + + case 12: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); + break; + + case 25: + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); + break; + + case 69: // DECVSSM - vertical split screen mode + // DECLRMM - left/right margin mode + state->mode.leftrightmargin = (unsigned)val; + if (val) { + // Setting DECVSSM must clear doublewidth/doubleheight state of every line + for (int row = 0; row < state->rows; row++) { + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + } + + break; + + case 1000: + case 1002: + case 1003: + settermprop_int(state, VTERM_PROP_MOUSE, + !val ? VTERM_PROP_MOUSE_NONE + : (num == 1000) ? VTERM_PROP_MOUSE_CLICK + : (num == 1002) ? VTERM_PROP_MOUSE_DRAG + : VTERM_PROP_MOUSE_MOVE); + break; + + case 1004: + settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val); + state->mode.report_focus = (unsigned)val; + break; + + case 1005: + state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; + break; + + case 1006: + state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; + break; + + case 1015: + state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; + break; + + case 1047: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + break; + + case 1048: + savecursor(state, val); + break; + + case 1049: + settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); + savecursor(state, val); + break; + + case 2004: + state->mode.bracketpaste = (unsigned)val; + break; + + default: + DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); + return; + } +} + +static void request_dec_mode(VTermState *state, int num) +{ + int reply; + + switch (num) { + case 1: + reply = state->mode.cursor; + break; + + case 5: + reply = state->mode.screen; + break; + + case 6: + reply = state->mode.origin; + break; + + case 7: + reply = state->mode.autowrap; + break; + + case 12: + reply = state->mode.cursor_blink; + break; + + case 25: + reply = state->mode.cursor_visible; + break; + + case 69: + reply = state->mode.leftrightmargin; + break; + + case 1000: + reply = state->mouse_flags == MOUSE_WANT_CLICK; + break; + + case 1002: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); + break; + + case 1003: + reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); + break; + + case 1004: + reply = state->mode.report_focus; + break; + + case 1005: + reply = state->mouse_protocol == MOUSE_UTF8; + break; + + case 1006: + reply = state->mouse_protocol == MOUSE_SGR; + break; + + case 1015: + reply = state->mouse_protocol == MOUSE_RXVT; + break; + + case 1047: + reply = state->mode.alt_screen; + break; + + case 2004: + reply = state->mode.bracketpaste; + break; + + default: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); + return; + } + + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); +} + +static void request_version_string(VTermState *state) +{ + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", + VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); +} + +static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user) +{ + VTermState *state = user; + int leader_byte = 0; + int intermed_byte = 0; + int cancel_phantom = 1; + + if (leader && leader[0]) { + if (leader[1]) { // longer than 1 char + return 0; + } + + switch (leader[0]) { + case '?': + case '>': + leader_byte = (int)leader[0]; + break; + default: + return 0; + } + } + + if (intermed && intermed[0]) { + if (intermed[1]) { // longer than 1 char + return 0; + } + + switch (intermed[0]) { + case ' ': + case '!': + case '"': + case '$': + case '\'': + intermed_byte = (int)intermed[0]; + break; + default: + return 0; + } + } + + VTermPos oldpos = state->pos; + + // Some temporaries for later code + int count, val; + int row, col; + VTermRect rect; + int selective; + +#define LBOUND(v, min) if ((v) < (min))(v) = (min) +#define UBOUND(v, max) if ((v) > (max))(v) = (max) + +#define LEADER(l, b) ((l << 8) | b) +#define INTERMED(i, b) ((i << 16) | b) + + switch (intermed_byte << 16 | leader_byte << 8 | command) { + case 0x40: // ICH - ECMA-48 8.3.64 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) { + rect.end_col = SCROLLREGION_RIGHT(state); + } else { + rect.end_col = THISROWWIDTH(state); + } + + scroll(state, rect, 0, -count); + + break; + + case 0x41: // CUU - ECMA-48 8.3.22 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x42: // CUD - ECMA-48 8.3.19 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x43: // CUF - ECMA-48 8.3.20 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x44: // CUB - ECMA-48 8.3.18 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x45: // CNL - ECMA-48 8.3.12 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x46: // CPL - ECMA-48 8.3.13 + count = CSI_ARG_COUNT(args[0]); + state->pos.col = 0; + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x47: // CHA - ECMA-48 8.3.9 + val = CSI_ARG_OR(args[0], 1); + state->pos.col = val - 1; + state->at_phantom = 0; + break; + + case 0x48: // CUP - ECMA-48 8.3.21 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x49: // CHT - ECMA-48 8.3.10 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, +1); + break; + + case 0x4a: // ED - ECMA-48 8.3.39 + case LEADER('?', 0x4a): // DECSED - Selective Erase in Display + selective = (leader_byte == '?'); + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; rect.end_col = state->cols; + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + rect.start_row = state->pos.row + 1; rect.end_row = state->rows; + rect.start_col = 0; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + if (rect.end_row > rect.start_row) { + erase(state, rect, selective); + } + break; + + case 1: + rect.start_row = 0; rect.end_row = state->pos.row; + rect.start_col = 0; rect.end_col = state->cols; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; + rect.end_col = state->pos.col + 1; + if (rect.end_row > rect.start_row) { + erase(state, rect, selective); + } + break; + + case 2: + rect.start_row = 0; rect.end_row = state->rows; + rect.start_col = 0; rect.end_col = state->cols; + for (int row_ = rect.start_row; row_ < rect.end_row; row_++) { + set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); + } + erase(state, rect, selective); + break; + + case 3: + if (state->callbacks && state->callbacks->sb_clear) { + if ((*state->callbacks->sb_clear)(state->cbdata)) { + return 1; + } + } + break; + } + break; + + case 0x4b: // EL - ECMA-48 8.3.41 + case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line + selective = (leader_byte == '?'); + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + + switch (CSI_ARG(args[0])) { + case CSI_ARG_MISSING: + case 0: + rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; + case 1: + rect.start_col = 0; rect.end_col = state->pos.col + 1; break; + case 2: + rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; + default: + return 0; + } + + if (rect.end_col > rect.start_col) { + erase(state, rect, selective); + } + + break; + + case 0x4c: // IL - ECMA-48 8.3.67 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x4d: // DL - ECMA-48 8.3.32 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x50: // DCH - ECMA-48 8.3.26 + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + if (state->mode.leftrightmargin) { + rect.end_col = SCROLLREGION_RIGHT(state); + } else { + rect.end_col = THISROWWIDTH(state); + } + + scroll(state, rect, 0, count); + + break; + + case 0x53: // SU - ECMA-48 8.3.147 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, count, 0); + + break; + + case 0x54: // SD - ECMA-48 8.3.113 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = SCROLLREGION_LEFT(state); + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, -count, 0); + + break; + + case 0x58: // ECH - ECMA-48 8.3.38 + count = CSI_ARG_COUNT(args[0]); + + rect.start_row = state->pos.row; + rect.end_row = state->pos.row + 1; + rect.start_col = state->pos.col; + rect.end_col = state->pos.col + count; + UBOUND(rect.end_col, THISROWWIDTH(state)); + + erase(state, rect, 0); + break; + + case 0x5a: // CBT - ECMA-48 8.3.7 + count = CSI_ARG_COUNT(args[0]); + tab(state, count, -1); + break; + + case 0x60: // HPA - ECMA-48 8.3.57 + col = CSI_ARG_OR(args[0], 1); + state->pos.col = col - 1; + state->at_phantom = 0; + break; + + case 0x61: // HPR - ECMA-48 8.3.59 + count = CSI_ARG_COUNT(args[0]); + state->pos.col += count; + state->at_phantom = 0; + break; + + case 0x62: { // REP - ECMA-48 8.3.103 + const int row_width = THISROWWIDTH(state); + count = CSI_ARG_COUNT(args[0]); + col = state->pos.col + count; + UBOUND(col, row_width); + schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len); + while (state->pos.col < col) { + putglyph(state, sc, state->combine_width, state->pos); + state->pos.col += state->combine_width; + } + if (state->pos.col + state->combine_width >= row_width) { + if (state->mode.autowrap) { + state->at_phantom = 1; + cancel_phantom = 0; + } + } + break; + } + + case 0x63: // DA - ECMA-48 8.3.24 + val = CSI_ARG_OR(args[0], 0); + if (val == 0) { + // DEC VT100 response + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); + } + break; + + case LEADER('>', 0x63): // DEC secondary Device Attributes + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); + break; + + case 0x64: // VPA - ECMA-48 8.3.158 + row = CSI_ARG_OR(args[0], 1); + state->pos.row = row - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + } + state->at_phantom = 0; + break; + + case 0x65: // VPR - ECMA-48 8.3.160 + count = CSI_ARG_COUNT(args[0]); + state->pos.row += count; + state->at_phantom = 0; + break; + + case 0x66: // HVP - ECMA-48 8.3.63 + row = CSI_ARG_OR(args[0], 1); + col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); + // zero-based + state->pos.row = row - 1; + state->pos.col = col - 1; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + state->at_phantom = 0; + break; + + case 0x67: // TBC - ECMA-48 8.3.154 + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + clear_col_tabstop(state, state->pos.col); + break; + case 3: + case 5: + for (col = 0; col < state->cols; col++) { + clear_col_tabstop(state, col); + } + break; + case 1: + case 2: + case 4: + break; + // TODO(vterm): 1, 2 and 4 aren't meaningful yet without line tab stops + default: + return 0; + } + break; + + case 0x68: // SM - ECMA-48 8.3.125 + if (!CSI_ARG_IS_MISSING(args[0])) { + set_mode(state, CSI_ARG(args[0]), 1); + } + break; + + case LEADER('?', 0x68): // DEC private mode set + for (int i = 0; i < argcount; i++) { + if (!CSI_ARG_IS_MISSING(args[i])) { + set_dec_mode(state, CSI_ARG(args[i]), 1); + } + } + break; + + case 0x6a: // HPB - ECMA-48 8.3.58 + count = CSI_ARG_COUNT(args[0]); + state->pos.col -= count; + state->at_phantom = 0; + break; + + case 0x6b: // VPB - ECMA-48 8.3.159 + count = CSI_ARG_COUNT(args[0]); + state->pos.row -= count; + state->at_phantom = 0; + break; + + case 0x6c: // RM - ECMA-48 8.3.106 + if (!CSI_ARG_IS_MISSING(args[0])) { + set_mode(state, CSI_ARG(args[0]), 0); + } + break; + + case LEADER('?', 0x6c): // DEC private mode reset + for (int i = 0; i < argcount; i++) { + if (!CSI_ARG_IS_MISSING(args[i])) { + set_dec_mode(state, CSI_ARG(args[i]), 0); + } + } + break; + + case 0x6d: // SGR - ECMA-48 8.3.117 + vterm_state_setpen(state, args, argcount); + break; + + case LEADER('?', 0x6d): // DECSGR + // No actual DEC terminal recognised these, but some printers did. These are alternative ways to + // request subscript/superscript/off + for (int argi = 0; argi < argcount; argi++) { + long arg; + switch (arg = CSI_ARG(args[argi])) { + case 4: // Superscript on + arg = 73; + vterm_state_setpen(state, &arg, 1); + break; + case 5: // Subscript on + arg = 74; + vterm_state_setpen(state, &arg, 1); + break; + case 24: // Super+subscript off + arg = 75; + vterm_state_setpen(state, &arg, 1); + break; + } + } + break; + + case 0x6e: // DSR - ECMA-48 8.3.35 + case LEADER('?', 0x6e): // DECDSR + val = CSI_ARG_OR(args[0], 0); + + { + char *qmark = (leader_byte == '?') ? "?" : ""; + + switch (val) { + case 0: + case 1: + case 2: + case 3: + case 4: + // ignore - these are replies + break; + case 5: + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); + break; + case 6: // CPR - cursor position report + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, + state->pos.col + 1); + break; + } + } + break; + + case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset + vterm_state_reset(state, 0); + break; + + case LEADER('?', INTERMED('$', 0x70)): + request_dec_mode(state, CSI_ARG(args[0])); + break; + + case LEADER('>', 0x71): // XTVERSION - xterm query version string + request_version_string(state); + break; + + case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape + val = CSI_ARG_OR(args[0], 1); + + switch (val) { + case 0: + case 1: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 2: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + break; + case 3: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 4: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); + break; + case 5: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + case 6: + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); + break; + } + + break; + + case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute + val = CSI_ARG_OR(args[0], 0); + + switch (val) { + case 0: + case 2: + state->protected_cell = 0; + break; + case 1: + state->protected_cell = 1; + break; + } + + break; + + case 0x72: // DECSTBM - DEC custom + state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_bottom = argcount < 2 + || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_top, 0); + UBOUND(state->scrollregion_top, state->rows); + LBOUND(state->scrollregion_bottom, -1); + if (state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) { + state->scrollregion_bottom = -1; + } else { + UBOUND(state->scrollregion_bottom, state->rows); + } + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + // Invalid + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case 0x73: // DECSLRM - DEC custom + // Always allow setting these margins, just they won't take effect without DECVSSM + state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; + state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); + LBOUND(state->scrollregion_left, 0); + UBOUND(state->scrollregion_left, state->cols); + LBOUND(state->scrollregion_right, -1); + if (state->scrollregion_left == 0 && state->scrollregion_right == state->cols) { + state->scrollregion_right = -1; + } else { + UBOUND(state->scrollregion_right, state->cols); + } + + if (state->scrollregion_right > -1 + && state->scrollregion_right <= state->scrollregion_left) { + // Invalid + state->scrollregion_left = 0; + state->scrollregion_right = -1; + } + + // Setting the scrolling region restores the cursor to the home position + state->pos.row = 0; + state->pos.col = 0; + if (state->mode.origin) { + state->pos.row += state->scrollregion_top; + state->pos.col += SCROLLREGION_LEFT(state); + } + + break; + + case INTERMED('\'', 0x7D): // DECIC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, -count); + + break; + + case INTERMED('\'', 0x7E): // DECDC + count = CSI_ARG_COUNT(args[0]); + + if (!is_cursor_in_scrollregion(state)) { + break; + } + + rect.start_row = state->scrollregion_top; + rect.end_row = SCROLLREGION_BOTTOM(state); + rect.start_col = state->pos.col; + rect.end_col = SCROLLREGION_RIGHT(state); + + scroll(state, rect, 0, count); + + break; + + default: + if (state->fallbacks && state->fallbacks->csi) { + if ((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) { + return 1; + } + } + + return 0; + } + + if (state->mode.origin) { + LBOUND(state->pos.row, state->scrollregion_top); + UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state) - 1); + LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); + UBOUND(state->pos.col, SCROLLREGION_RIGHT(state) - 1); + } else { + LBOUND(state->pos.row, 0); + UBOUND(state->pos.row, state->rows - 1); + LBOUND(state->pos.col, 0); + UBOUND(state->pos.col, THISROWWIDTH(state) - 1); + } + + updatecursor(state, &oldpos, cancel_phantom); + +#ifdef DEBUG + if (state->pos.row < 0 || state->pos.row >= state->rows + || state->pos.col < 0 || state->pos.col >= state->cols) { + fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", + command, state->pos.row, state->pos.col); + abort(); + } + + if (SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { + fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); + abort(); + } + + if (SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { + fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", + command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); + abort(); + } +#endif + + return 1; +} + +static uint8_t unbase64one(char c) +{ + if (c >= 'A' && c <= 'Z') { + return (uint8_t)c - 'A'; + } else if (c >= 'a' && c <= 'z') { + return (uint8_t)c - 'a' + 26; + } else if (c >= '0' && c <= '9') { + return (uint8_t)c - '0' + 52; + } else if (c == '+') { + return 62; + } else if (c == '/') { + return 63; + } + + return 0xFF; +} + +static void osc_selection(VTermState *state, VTermStringFragment frag) +{ + if (frag.initial) { + state->tmp.selection.mask = 0; + state->tmp.selection.state = SELECTION_INITIAL; + } + + while (!state->tmp.selection.state && frag.len) { + // Parse selection parameter + switch (frag.str[0]) { + case 'c': + state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; + break; + case 'p': + state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; + break; + case 'q': + state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; + break; + case 's': + state->tmp.selection.mask |= VTERM_SELECTION_SELECT; + break; + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); + break; + + case ';': + state->tmp.selection.state = SELECTION_SELECTED; + if (!state->tmp.selection.mask) { + state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; + } + break; + } + + frag.str++; + frag.len--; + } + + if (!frag.len) { + // Clear selection if we're already finished but didn't do anything + if (frag.final && state->selection.callbacks->set) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = NULL, + .len = 0, + .initial = state->tmp.selection.state != SELECTION_SET, + .final = true, + }, state->selection.user); + } + return; + } + + if (state->tmp.selection.state == SELECTION_SELECTED) { + if (frag.str[0] == '?') { + state->tmp.selection.state = SELECTION_QUERY; + } else { + state->tmp.selection.state = SELECTION_SET_INITIAL; + state->tmp.selection.recvpartial = 0; + } + } + + if (state->tmp.selection.state == SELECTION_QUERY) { + if (state->selection.callbacks->query) { + (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); + } + return; + } + + if (state->tmp.selection.state == SELECTION_INVALID) { + return; + } + + if (state->selection.callbacks->set) { + size_t bufcur = 0; + char *buffer = state->selection.buffer; + + uint32_t x = 0; // Current decoding value + int n = 0; // Number of sextets consumed + + if (state->tmp.selection.recvpartial) { + n = state->tmp.selection.recvpartial >> 24; + x = state->tmp.selection.recvpartial & 0x03FFFF; // could be up to 18 bits of state in here + + state->tmp.selection.recvpartial = 0; + } + + while ((state->selection.buflen - bufcur) >= 3 && frag.len) { + if (frag.str[0] == '=') { + if (n == 2) { + buffer[0] = (char)(x >> 4 & 0xFF); + buffer += 1, bufcur += 1; + } + if (n == 3) { + buffer[0] = (char)(x >> 10 & 0xFF); + buffer[1] = (char)(x >> 2 & 0xFF); + buffer += 2, bufcur += 2; + } + + while (frag.len && frag.str[0] == '=') { + frag.str++, frag.len--; + } + + n = 0; + } else { + uint8_t b = unbase64one(frag.str[0]); + if (b == 0xFF) { + DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); + + state->tmp.selection.state = SELECTION_INVALID; + if (state->selection.callbacks->set) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = NULL, + .len = 0, + .initial = true, + .final = true, + }, state->selection.user); + } + break; + } + + x = (x << 6) | b; + n++; + frag.str++, frag.len--; + + if (n == 4) { + buffer[0] = (char)(x >> 16 & 0xFF); + buffer[1] = (char)(x >> 8 & 0xFF); + buffer[2] = (char)(x >> 0 & 0xFF); + + buffer += 3, bufcur += 3; + x = 0; + n = 0; + } + } + + if (!frag.len || (state->selection.buflen - bufcur) < 3) { + if (bufcur) { + (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ + .str = state->selection.buffer, + .len = bufcur, + .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, + .final = frag.final && !frag.len, + }, state->selection.user); + state->tmp.selection.state = SELECTION_SET; + } + + buffer = state->selection.buffer; + bufcur = 0; + } + } + + if (n) { + state->tmp.selection.recvpartial = (uint32_t)(n << 24) | x; + } + } +} + +static int on_osc(int command, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + switch (command) { + case 0: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 1: + settermprop_string(state, VTERM_PROP_ICONNAME, frag); + return 1; + + case 2: + settermprop_string(state, VTERM_PROP_TITLE, frag); + return 1; + + case 52: + if (state->selection.callbacks) { + osc_selection(state, frag); + } + + return 1; + + default: + if (state->fallbacks && state->fallbacks->osc) { + if ((*state->fallbacks->osc)(command, frag, state->fbdata)) { + return 1; + } + } + } + + return 0; +} + +static void request_status_string(VTermState *state, VTermStringFragment frag) +{ + VTerm *vt = state->vt; + + char *tmp = state->tmp.decrqss; + + if (frag.initial) { + tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; + } + + size_t i = 0; + while (i < sizeof(state->tmp.decrqss) - 1 && tmp[i]) { + i++; + } + while (i < sizeof(state->tmp.decrqss) - 1 && frag.len--) { + tmp[i++] = (frag.str++)[0]; + } + tmp[i] = 0; + + if (!frag.final) { + return; + } + + switch (tmp[0] | tmp[1] << 8 | tmp[2] << 16) { + case 'm': { + // Query SGR + long args[20]; + int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); + size_t cur = 0; + + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... + if (cur >= vt->tmpbuffer_len) { + return; + } + + for (int argi = 0; argi < argc; argi++) { + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + argi == argc - 1 ? "%ld" + : CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" + : "%ld;", + CSI_ARG(args[argi])); + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST + if (cur >= vt->tmpbuffer_len) { + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); + return; + } + + case 'r': + // Query DECSTBM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%dr", state->scrollregion_top + 1, + SCROLLREGION_BOTTOM(state)); + return; + + case 's': + // Query DECSLRM + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d;%ds", SCROLLREGION_LEFT(state) + 1, + SCROLLREGION_RIGHT(state)); + return; + + case ' '|('q' << 8): { + // Query DECSCUSR + int reply = 0; + switch (state->mode.cursor_shape) { + case VTERM_PROP_CURSORSHAPE_BLOCK: + reply = 2; break; + case VTERM_PROP_CURSORSHAPE_UNDERLINE: + reply = 4; break; + case VTERM_PROP_CURSORSHAPE_BAR_LEFT: + reply = 6; break; + } + if (state->mode.cursor_blink) { + reply--; + } + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d q", reply); + return; + } + + case '\"'|('q' << 8): + // Query DECSCA + vterm_push_output_sprintf_str(vt, C1_DCS, true, + "1$r%d\"q", state->protected_cell ? 1 : 2); + return; + } + + vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); +} + +static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (commandlen == 2 && strneq(command, "$q", 2)) { + request_status_string(state, frag); + return 1; + } else if (state->fallbacks && state->fallbacks->dcs) { + if ((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) { + return 1; + } + } + + DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); + return 0; +} + +static int on_apc(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->apc) { + if ((*state->fallbacks->apc)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all APCs are unhandled + return 0; +} + +static int on_pm(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->pm) { + if ((*state->fallbacks->pm)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all PMs are unhandled + return 0; +} + +static int on_sos(VTermStringFragment frag, void *user) +{ + VTermState *state = user; + + if (state->fallbacks && state->fallbacks->sos) { + if ((*state->fallbacks->sos)(frag, state->fbdata)) { + return 1; + } + } + + // No DEBUG_LOG because all SOSs are unhandled + return 0; +} + +static int on_resize(int rows, int cols, void *user) +{ + VTermState *state = user; + VTermPos oldpos = state->pos; + + if (cols != state->cols) { + uint8_t *newtabstops = vterm_allocator_malloc(state->vt, ((size_t)cols + 7) / 8); + + // TODO(vterm): This can all be done much more efficiently bytewise + int col; + for (col = 0; col < state->cols && col < cols; col++) { + uint8_t mask = (uint8_t)(1 << (col & 7)); + if (state->tabstops[col >> 3] & mask) { + newtabstops[col >> 3] |= mask; + } else { + newtabstops[col >> 3] &= ~mask; + } + } + + for (; col < cols; col++) { + uint8_t mask = (uint8_t)(1 << (col & 7)); + if (col % 8 == 0) { + newtabstops[col >> 3] |= mask; + } else { + newtabstops[col >> 3] &= ~mask; + } + } + + vterm_allocator_free(state->vt, state->tabstops); + state->tabstops = newtabstops; + } + + state->rows = rows; + state->cols = cols; + + if (state->scrollregion_bottom > -1) { + UBOUND(state->scrollregion_bottom, state->rows); + } + if (state->scrollregion_right > -1) { + UBOUND(state->scrollregion_right, state->cols); + } + + VTermStateFields fields = { + .pos = state->pos, + .lineinfos = {[0] = state->lineinfos[0], [1] = state->lineinfos[1] }, + }; + + if (state->callbacks && state->callbacks->resize) { + (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); + state->pos = fields.pos; + + state->lineinfos[0] = fields.lineinfos[0]; + state->lineinfos[1] = fields.lineinfos[1]; + } else { + if (rows != state->rows) { + for (int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { + VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; + if (!oldlineinfo) { + continue; + } + + VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, + (size_t)rows * sizeof(VTermLineInfo)); + + int row; + for (row = 0; row < state->rows && row < rows; row++) { + newlineinfo[row] = oldlineinfo[row]; + } + + for (; row < rows; row++) { + newlineinfo[row] = (VTermLineInfo){ + .doublewidth = 0, + }; + } + + vterm_allocator_free(state->vt, state->lineinfos[bufidx]); + state->lineinfos[bufidx] = newlineinfo; + } + } + } + + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + + if (state->at_phantom && state->pos.col < cols - 1) { + state->at_phantom = 0; + state->pos.col++; + } + + if (state->pos.row < 0) { + state->pos.row = 0; + } + if (state->pos.row >= rows) { + state->pos.row = rows - 1; + } + if (state->pos.col < 0) { + state->pos.col = 0; + } + if (state->pos.col >= cols) { + state->pos.col = cols - 1; + } + + updatecursor(state, &oldpos, 1); + + return 1; +} + +static const VTermParserCallbacks parser_callbacks = { + .text = on_text, + .control = on_control, + .escape = on_escape, + .csi = on_csi, + .osc = on_osc, + .dcs = on_dcs, + .apc = on_apc, + .pm = on_pm, + .sos = on_sos, + .resize = on_resize, +}; + +VTermState *vterm_obtain_state(VTerm *vt) +{ + if (vt->state) { + return vt->state; + } + + VTermState *state = vterm_state_new(vt); + vt->state = state; + + vterm_parser_set_callbacks(vt, &parser_callbacks, state); + + return state; +} + +void vterm_state_reset(VTermState *state, int hard) +{ + state->scrollregion_top = 0; + state->scrollregion_bottom = -1; + state->scrollregion_left = 0; + state->scrollregion_right = -1; + + state->mode.keypad = 0; + state->mode.cursor = 0; + state->mode.autowrap = 1; + state->mode.insert = 0; + state->mode.newline = 0; + state->mode.alt_screen = 0; + state->mode.origin = 0; + state->mode.leftrightmargin = 0; + state->mode.bracketpaste = 0; + state->mode.report_focus = 0; + + state->mouse_flags = 0; + + state->vt->mode.ctrl8bit = 0; + + for (int col = 0; col < state->cols; col++) { + if (col % 8 == 0) { + set_col_tabstop(state, col); + } else { + clear_col_tabstop(state, col); + } + } + + for (int row = 0; row < state->rows; row++) { + set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); + } + + if (state->callbacks && state->callbacks->initpen) { + (*state->callbacks->initpen)(state->cbdata); + } + + vterm_state_resetpen(state); + + VTermEncoding *default_enc = state->vt->mode.utf8 + ? vterm_lookup_encoding(ENC_UTF8, 'u') + : vterm_lookup_encoding(ENC_SINGLE_94, 'B'); + + for (int i = 0; i < 4; i++) { + state->encoding[i].enc = default_enc; + if (default_enc->init) { + (*default_enc->init)(default_enc, state->encoding[i].data); + } + } + + state->gl_set = 0; + state->gr_set = 1; + state->gsingle_set = 0; + + state->protected_cell = 0; + + // Initialise the props + settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); + settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); + settermprop_int(state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); + + if (hard) { + state->pos.row = 0; + state->pos.col = 0; + state->at_phantom = 0; + + VTermRect rect = { 0, state->rows, 0, state->cols }; + erase(state, rect, 0); + } +} + +void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) +{ + if (callbacks) { + state->callbacks = callbacks; + state->cbdata = user; + + if (state->callbacks && state->callbacks->initpen) { + (*state->callbacks->initpen)(state->cbdata); + } + } else { + state->callbacks = NULL; + state->cbdata = NULL; + } +} + +void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, + void *user) +{ + if (fallbacks) { + state->fallbacks = fallbacks; + state->fbdata = user; + } else { + state->fallbacks = NULL; + state->fbdata = NULL; + } +} + +int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) +{ + // Only store the new value of the property if usercode said it was happy. This is especially + // important for altscreen switching + if (state->callbacks && state->callbacks->settermprop) { + if (!(*state->callbacks->settermprop)(prop, val, state->cbdata)) { + return 0; + } + } + + switch (prop) { + case VTERM_PROP_TITLE: + case VTERM_PROP_ICONNAME: + // we don't store these, just transparently pass through + return 1; + case VTERM_PROP_CURSORVISIBLE: + state->mode.cursor_visible = (unsigned)val->boolean; + return 1; + case VTERM_PROP_CURSORBLINK: + state->mode.cursor_blink = (unsigned)val->boolean; + return 1; + case VTERM_PROP_CURSORSHAPE: + state->mode.cursor_shape = (unsigned)val->number; + return 1; + case VTERM_PROP_REVERSE: + state->mode.screen = (unsigned)val->boolean; + return 1; + case VTERM_PROP_ALTSCREEN: + state->mode.alt_screen = (unsigned)val->boolean; + state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; + if (state->mode.alt_screen) { + VTermRect rect = { + .start_row = 0, + .start_col = 0, + .end_row = state->rows, + .end_col = state->cols, + }; + erase(state, rect, 0); + } + return 1; + case VTERM_PROP_MOUSE: + state->mouse_flags = 0; + if (val->number) { + state->mouse_flags |= MOUSE_WANT_CLICK; + } + if (val->number == VTERM_PROP_MOUSE_DRAG) { + state->mouse_flags |= MOUSE_WANT_DRAG; + } + if (val->number == VTERM_PROP_MOUSE_MOVE) { + state->mouse_flags |= MOUSE_WANT_MOVE; + } + return 1; + case VTERM_PROP_FOCUSREPORT: + state->mode.report_focus = (unsigned)val->boolean; + return 1; + + case VTERM_N_PROPS: + return 0; + } + + return 0; +} + +void vterm_state_focus_in(VTermState *state) +{ + if (state->mode.report_focus) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); + } +} + +void vterm_state_focus_out(VTermState *state) +{ + if (state->mode.report_focus) { + vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); + } +} + +const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) +{ + return state->lineinfo + row; +} + +void vterm_state_set_selection_callbacks(VTermState *state, + const VTermSelectionCallbacks *callbacks, void *user, + char *buffer, size_t buflen) +{ + if (buflen && !buffer) { + buffer = vterm_allocator_malloc(state->vt, buflen); + } + + state->selection.callbacks = callbacks; + state->selection.user = user; + state->selection.buffer = buffer; + state->selection.buflen = buflen; +} diff --git a/src/nvim/vterm/state.h b/src/nvim/vterm/state.h new file mode 100644 index 0000000000..2f59cf7eec --- /dev/null +++ b/src/nvim/vterm/state.h @@ -0,0 +1,7 @@ +#pragma once + +#include "nvim/vterm/vterm_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/state.h.generated.h" +#endif diff --git a/src/nvim/vterm/vterm.c b/src/nvim/vterm/vterm.c new file mode 100644 index 0000000000..76d5dc3808 --- /dev/null +++ b/src/nvim/vterm/vterm.c @@ -0,0 +1,335 @@ +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "auto/config.h" +#include "nvim/memory.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/state.h" +#include "nvim/vterm/vterm.h" +#include "nvim/vterm/vterm_internal_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/vterm.c.generated.h" +#endif + +// ***************** +// * API functions * +// ***************** + +static void *default_malloc(size_t size, void *allocdata) +{ + void *ptr = xmalloc(size); + if (ptr) { + memset(ptr, 0, size); + } + return ptr; +} + +static void default_free(void *ptr, void *allocdata) +{ + xfree(ptr); +} + +static VTermAllocatorFunctions default_allocator = { + .malloc = &default_malloc, + .free = &default_free, +}; + +/// Convenient shortcut for default cases +VTerm *vterm_new(int rows, int cols) +{ + return vterm_build(&(const struct VTermBuilder){ + .rows = rows, + .cols = cols, + }); +} + +// A handy macro for defaulting values out of builder fields +#define DEFAULT(v, def) ((v) ? (v) : (def)) + +VTerm *vterm_build(const struct VTermBuilder *builder) +{ + const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); + + // Need to bootstrap using the allocator function directly + VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); + + vt->allocator = allocator; + vt->allocdata = builder->allocdata; + + vt->rows = builder->rows; + vt->cols = builder->cols; + + vt->parser.state = NORMAL; + + vt->parser.callbacks = NULL; + vt->parser.cbdata = NULL; + + vt->parser.emit_nul = false; + + vt->outfunc = NULL; + vt->outdata = NULL; + + vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); + vt->outbuffer_cur = 0; + vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); + + vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); + vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); + + return vt; +} + +void vterm_free(VTerm *vt) +{ + if (vt->screen) { + vterm_screen_free(vt->screen); + } + + if (vt->state) { + vterm_state_free(vt->state); + } + + vterm_allocator_free(vt, vt->outbuffer); + vterm_allocator_free(vt, vt->tmpbuffer); + + vterm_allocator_free(vt, vt); +} + +void *vterm_allocator_malloc(VTerm *vt, size_t size) +{ + return (*vt->allocator->malloc)(size, vt->allocdata); +} + +void vterm_allocator_free(VTerm *vt, void *ptr) +{ + (*vt->allocator->free)(ptr, vt->allocdata); +} + +void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) +{ + if (rowsp) { + *rowsp = vt->rows; + } + if (colsp) { + *colsp = vt->cols; + } +} + +void vterm_set_size(VTerm *vt, int rows, int cols) +{ + if (rows < 1 || cols < 1) { + return; + } + + vt->rows = rows; + vt->cols = cols; + + if (vt->parser.callbacks && vt->parser.callbacks->resize) { + (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); + } +} + +void vterm_set_utf8(VTerm *vt, int is_utf8) +{ + vt->mode.utf8 = (unsigned)is_utf8; +} + +void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) +{ + vt->outfunc = func; + vt->outdata = user; +} + +void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) +{ + if (vt->outfunc) { + (vt->outfunc)(bytes, len, vt->outdata); + return; + } + + if (len > vt->outbuffer_len - vt->outbuffer_cur) { + return; + } + + memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); + vt->outbuffer_cur += len; +} + +void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) + FUNC_ATTR_PRINTF(2, 3) +{ + va_list args; + va_start(args, format); + size_t len = (size_t)vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, format, args); + vterm_push_output_bytes(vt, vt->tmpbuffer, len); + va_end(args); +} + +void vterm_push_output_sprintf_ctrl(VTerm *vt, uint8_t ctrl, const char *fmt, ...) + FUNC_ATTR_PRINTF(3, 4) +{ + size_t cur; + + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + } else { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + } + + if (cur >= vt->tmpbuffer_len) { + return; + } + + va_list args; + va_start(args, fmt); + cur += (size_t)vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) { + return; + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +void vterm_push_output_sprintf_str(VTerm *vt, uint8_t ctrl, bool term, const char *fmt, ...) + FUNC_ATTR_PRINTF(4, 5) +{ + size_t cur = 0; + + if (ctrl) { + if (ctrl >= 0x80 && !vt->mode.ctrl8bit) { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, ESC_S "%c", ctrl - 0x40); + } else { + cur = (size_t)snprintf(vt->tmpbuffer, vt->tmpbuffer_len, "%c", ctrl); + } + + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + va_list args; + va_start(args, fmt); + cur += (size_t)vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, fmt, args); + va_end(args); + + if (cur >= vt->tmpbuffer_len) { + return; + } + + if (term) { + cur += (size_t)snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, + vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST + + if (cur >= vt->tmpbuffer_len) { + return; + } + } + + vterm_push_output_bytes(vt, vt->tmpbuffer, cur); +} + +VTermValueType vterm_get_attr_type(VTermAttr attr) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_UNDERLINE: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_ITALIC: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BLINK: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_REVERSE: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_CONCEAL: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_STRIKE: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_FONT: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_FOREGROUND: + return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_BACKGROUND: + return VTERM_VALUETYPE_COLOR; + case VTERM_ATTR_SMALL: + return VTERM_VALUETYPE_BOOL; + case VTERM_ATTR_BASELINE: + return VTERM_VALUETYPE_INT; + case VTERM_ATTR_URI: + return VTERM_VALUETYPE_INT; + + case VTERM_N_ATTRS: + return 0; + } + return 0; // UNREACHABLE +} + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), void *user) +{ + VTermRect src; + VTermRect dest; + + if (abs(downward) >= rect.end_row - rect.start_row + || abs(rightward) >= rect.end_col - rect.start_col) { + // Scroll more than area; just erase the lot + (*eraserect)(rect, 0, user); + return; + } + + if (rightward >= 0) { + // rect: [XXX................] + // src: [----------------] + // dest: [----------------] + dest.start_col = rect.start_col; + dest.end_col = rect.end_col - rightward; + src.start_col = rect.start_col + rightward; + src.end_col = rect.end_col; + } else { + // rect: [................XXX] + // src: [----------------] + // dest: [----------------] + int leftward = -rightward; + dest.start_col = rect.start_col + leftward; + dest.end_col = rect.end_col; + src.start_col = rect.start_col; + src.end_col = rect.end_col - leftward; + } + + if (downward >= 0) { + dest.start_row = rect.start_row; + dest.end_row = rect.end_row - downward; + src.start_row = rect.start_row + downward; + src.end_row = rect.end_row; + } else { + int upward = -downward; + dest.start_row = rect.start_row + upward; + dest.end_row = rect.end_row; + src.start_row = rect.start_row; + src.end_row = rect.end_row - upward; + } + + if (moverect) { + (*moverect)(dest, src, user); + } + + if (downward > 0) { + rect.start_row = rect.end_row - downward; + } else if (downward < 0) { + rect.end_row = rect.start_row - downward; + } + + if (rightward > 0) { + rect.start_col = rect.end_col - rightward; + } else if (rightward < 0) { + rect.end_col = rect.start_col - rightward; + } + + (*eraserect)(rect, 0, user); +} diff --git a/src/nvim/vterm/vterm.h b/src/nvim/vterm/vterm.h new file mode 100644 index 0000000000..e66f40425a --- /dev/null +++ b/src/nvim/vterm/vterm.h @@ -0,0 +1,161 @@ +#pragma once + +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +#include "nvim/macros_defs.h" +#include "nvim/types_defs.h" +#include "nvim/vterm/vterm_defs.h" +#include "nvim/vterm/vterm_keycodes_defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "vterm/vterm.h.generated.h" +#endif + +#define VTERM_VERSION_MAJOR 0 +#define VTERM_VERSION_MINOR 3 + +// move a rect +static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) +{ + rect->start_row += row_delta; rect->end_row += row_delta; + rect->start_col += col_delta; rect->end_col += col_delta; +} + +// Bit-field describing the content of the tagged union `VTermColor`. +typedef enum { + // If the lower bit of `type` is not set, the colour is 24-bit RGB. + VTERM_COLOR_RGB = 0x00, + + // The colour is an index into a palette of 256 colours. + VTERM_COLOR_INDEXED = 0x01, + + // Mask that can be used to extract the RGB/Indexed bit. + VTERM_COLOR_TYPE_MASK = 0x01, + + // If set, indicates that this colour should be the default foreground color, i.e. there was no + // SGR request for another colour. When rendering this colour it is possible to ignore "idx" and + // just use a colour that is not in the palette. + VTERM_COLOR_DEFAULT_FG = 0x02, + + // If set, indicates that this colour should be the default background color, i.e. there was no + // SGR request for another colour. A common option when rendering this colour is to not render a + // background at all, for example by rendering the window transparently at this spot. + VTERM_COLOR_DEFAULT_BG = 0x04, + + // Mask that can be used to extract the default foreground/background bit. + VTERM_COLOR_DEFAULT_MASK = 0x06, +} VTermColorType; + +// Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the given VTermColor +// instance is an indexed colour. +#define VTERM_COLOR_IS_INDEXED(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) + +// Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that the given VTermColor +// instance is an rgb colour. +#define VTERM_COLOR_IS_RGB(col) \ + (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) + +// Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating that the given +// VTermColor instance corresponds to the default foreground color. +#define VTERM_COLOR_IS_DEFAULT_FG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) + +// Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating that the given +// VTermColor instance corresponds to the default background color. +#define VTERM_COLOR_IS_DEFAULT_BG(col) \ + (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) + +// Constructs a new VTermColor instance representing the given RGB values. +static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, uint8_t blue) +{ + col->type = VTERM_COLOR_RGB; + col->rgb.red = red; + col->rgb.green = green; + col->rgb.blue = blue; +} + +// Construct a new VTermColor instance representing an indexed color with the given index. +static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) +{ + col->type = VTERM_COLOR_INDEXED; + col->indexed.idx = idx; +} + +// ------------ +// Parser layer +// ------------ + +/// Flag to indicate non-final subparameters in a single CSI parameter. +/// Consider +/// CSI 1;2:3:4;5a +/// 1 4 and 5 are final. +/// 2 and 3 are non-final and will have this bit set +/// +/// Don't confuse this with the final byte of the CSI escape; 'a' in this case. +#define CSI_ARG_FLAG_MORE (1U << 31) +#define CSI_ARG_MASK (~(1U << 31)) + +#define CSI_ARG_HAS_MORE(a) ((a)& CSI_ARG_FLAG_MORE) +#define CSI_ARG(a) ((a)& CSI_ARG_MASK) + +// Can't use -1 to indicate a missing argument; use this instead +#define CSI_ARG_MISSING ((1UL<<31) - 1) + +#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) +#define CSI_ARG_OR(a, def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) +#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) + +enum { + VTERM_UNDERLINE_OFF, + VTERM_UNDERLINE_SINGLE, + VTERM_UNDERLINE_DOUBLE, + VTERM_UNDERLINE_CURLY, +}; + +enum { + VTERM_BASELINE_NORMAL, + VTERM_BASELINE_RAISE, + VTERM_BASELINE_LOWER, +}; + +// Back-compat alias for the brief time it was in 0.3-RC1 +#define vterm_screen_set_reflow vterm_screen_enable_reflow + +void vterm_scroll_rect(VTermRect rect, int downward, int rightward, + int (*moverect)(VTermRect src, VTermRect dest, void *user), + int (*eraserect)(VTermRect rect, int selective, void *user), void *user); + +struct VTermScreen { + VTerm *vt; + VTermState *state; + + const VTermScreenCallbacks *callbacks; + void *cbdata; + + VTermDamageSize damage_merge; + // start_row == -1 => no damage + VTermRect damaged; + VTermRect pending_scrollrect; + int pending_scroll_downward, pending_scroll_rightward; + + int rows; + int cols; + + unsigned global_reverse : 1; + unsigned reflow : 1; + + // Primary and Altscreen. buffers[1] is lazily allocated as needed + ScreenCell *buffers[2]; + + // buffer will == buffers[0] or buffers[1], depending on altscreen + ScreenCell *buffer; + + // buffer for a single screen row used in scrollback storage callbacks + VTermScreenCell *sb_buffer; + + ScreenPen pen; +}; diff --git a/src/nvim/vterm/vterm_defs.h b/src/nvim/vterm/vterm_defs.h new file mode 100644 index 0000000000..d0a8ba8814 --- /dev/null +++ b/src/nvim/vterm/vterm_defs.h @@ -0,0 +1,319 @@ +#pragma once +#include <stdbool.h> +#include <stddef.h> +#include <stdint.h> + +#include "nvim/types_defs.h" + +typedef struct VTerm VTerm; +typedef struct VTermState VTermState; +typedef struct VTermScreen VTermScreen; + +typedef struct { + int row; + int col; +} VTermPos; + +// some small utility functions; we can just keep these static here + +typedef struct { + int start_row; + int end_row; + int start_col; + int end_col; +} VTermRect; + +// Tagged union storing either an RGB color or an index into a colour palette. In order to convert +// indexed colours to RGB, you may use the vterm_state_convert_color_to_rgb() or +// vterm_screen_convert_color_to_rgb() functions which lookup the RGB colour from the palette +// maintained by a VTermState or VTermScreen instance. +typedef union { + // Tag indicating which union member is actually valid. This variable coincides with the `type` + // member of the `rgb` and the `indexed` struct in memory. Please use the `VTERM_COLOR_IS_*` test + // macros to check whether a particular type flag is set. + uint8_t type; + + // Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. + struct { + // Same as the top-level `type` member stored in VTermColor. + uint8_t type; + + // The actual 8-bit red, green, blue colour values. + uint8_t red, green, blue; + } rgb; + + // If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into the colour palette. + struct { + // Same as the top-level `type` member stored in VTermColor. + uint8_t type; + + // Index into the colour map. + uint8_t idx; + } indexed; +} VTermColor; + +typedef struct { + unsigned bold : 1; + unsigned underline : 2; + unsigned italic : 1; + unsigned blink : 1; + unsigned reverse : 1; + unsigned conceal : 1; + unsigned strike : 1; + unsigned font : 4; // 0 to 9 + unsigned dwl : 1; // On a DECDWL or DECDHL line + unsigned dhl : 2; // On a DECDHL line (1=top 2=bottom) + unsigned small : 1; + unsigned baseline : 2; +} VTermScreenCellAttrs; + +typedef struct { + schar_T schar; + char width; + VTermScreenCellAttrs attrs; + VTermColor fg, bg; + int uri; +} VTermScreenCell; + +typedef enum { + // VTERM_PROP_NONE = 0 + VTERM_PROP_CURSORVISIBLE = 1, // bool + VTERM_PROP_CURSORBLINK, // bool + VTERM_PROP_ALTSCREEN, // bool + VTERM_PROP_TITLE, // string + VTERM_PROP_ICONNAME, // string + VTERM_PROP_REVERSE, // bool + VTERM_PROP_CURSORSHAPE, // number + VTERM_PROP_MOUSE, // number + VTERM_PROP_FOCUSREPORT, // bool + + VTERM_N_PROPS, +} VTermProp; + +typedef struct { + const char *str; + size_t len : 30; + bool initial : 1; + bool final : 1; +} VTermStringFragment; + +typedef union { + int boolean; + int number; + VTermStringFragment string; + VTermColor color; +} VTermValue; + +typedef struct { + int (*damage)(VTermRect rect, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, void *user); + int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); + int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); + int (*sb_clear)(void *user); +} VTermScreenCallbacks; + +typedef struct { + int (*control)(uint8_t control, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); +} VTermStateFallbacks; + +typedef enum { + VTERM_DAMAGE_CELL, // every cell + VTERM_DAMAGE_ROW, // entire rows + VTERM_DAMAGE_SCREEN, // entire screen + VTERM_DAMAGE_SCROLL, // entire screen + scrollrect + + VTERM_N_DAMAGES, +} VTermDamageSize; + +typedef enum { + VTERM_ATTR_BOLD_MASK = 1 << 0, + VTERM_ATTR_UNDERLINE_MASK = 1 << 1, + VTERM_ATTR_ITALIC_MASK = 1 << 2, + VTERM_ATTR_BLINK_MASK = 1 << 3, + VTERM_ATTR_REVERSE_MASK = 1 << 4, + VTERM_ATTR_STRIKE_MASK = 1 << 5, + VTERM_ATTR_FONT_MASK = 1 << 6, + VTERM_ATTR_FOREGROUND_MASK = 1 << 7, + VTERM_ATTR_BACKGROUND_MASK = 1 << 8, + VTERM_ATTR_CONCEAL_MASK = 1 << 9, + VTERM_ATTR_SMALL_MASK = 1 << 10, + VTERM_ATTR_BASELINE_MASK = 1 << 11, + VTERM_ATTR_URI_MASK = 1 << 12, + + VTERM_ALL_ATTRS_MASK = (1 << 13) - 1, +} VTermAttrMask; + +typedef enum { + // VTERM_VALUETYPE_NONE = 0 + VTERM_VALUETYPE_BOOL = 1, + VTERM_VALUETYPE_INT, + VTERM_VALUETYPE_STRING, + VTERM_VALUETYPE_COLOR, + + VTERM_N_VALUETYPES, +} VTermValueType; + +typedef enum { + // VTERM_ATTR_NONE = 0 + VTERM_ATTR_BOLD = 1, // bool: 1, 22 + VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 + VTERM_ATTR_ITALIC, // bool: 3, 23 + VTERM_ATTR_BLINK, // bool: 5, 25 + VTERM_ATTR_REVERSE, // bool: 7, 27 + VTERM_ATTR_CONCEAL, // bool: 8, 28 + VTERM_ATTR_STRIKE, // bool: 9, 29 + VTERM_ATTR_FONT, // number: 10-19 + VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 + VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 + VTERM_ATTR_SMALL, // bool: 73, 74, 75 + VTERM_ATTR_BASELINE, // number: 73, 74, 75 + VTERM_ATTR_URI, // number + + VTERM_N_ATTRS, +} VTermAttr; + +enum { + VTERM_PROP_CURSORSHAPE_BLOCK = 1, + VTERM_PROP_CURSORSHAPE_UNDERLINE, + VTERM_PROP_CURSORSHAPE_BAR_LEFT, + + VTERM_N_PROP_CURSORSHAPES, +}; + +enum { + VTERM_PROP_MOUSE_NONE = 0, + VTERM_PROP_MOUSE_CLICK, + VTERM_PROP_MOUSE_DRAG, + VTERM_PROP_MOUSE_MOVE, + + VTERM_N_PROP_MOUSES, +}; + +typedef enum { + VTERM_SELECTION_CLIPBOARD = (1<<0), + VTERM_SELECTION_PRIMARY = (1<<1), + VTERM_SELECTION_SECONDARY = (1<<2), + VTERM_SELECTION_SELECT = (1<<3), + VTERM_SELECTION_CUT0 = (1<<4), // also CUT1 .. CUT7 by bitshifting +} VTermSelectionMask; + +typedef struct { + schar_T schar; + int width; + unsigned protected_cell:1; // DECSCA-protected against DECSEL/DECSED + unsigned dwl:1; // DECDWL or DECDHL double-width line + unsigned dhl:2; // DECDHL double-height line (1=top 2=bottom) +} VTermGlyphInfo; + +typedef struct { + unsigned doublewidth:1; // DECDWL or DECDHL line + unsigned doubleheight:2; // DECDHL line (1=top 2=bottom) + unsigned continuation:1; // Line is a flow continuation of the previous +} VTermLineInfo; + +// Copies of VTermState fields that the 'resize' callback might have reason to edit. 'resize' +// callback gets total control of these fields and may free-and-reallocate them if required. They +// will be copied back from the struct after the callback has returned. +typedef struct { + VTermPos pos; // current cursor position + VTermLineInfo *lineinfos[2]; // [1] may be NULL +} VTermStateFields; + +typedef struct { + // libvterm relies on this memory to be zeroed out before it is returned by the allocator. + void *(*malloc)(size_t size, void *allocdata); + void (*free)(void *ptr, void *allocdata); +} VTermAllocatorFunctions; + +// Setting output callback will override the buffer logic +typedef void VTermOutputCallback(const char *s, size_t len, void *user); + +struct VTermBuilder { + int ver; // currently unused but reserved for some sort of ABI version flag + + int rows, cols; + + const VTermAllocatorFunctions *allocator; + void *allocdata; + + // Override default sizes for various structures + size_t outbuffer_len; // default: 4096 + size_t tmpbuffer_len; // default: 4096 +}; + +typedef struct { + int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); + int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); + int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); + int (*moverect)(VTermRect dest, VTermRect src, void *user); + int (*erase)(VTermRect rect, int selective, void *user); + int (*initpen)(void *user); + int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); + int (*settermprop)(VTermProp prop, VTermValue *val, void *user); + int (*bell)(void *user); + int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); + int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, + void *user); + int (*sb_clear)(void *user); +} VTermStateCallbacks; + +typedef struct { + int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); + int (*query)(VTermSelectionMask mask, void *user); +} VTermSelectionCallbacks; + +typedef struct { + int (*text)(const char *bytes, size_t len, void *user); + int (*control)(uint8_t control, void *user); + int (*escape)(const char *bytes, size_t len, void *user); + int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, + char command, void *user); + int (*osc)(int command, VTermStringFragment frag, void *user); + int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); + int (*apc)(VTermStringFragment frag, void *user); + int (*pm)(VTermStringFragment frag, void *user); + int (*sos)(VTermStringFragment frag, void *user); + int (*resize)(int rows, int cols, void *user); +} VTermParserCallbacks; + +// State of the pen at some moment in time, also used in a cell +typedef struct { + // After the bitfield + VTermColor fg, bg; + + // Opaque ID that maps to a URI in a set + int uri; + + unsigned bold : 1; + unsigned underline : 2; + unsigned italic : 1; + unsigned blink : 1; + unsigned reverse : 1; + unsigned conceal : 1; + unsigned strike : 1; + unsigned font : 4; // 0 to 9 + unsigned small : 1; + unsigned baseline : 2; + + // Extra state storage that isn't strictly pen-related + unsigned protected_cell : 1; + unsigned dwl : 1; // on a DECDWL or DECDHL line + unsigned dhl : 2; // on a DECDHL line (1=top 2=bottom) +} ScreenPen; + +// Internal representation of a screen cell +typedef struct { + schar_T schar; + ScreenPen pen; +} ScreenCell; diff --git a/src/nvim/vterm/vterm_internal_defs.h b/src/nvim/vterm/vterm_internal_defs.h new file mode 100644 index 0000000000..770f862ce3 --- /dev/null +++ b/src/nvim/vterm/vterm_internal_defs.h @@ -0,0 +1,266 @@ +#pragma once + +#include <stdarg.h> + +#include "nvim/mbyte_defs.h" +#include "nvim/vterm/vterm_defs.h" + +#ifdef DEBUG +# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) +#else +# define DEBUG_LOG(...) +#endif + +#define ESC_S "\x1b" + +#define INTERMED_MAX 16 + +#define CSI_ARGS_MAX 16 +#define CSI_LEADER_MAX 16 + +#define BUFIDX_PRIMARY 0 +#define BUFIDX_ALTSCREEN 1 + +typedef struct VTermEncoding VTermEncoding; + +typedef struct { + VTermEncoding *enc; + + // This size should be increased if required by other stateful encodings + char data[4 * sizeof(uint32_t)]; +} VTermEncodingInstance; + +struct VTermPen { + VTermColor fg; + VTermColor bg; + int uri; + unsigned bold:1; + unsigned underline:2; + unsigned italic:1; + unsigned blink:1; + unsigned reverse:1; + unsigned conceal:1; + unsigned strike:1; + unsigned font:4; // To store 0-9 + unsigned small:1; + unsigned baseline:2; +}; + +struct VTermState { + VTerm *vt; + + const VTermStateCallbacks *callbacks; + void *cbdata; + + const VTermStateFallbacks *fallbacks; + void *fbdata; + + int rows; + int cols; + + // Current cursor position + VTermPos pos; + + int at_phantom; // True if we're on the "81st" phantom column to defer a wraparound + + int scrollregion_top; + int scrollregion_bottom; // -1 means unbounded +#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > \ + -1 ? (state)->scrollregion_bottom : (state)->rows) + int scrollregion_left; +#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) + int scrollregion_right; // -1 means unbounded +#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin \ + && (state)->scrollregion_right > \ + -1 ? (state)->scrollregion_right : (state)->cols) + + // Bitvector of tab stops + uint8_t *tabstops; + + // Primary and Altscreen; lineinfos[1] is lazily allocated as needed + VTermLineInfo *lineinfos[2]; + + // lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen + VTermLineInfo *lineinfo; +#define ROWWIDTH(state, \ + row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) +#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) + + // Mouse state + int mouse_col, mouse_row; + int mouse_buttons; + int mouse_flags; +#define MOUSE_WANT_CLICK 0x01 +#define MOUSE_WANT_DRAG 0x02 +#define MOUSE_WANT_MOVE 0x04 + + enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT, } mouse_protocol; + +// Last glyph output, for Unicode recombining purposes + char grapheme_buf[MAX_SCHAR_SIZE]; + size_t grapheme_len; + uint32_t grapheme_last; // last added UTF-32 char + GraphemeState grapheme_state; + int combine_width; // The width of the glyph above + VTermPos combine_pos; // Position before movement + + struct { + unsigned keypad:1; + unsigned cursor:1; + unsigned autowrap:1; + unsigned insert:1; + unsigned newline:1; + unsigned cursor_visible:1; + unsigned cursor_blink:1; + unsigned cursor_shape:2; + unsigned alt_screen:1; + unsigned origin:1; + unsigned screen:1; + unsigned leftrightmargin:1; + unsigned bracketpaste:1; + unsigned report_focus:1; + } mode; + + VTermEncodingInstance encoding[4], encoding_utf8; + int gl_set, gr_set, gsingle_set; + + struct VTermPen pen; + + VTermColor default_fg; + VTermColor default_bg; + VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only + + int bold_is_highbright; + + unsigned protected_cell : 1; + +// Saved state under DEC mode 1048/1049 + struct { + VTermPos pos; + struct VTermPen pen; + + struct { + unsigned cursor_visible:1; + unsigned cursor_blink:1; + unsigned cursor_shape:2; + } mode; + } saved; + +// Temporary state for DECRQSS parsing + union { + char decrqss[4]; + struct { + uint16_t mask; + enum { + SELECTION_INITIAL, + SELECTION_SELECTED, + SELECTION_QUERY, + SELECTION_SET_INITIAL, + SELECTION_SET, + SELECTION_INVALID, + } state : 8; + uint32_t recvpartial; + uint32_t sendpartial; + } selection; + } tmp; + + struct { + const VTermSelectionCallbacks *callbacks; + void *user; + char *buffer; + size_t buflen; + } selection; +}; + +struct VTerm { + const VTermAllocatorFunctions *allocator; + void *allocdata; + + int rows; + int cols; + + struct { + unsigned utf8:1; + unsigned ctrl8bit:1; + } mode; + + struct { + enum VTermParserState { + NORMAL, + CSI_LEADER, + CSI_ARGS, + CSI_INTERMED, + DCS_COMMAND, + // below here are the "string states" + OSC_COMMAND, + OSC, + DCS_VTERM, + APC, + PM, + SOS, + } state; + + bool in_esc : 1; + + int intermedlen; + char intermed[INTERMED_MAX]; + + union { + struct { + int leaderlen; + char leader[CSI_LEADER_MAX]; + + int argi; + long args[CSI_ARGS_MAX]; + } csi; + struct { + int command; + } osc; + struct { + int commandlen; + char command[CSI_LEADER_MAX]; + } dcs; + } v; + + const VTermParserCallbacks *callbacks; + void *cbdata; + + bool string_initial; + + bool emit_nul; + } parser; + + // len == malloc()ed size; cur == number of valid bytes + + VTermOutputCallback *outfunc; + void *outdata; + + char *outbuffer; + size_t outbuffer_len; + size_t outbuffer_cur; + + char *tmpbuffer; + size_t tmpbuffer_len; + + VTermState *state; + VTermScreen *screen; +}; + +struct VTermEncoding { + void (*init)(VTermEncoding *enc, void *data); + void (*decode)(VTermEncoding *enc, void *data, uint32_t cp[], int *cpi, int cplen, + const char bytes[], size_t *pos, size_t len); +}; + +typedef enum { + ENC_UTF8, + ENC_SINGLE_94, +} VTermEncodingType; + +enum { + C1_SS3 = 0x8f, + C1_DCS = 0x90, + C1_CSI = 0x9b, + C1_ST = 0x9c, + C1_OSC = 0x9d, +}; diff --git a/src/vterm/vterm_keycodes.h b/src/nvim/vterm/vterm_keycodes_defs.h index 661759febd..70db05af54 100644 --- a/src/vterm/vterm_keycodes.h +++ b/src/nvim/vterm/vterm_keycodes_defs.h @@ -1,5 +1,4 @@ -#ifndef __VTERM_INPUT_H__ -#define __VTERM_INPUT_H__ +#pragma once typedef enum { VTERM_MOD_NONE = 0x00, @@ -7,7 +6,7 @@ typedef enum { VTERM_MOD_ALT = 0x02, VTERM_MOD_CTRL = 0x04, - VTERM_ALL_MODS_MASK = 0x07 + VTERM_ALL_MODS_MASK = 0x07, } VTermModifier; typedef enum { @@ -52,10 +51,8 @@ typedef enum { VTERM_KEY_KP_ENTER, VTERM_KEY_KP_EQUAL, - VTERM_KEY_MAX, // Must be last - VTERM_N_KEYS = VTERM_KEY_MAX + VTERM_KEY_MAX, // Must be last + VTERM_N_KEYS = VTERM_KEY_MAX, } VTermKey; -#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0+(n)) - -#endif +#define VTERM_KEY_FUNCTION(n) (VTERM_KEY_FUNCTION_0 + (n)) diff --git a/src/vterm/encoding.c b/src/vterm/encoding.c deleted file mode 100644 index 434ac3f99b..0000000000 --- a/src/vterm/encoding.c +++ /dev/null @@ -1,230 +0,0 @@ -#include "vterm_internal.h" - -#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)++) { - unsigned char c = 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)++] = 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)++) { - unsigned char c = 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)++) { - unsigned char c = 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; - } -} - -#include "encoding/DECdrawing.inc" -#include "encoding/uk.inc" - -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 }, -}; - -/* This ought to be INTERNAL but isn't because it's used by unit testing */ -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/vterm/encoding/DECdrawing.inc b/src/vterm/encoding/DECdrawing.inc deleted file mode 100644 index 627397bcc2..0000000000 --- a/src/vterm/encoding/DECdrawing.inc +++ /dev/null @@ -1,36 +0,0 @@ -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 - } -}; diff --git a/src/vterm/encoding/uk.inc b/src/vterm/encoding/uk.inc deleted file mode 100644 index 5c7700226b..0000000000 --- a/src/vterm/encoding/uk.inc +++ /dev/null @@ -1,6 +0,0 @@ -static const struct StaticTableEncoding encoding_uk = { - { .decode = &decode_table }, - { - [0x23] = 0x00a3, // £ - } -}; diff --git a/src/vterm/keyboard.c b/src/vterm/keyboard.c deleted file mode 100644 index 7e062c8c02..0000000000 --- a/src/vterm/keyboard.c +++ /dev/null @@ -1,225 +0,0 @@ -#include "vterm_internal.h" -#include <stdio.h> - -#include "nvim/tui/termkey/termkey.h" - -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 &= ~VTERM_MOD_SHIFT; - - if(mod == 0) { - // Normal text - ignore just shift - char str[6]; - int seqlen = fill_utf8(c, str); - vterm_push_output_bytes(vt, str, 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 & ~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 }, // NONE - - { KEYCODE_ENTER, '\r' }, // ENTER - { KEYCODE_TAB, '\t' }, // TAB - { KEYCODE_LITERAL, '\x7f' }, // BACKSPACE == ASCII DEL - { KEYCODE_LITERAL, '\x1b' }, // ESCAPE - - { KEYCODE_CSI_CURSOR, 'A' }, // UP - { KEYCODE_CSI_CURSOR, 'B' }, // DOWN - { KEYCODE_CSI_CURSOR, 'D' }, // LEFT - { KEYCODE_CSI_CURSOR, 'C' }, // RIGHT - - { KEYCODE_CSINUM, '~', 2 }, // INS - { KEYCODE_CSINUM, '~', 3 }, // DEL - { KEYCODE_CSI_CURSOR, 'H' }, // HOME - { KEYCODE_CSI_CURSOR, 'F' }, // END - { KEYCODE_CSINUM, '~', 5 }, // PAGEUP - { KEYCODE_CSINUM, '~', 6 }, // PAGEDOWN -}; - -static keycodes_s keycodes_fn[] = { - { KEYCODE_NONE }, // F0 - shouldn't happen - { KEYCODE_SS3, 'P' }, // F1 - { KEYCODE_SS3, 'Q' }, // F2 - { KEYCODE_SS3, 'R' }, // F3 - { KEYCODE_SS3, 'S' }, // 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 = 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/vterm/mouse.c b/src/vterm/mouse.c deleted file mode 100644 index a9d3fe4ca9..0000000000 --- a/src/vterm/mouse.c +++ /dev/null @@ -1,99 +0,0 @@ -#include "vterm_internal.h" - -#include "nvim/tui/termkey/termkey.h" - -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 += fill_utf8((code | modifiers) + 0x20, utf8 + len); - len += fill_utf8(col + 0x21, utf8 + len); - len += 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, 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, mod, state->mouse_col, state->mouse_row); - } - else if(button < 8) { - output_mouse(state, button-4 + 0x40, pressed, mod, state->mouse_col, state->mouse_row); - } -} diff --git a/src/vterm/parser.c b/src/vterm/parser.c deleted file mode 100644 index 84d017a791..0000000000 --- a/src/vterm/parser.c +++ /dev/null @@ -1,408 +0,0 @@ -#include "vterm_internal.h" - -#include <assert.h> -#include <stdio.h> -#include <string.h> - -#undef DEBUG_PARSER - -static bool is_intermed(unsigned char c) -{ - return c >= 0x20 && c <= 0x2f; -} - -static void do_control(VTerm *vt, unsigned char 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 = vt->parser.intermedlen; - strncpy(seq, vt->parser.intermed, len); - 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: - if(vt->parser.callbacks && vt->parser.callbacks->dcs) - (*vt->parser.callbacks->dcs)(vt->parser.v.dcs.command, 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: - case APC: - case PM: - case SOS: - string_start = bytes; - break; - } - -#define ENTER_STATE(st) do { vt->parser.state = st; string_start = NULL; } while(0) -#define ENTER_NORMAL_STATE() ENTER_STATE(NORMAL) - -#define IS_STRING_STATE() (vt->parser.state >= OSC_COMMAND) - - for( ; pos < len; pos++) { - unsigned char c = bytes[pos]; - bool c1_allowed = !vt->mode.utf8; - - if(c == 0x00 || c == 0x7f) { // NUL, DEL - if(IS_STRING_STATE()) { - string_fragment(vt, string_start, bytes + pos - string_start, false); - string_start = bytes + pos + 1; - } - if(vt->parser.emit_nul) - do_control(vt, c); - continue; - } - if(c == 0x18 || c == 0x1a) { // CAN, SUB - vt->parser.in_esc = false; - ENTER_NORMAL_STATE(); - if(vt->parser.emit_nul) - do_control(vt, c); - continue; - } - else if(c == 0x1b) { // ESC - vt->parser.intermedlen = 0; - if(!IS_STRING_STATE()) - vt->parser.state = NORMAL; - vt->parser.in_esc = true; - continue; - } - else if(c == 0x07 && // BEL, can stand for ST in OSC or DCS state - IS_STRING_STATE()) { - // fallthrough - } - else if(c < 0x20) { // other C0 - if(vt->parser.state == SOS) - continue; // All other C0s permitted in SOS - - if(IS_STRING_STATE()) - string_fragment(vt, string_start, bytes + pos - string_start, false); - do_control(vt, c); - if(IS_STRING_STATE()) - string_start = bytes + pos + 1; - continue; - } - // else fallthrough - - size_t string_len = bytes + pos - string_start; - - if(vt->parser.in_esc) { - // Hoist an ESC letter into a C1 if we're not in a string mode - // Always accept ESC \ == ST even in string mode - if(!vt->parser.intermedlen && - c >= 0x40 && c < 0x60 && - ((!IS_STRING_STATE() || c == 0x5c))) { - c += 0x40; - c1_allowed = true; - if(string_len) { - assert(string_len > 0); - string_len -= 1; - } - vt->parser.in_esc = false; - } - else { - string_start = NULL; - vt->parser.state = NORMAL; - } - } - - switch(vt->parser.state) { - case CSI_LEADER: - /* Extract leader bytes 0x3c to 0x3f */ - if(c >= 0x3c && c <= 0x3f) { - if(vt->parser.v.csi.leaderlen < CSI_LEADER_MAX-1) - vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen++] = c; - break; - } - - /* else fallthrough */ - vt->parser.v.csi.leader[vt->parser.v.csi.leaderlen] = 0; - - vt->parser.v.csi.argi = 0; - vt->parser.v.csi.args[0] = CSI_ARG_MISSING; - vt->parser.state = CSI_ARGS; - - /* fallthrough */ - case CSI_ARGS: - /* Numerical value of argument */ - if(c >= '0' && c <= '9') { - if(vt->parser.v.csi.args[vt->parser.v.csi.argi] == CSI_ARG_MISSING) - vt->parser.v.csi.args[vt->parser.v.csi.argi] = 0; - vt->parser.v.csi.args[vt->parser.v.csi.argi] *= 10; - vt->parser.v.csi.args[vt->parser.v.csi.argi] += c - '0'; - break; - } - if(c == ':') { - vt->parser.v.csi.args[vt->parser.v.csi.argi] |= CSI_ARG_FLAG_MORE; - c = ';'; - } - if(c == ';') { - vt->parser.v.csi.argi++; - vt->parser.v.csi.args[vt->parser.v.csi.argi] = CSI_ARG_MISSING; - break; - } - - /* else fallthrough */ - vt->parser.v.csi.argi++; - vt->parser.intermedlen = 0; - vt->parser.state = CSI_INTERMED; - case CSI_INTERMED: - if(is_intermed(c)) { - if(vt->parser.intermedlen < INTERMED_MAX-1) - vt->parser.intermed[vt->parser.intermedlen++] = c; - break; - } - else if(c == 0x1b) { - /* ESC in CSI cancels */ - } - else if(c >= 0x40 && c <= 0x7e) { - vt->parser.intermed[vt->parser.intermedlen] = 0; - do_csi(vt, c); - } - /* else was invalid CSI */ - - ENTER_NORMAL_STATE(); - break; - - case OSC_COMMAND: - /* Numerical value of command */ - if(c >= '0' && c <= '9') { - if(vt->parser.v.osc.command == -1) - vt->parser.v.osc.command = 0; - else - vt->parser.v.osc.command *= 10; - vt->parser.v.osc.command += c - '0'; - break; - } - if(c == ';') { - vt->parser.state = OSC; - string_start = bytes + pos + 1; - break; - } - - /* else fallthrough */ - string_start = bytes + pos; - string_len = 0; - vt->parser.state = OSC; - goto string_state; - - case DCS_COMMAND: - if(vt->parser.v.dcs.commandlen < CSI_LEADER_MAX) - vt->parser.v.dcs.command[vt->parser.v.dcs.commandlen++] = c; - - if(c >= 0x40 && c<= 0x7e) { - string_start = bytes + pos + 1; - vt->parser.state = DCS; - } - break; - -string_state: - case OSC: - case DCS: - case APC: - case PM: - case SOS: - if(c == 0x07 || (c1_allowed && c == 0x9c)) { - string_fragment(vt, string_start, string_len, true); - ENTER_NORMAL_STATE(); - } - break; - - case NORMAL: - if(vt->parser.in_esc) { - if(is_intermed(c)) { - if(vt->parser.intermedlen < INTERMED_MAX-1) - vt->parser.intermed[vt->parser.intermedlen++] = c; - } - else if(c >= 0x30 && c < 0x7f) { - do_escape(vt, c); - vt->parser.in_esc = 0; - ENTER_NORMAL_STATE(); - } - else { - DEBUG_LOG("TODO: Unhandled byte %02x in Escape\n", c); - } - break; - } - if(c1_allowed && c >= 0x80 && c < 0xa0) { - switch(c) { - case 0x90: // DCS - vt->parser.string_initial = true; - vt->parser.v.dcs.commandlen = 0; - ENTER_STATE(DCS_COMMAND); - break; - case 0x98: // SOS - vt->parser.string_initial = true; - ENTER_STATE(SOS); - string_start = bytes + pos + 1; - string_len = 0; - break; - case 0x9b: // CSI - vt->parser.v.csi.leaderlen = 0; - ENTER_STATE(CSI_LEADER); - break; - case 0x9d: // OSC - vt->parser.v.osc.command = -1; - vt->parser.string_initial = true; - string_start = bytes + pos + 1; - ENTER_STATE(OSC_COMMAND); - break; - case 0x9e: // PM - vt->parser.string_initial = true; - ENTER_STATE(PM); - string_start = bytes + pos + 1; - string_len = 0; - break; - case 0x9f: // APC - vt->parser.string_initial = true; - ENTER_STATE(APC); - string_start = bytes + pos + 1; - string_len = 0; - break; - default: - do_control(vt, c); - break; - } - } - else { - size_t eaten = 0; - if(vt->parser.callbacks && vt->parser.callbacks->text) - eaten = (*vt->parser.callbacks->text)(bytes + pos, len - pos, vt->parser.cbdata); - - if(!eaten) { - DEBUG_LOG("libvterm: Text callback did not consume any input\n"); - /* force it to make progress */ - eaten = 1; - } - - pos += (eaten - 1); // we'll ++ it again in a moment - } - break; - } - } - - if(string_start) { - size_t string_len = bytes + pos - string_start; - if (string_len > 0) { - if(vt->parser.in_esc) { - string_len -= 1; - } - string_fragment(vt, string_start, string_len, false); - } - } - - return len; -} - -void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user) -{ - vt->parser.callbacks = callbacks; - vt->parser.cbdata = user; -} - -void *vterm_parser_get_cbdata(VTerm *vt) -{ - return vt->parser.cbdata; -} - -void vterm_parser_set_emit_nul(VTerm *vt, bool emit) -{ - vt->parser.emit_nul = emit; -} diff --git a/src/vterm/pen.c b/src/vterm/pen.c deleted file mode 100644 index 1876eb9881..0000000000 --- a/src/vterm/pen.c +++ /dev/null @@ -1,678 +0,0 @@ -#include "vterm_internal.h" - -#include <stdio.h> - -/** - * Structure used to store RGB triples without the additional metadata stored in - * VTermColor. - */ -typedef struct { - uint8_t red, green, blue; -} VTermRGB; - -static const VTermRGB ansi_colors[] = { - /* R G B */ - { 0, 0, 0 }, // black - { 224, 0, 0 }, // red - { 0, 224, 0 }, // green - { 224, 224, 0 }, // yellow - { 0, 0, 224 }, // blue - { 224, 0, 224 }, // magenta - { 0, 224, 224 }, // cyan - { 224, 224, 224 }, // white == light grey - - // high intensity - { 128, 128, 128 }, // black - { 255, 64, 64 }, // red - { 64, 255, 64 }, // green - { 255, 255, 64 }, // yellow - { 64, 64, 255 }, // blue - { 255, 64, 255 }, // magenta - { 64, 255, 255 }, // cyan - { 255, 255, 255 }, // white for real -}; - -static int ramp6[] = { - 0x00, 0x33, 0x66, 0x99, 0xCC, 0xFF, -}; - -static int ramp24[] = { - 0x00, 0x0B, 0x16, 0x21, 0x2C, 0x37, 0x42, 0x4D, 0x58, 0x63, 0x6E, 0x79, - 0x85, 0x90, 0x9B, 0xA6, 0xB1, 0xBC, 0xC7, 0xD2, 0xDD, 0xE8, 0xF3, 0xFF, -}; - -static void lookup_default_colour_ansi(long idx, VTermColor *col) -{ - if (idx >= 0 && idx < 16) { - vterm_color_rgb( - col, - ansi_colors[idx].red, ansi_colors[idx].green, ansi_colors[idx].blue); - } -} - -static bool lookup_colour_ansi(const VTermState *state, long index, VTermColor *col) -{ - if(index >= 0 && index < 16) { - *col = state->colors[index]; - return true; - } - - return false; -} - -static bool lookup_colour_palette(const VTermState *state, long index, VTermColor *col) -{ - if(index >= 0 && index < 16) { - // Normal 8 colours or high intensity - parse as palette 0 - return lookup_colour_ansi(state, index, col); - } - else if(index >= 16 && index < 232) { - // 216-colour cube - index -= 16; - - vterm_color_rgb(col, ramp6[index/6/6 % 6], - ramp6[index/6 % 6], - ramp6[index % 6]); - - return true; - } - else if(index >= 232 && index < 256) { - // 24 greyscales - index -= 232; - - vterm_color_rgb(col, ramp24[index], ramp24[index], ramp24[index]); - - return true; - } - - return false; -} - -static int lookup_colour(const VTermState *state, int palette, const long args[], int argcount, VTermColor *col) -{ - switch(palette) { - case 2: // RGB mode - 3 args contain colour values directly - if(argcount < 3) - return argcount; - - vterm_color_rgb(col, CSI_ARG(args[0]), CSI_ARG(args[1]), CSI_ARG(args[2])); - - return 3; - - case 5: // XTerm 256-colour mode - if (!argcount || CSI_ARG_IS_MISSING(args[0])) { - return argcount ? 1 : 0; - } - - vterm_color_indexed(col, args[0]); - - return argcount ? 1 : 0; - - default: - DEBUG_LOG("Unrecognised colour palette %d\n", palette); - return 0; - } -} - -// Some conveniences - -static void setpenattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) -{ -#ifdef DEBUG - if(type != vterm_get_attr_type(attr)) { - DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", - attr, vterm_get_attr_type(attr), type); - return; - } -#endif - if(state->callbacks && state->callbacks->setpenattr) - (*state->callbacks->setpenattr)(attr, val, state->cbdata); -} - -static void setpenattr_bool(VTermState *state, VTermAttr attr, int boolean) -{ - VTermValue val = { .boolean = boolean }; - setpenattr(state, attr, VTERM_VALUETYPE_BOOL, &val); -} - -static void setpenattr_int(VTermState *state, VTermAttr attr, int number) -{ - VTermValue val = { .number = number }; - setpenattr(state, attr, VTERM_VALUETYPE_INT, &val); -} - -static void setpenattr_col(VTermState *state, VTermAttr attr, VTermColor color) -{ - VTermValue val = { .color = color }; - setpenattr(state, attr, VTERM_VALUETYPE_COLOR, &val); -} - -static void set_pen_col_ansi(VTermState *state, VTermAttr attr, long col) -{ - VTermColor *colp = (attr == VTERM_ATTR_BACKGROUND) ? &state->pen.bg : &state->pen.fg; - - vterm_color_indexed(colp, col); - - setpenattr_col(state, attr, *colp); -} - -INTERNAL void vterm_state_newpen(VTermState *state) -{ - // 90% grey so that pure white is brighter - vterm_color_rgb(&state->default_fg, 240, 240, 240); - vterm_color_rgb(&state->default_bg, 0, 0, 0); - vterm_state_set_default_colors(state, &state->default_fg, &state->default_bg); - - for(int col = 0; col < 16; col++) - lookup_default_colour_ansi(col, &state->colors[col]); -} - -INTERNAL void vterm_state_resetpen(VTermState *state) -{ - state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0); - state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0); - state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); - state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0); - state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); - state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); - state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); - state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0); - state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0); - state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0); - - state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg); - state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg); - - state->pen.uri = 0; setpenattr_int(state, VTERM_ATTR_URI, 0); -} - -INTERNAL void vterm_state_savepen(VTermState *state, int save) -{ - if(save) { - state->saved.pen = state->pen; - } - else { - state->pen = state->saved.pen; - - setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold); - setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline); - setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic); - setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink); - setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse); - setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal); - setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike); - setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font); - setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); - setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); - - setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg); - setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg); - - setpenattr_int( state, VTERM_ATTR_URI, state->pen.uri); - } -} - -int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) -{ - /* First make sure that the two colours are of the same type (RGB/Indexed) */ - if (a->type != b->type) { - return false; - } - - /* Depending on the type inspect the corresponding members */ - if (VTERM_COLOR_IS_INDEXED(a)) { - return a->indexed.idx == b->indexed.idx; - } - else if (VTERM_COLOR_IS_RGB(a)) { - return (a->rgb.red == b->rgb.red) - && (a->rgb.green == b->rgb.green) - && (a->rgb.blue == b->rgb.blue); - } - - return 0; -} - -void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg) -{ - *default_fg = state->default_fg; - *default_bg = state->default_bg; -} - -void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col) -{ - lookup_colour_palette(state, index, col); -} - -void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg) -{ - if(default_fg) { - state->default_fg = *default_fg; - state->default_fg.type = (state->default_fg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_FG; - } - - if(default_bg) { - state->default_bg = *default_bg; - state->default_bg.type = (state->default_bg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_BG; - } -} - -void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col) -{ - if(index >= 0 && index < 16) - state->colors[index] = *col; -} - -void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col) -{ - if (VTERM_COLOR_IS_INDEXED(col)) { /* Convert indexed colors to RGB */ - lookup_colour_palette(state, col->indexed.idx, col); - } - col->type &= VTERM_COLOR_TYPE_MASK; /* Reset any metadata but the type */ -} - -void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) -{ - state->bold_is_highbright = bold_is_highbright; -} - -INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argcount) -{ - // SGR - ECMA-48 8.3.117 - - int argi = 0; - int value; - - while(argi < argcount) { - // This logic is easier to do 'done' backwards; set it true, and make it - // false again in the 'default' case - int done = 1; - - long arg; - switch(arg = CSI_ARG(args[argi])) { - case CSI_ARG_MISSING: - case 0: // Reset - vterm_state_resetpen(state); - break; - - case 1: { // Bold on - const VTermColor *fg = &state->pen.fg; - state->pen.bold = 1; - setpenattr_bool(state, VTERM_ATTR_BOLD, 1); - if(!VTERM_COLOR_IS_DEFAULT_FG(fg) && VTERM_COLOR_IS_INDEXED(fg) && fg->indexed.idx < 8 && state->bold_is_highbright) - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, fg->indexed.idx + (state->pen.bold ? 8 : 0)); - break; - } - - case 3: // Italic on - state->pen.italic = 1; - setpenattr_bool(state, VTERM_ATTR_ITALIC, 1); - break; - - case 4: // Underline - state->pen.underline = VTERM_UNDERLINE_SINGLE; - if(CSI_ARG_HAS_MORE(args[argi])) { - argi++; - switch(CSI_ARG(args[argi])) { - case 0: - state->pen.underline = 0; - break; - case 1: - state->pen.underline = VTERM_UNDERLINE_SINGLE; - break; - case 2: - state->pen.underline = VTERM_UNDERLINE_DOUBLE; - break; - case 3: - state->pen.underline = VTERM_UNDERLINE_CURLY; - break; - } - } - setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); - break; - - case 5: // Blink - state->pen.blink = 1; - setpenattr_bool(state, VTERM_ATTR_BLINK, 1); - break; - - case 7: // Reverse on - state->pen.reverse = 1; - setpenattr_bool(state, VTERM_ATTR_REVERSE, 1); - break; - - case 8: // Conceal on - state->pen.conceal = 1; - setpenattr_bool(state, VTERM_ATTR_CONCEAL, 1); - break; - - case 9: // Strikethrough on - state->pen.strike = 1; - setpenattr_bool(state, VTERM_ATTR_STRIKE, 1); - break; - - case 10: case 11: case 12: case 13: case 14: - case 15: case 16: case 17: case 18: case 19: // Select font - state->pen.font = CSI_ARG(args[argi]) - 10; - setpenattr_int(state, VTERM_ATTR_FONT, state->pen.font); - break; - - case 21: // Underline double - state->pen.underline = VTERM_UNDERLINE_DOUBLE; - setpenattr_int(state, VTERM_ATTR_UNDERLINE, state->pen.underline); - break; - - case 22: // Bold off - state->pen.bold = 0; - setpenattr_bool(state, VTERM_ATTR_BOLD, 0); - break; - - case 23: // Italic and Gothic (currently unsupported) off - state->pen.italic = 0; - setpenattr_bool(state, VTERM_ATTR_ITALIC, 0); - break; - - case 24: // Underline off - state->pen.underline = 0; - setpenattr_int(state, VTERM_ATTR_UNDERLINE, 0); - break; - - case 25: // Blink off - state->pen.blink = 0; - setpenattr_bool(state, VTERM_ATTR_BLINK, 0); - break; - - case 27: // Reverse off - state->pen.reverse = 0; - setpenattr_bool(state, VTERM_ATTR_REVERSE, 0); - break; - - case 28: // Conceal off (Reveal) - state->pen.conceal = 0; - setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0); - break; - - case 29: // Strikethrough off - state->pen.strike = 0; - setpenattr_bool(state, VTERM_ATTR_STRIKE, 0); - break; - - case 30: case 31: case 32: case 33: - case 34: case 35: case 36: case 37: // Foreground colour palette - value = CSI_ARG(args[argi]) - 30; - if(state->pen.bold && state->bold_is_highbright) - value += 8; - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); - break; - - case 38: // Foreground colour alternative palette - if(argcount - argi < 1) - return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.fg); - setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); - break; - - case 39: // Foreground colour default - state->pen.fg = state->default_fg; - setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->pen.fg); - break; - - case 40: case 41: case 42: case 43: - case 44: case 45: case 46: case 47: // Background colour palette - value = CSI_ARG(args[argi]) - 40; - set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); - break; - - case 48: // Background colour alternative palette - if(argcount - argi < 1) - return; - argi += 1 + lookup_colour(state, CSI_ARG(args[argi+1]), args+argi+2, argcount-argi-2, &state->pen.bg); - setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); - break; - - case 49: // Default background - state->pen.bg = state->default_bg; - setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg); - break; - - case 73: // Superscript - case 74: // Subscript - case 75: // Superscript/subscript off - state->pen.small = (arg != 75); - state->pen.baseline = - (arg == 73) ? VTERM_BASELINE_RAISE : - (arg == 74) ? VTERM_BASELINE_LOWER : - VTERM_BASELINE_NORMAL; - setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small); - setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline); - break; - - case 90: case 91: case 92: case 93: - case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette - value = CSI_ARG(args[argi]) - 90 + 8; - set_pen_col_ansi(state, VTERM_ATTR_FOREGROUND, value); - break; - - case 100: case 101: case 102: case 103: - case 104: case 105: case 106: case 107: // Background colour high-intensity palette - value = CSI_ARG(args[argi]) - 100 + 8; - set_pen_col_ansi(state, VTERM_ATTR_BACKGROUND, value); - break; - - default: - done = 0; - break; - } - - if(!done) { - DEBUG_LOG("libvterm: Unhandled CSI SGR %ld\n", arg); - } - - while(CSI_ARG_HAS_MORE(args[argi++])); - } -} - -static int vterm_state_getpen_color(const VTermColor *col, int argi, long args[], int fg) -{ - /* Do nothing if the given color is the default color */ - if (( fg && VTERM_COLOR_IS_DEFAULT_FG(col)) || - (!fg && VTERM_COLOR_IS_DEFAULT_BG(col))) { - return argi; - } - - /* Decide whether to send an indexed color or an RGB color */ - if (VTERM_COLOR_IS_INDEXED(col)) { - const uint8_t idx = col->indexed.idx; - if (idx < 8) { - args[argi++] = (idx + (fg ? 30 : 40)); - } - else if (idx < 16) { - args[argi++] = (idx - 8 + (fg ? 90 : 100)); - } - else { - args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); - args[argi++] = CSI_ARG_FLAG_MORE | 5; - args[argi++] = idx; - } - } - else if (VTERM_COLOR_IS_RGB(col)) { - args[argi++] = CSI_ARG_FLAG_MORE | (fg ? 38 : 48); - args[argi++] = CSI_ARG_FLAG_MORE | 2; - args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.red; - args[argi++] = CSI_ARG_FLAG_MORE | col->rgb.green; - args[argi++] = col->rgb.blue; - } - return argi; -} - -INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount) -{ - int argi = 0; - - if(state->pen.bold) - args[argi++] = 1; - - if(state->pen.italic) - args[argi++] = 3; - - if(state->pen.underline == VTERM_UNDERLINE_SINGLE) - args[argi++] = 4; - if(state->pen.underline == VTERM_UNDERLINE_CURLY) - args[argi++] = 4 | CSI_ARG_FLAG_MORE, args[argi++] = 3; - - if(state->pen.blink) - args[argi++] = 5; - - if(state->pen.reverse) - args[argi++] = 7; - - if(state->pen.conceal) - args[argi++] = 8; - - if(state->pen.strike) - args[argi++] = 9; - - if(state->pen.font) - args[argi++] = 10 + state->pen.font; - - if(state->pen.underline == VTERM_UNDERLINE_DOUBLE) - args[argi++] = 21; - - argi = vterm_state_getpen_color(&state->pen.fg, argi, args, true); - - argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false); - - if(state->pen.small) { - if(state->pen.baseline == VTERM_BASELINE_RAISE) - args[argi++] = 73; - else if(state->pen.baseline == VTERM_BASELINE_LOWER) - args[argi++] = 74; - } - - return argi; -} - -int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) -{ - switch(attr) { - case VTERM_ATTR_BOLD: - val->boolean = state->pen.bold; - return 1; - - case VTERM_ATTR_UNDERLINE: - val->number = state->pen.underline; - return 1; - - case VTERM_ATTR_ITALIC: - val->boolean = state->pen.italic; - return 1; - - case VTERM_ATTR_BLINK: - val->boolean = state->pen.blink; - return 1; - - case VTERM_ATTR_REVERSE: - val->boolean = state->pen.reverse; - return 1; - - case VTERM_ATTR_CONCEAL: - val->boolean = state->pen.conceal; - return 1; - - case VTERM_ATTR_STRIKE: - val->boolean = state->pen.strike; - return 1; - - case VTERM_ATTR_FONT: - val->number = state->pen.font; - return 1; - - case VTERM_ATTR_FOREGROUND: - val->color = state->pen.fg; - return 1; - - case VTERM_ATTR_BACKGROUND: - val->color = state->pen.bg; - return 1; - - case VTERM_ATTR_SMALL: - val->boolean = state->pen.small; - return 1; - - case VTERM_ATTR_BASELINE: - val->number = state->pen.baseline; - return 1; - - case VTERM_ATTR_URI: - val->number = state->pen.uri; - return 1; - - case VTERM_N_ATTRS: - return 0; - } - - return 0; -} - -int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val) -{ - if (!val) { - return 0; - } - - if(type != vterm_get_attr_type(attr)) { - DEBUG_LOG("Cannot set attr %d as it has type %d, not type %d\n", - attr, vterm_get_attr_type(attr), type); - return 0; - } - - switch (attr) { - case VTERM_ATTR_BOLD: - state->pen.bold = val->boolean; - break; - case VTERM_ATTR_UNDERLINE: - state->pen.underline = val->number; - break; - case VTERM_ATTR_ITALIC: - state->pen.italic = val->boolean; - break; - case VTERM_ATTR_BLINK: - state->pen.blink = val->boolean; - break; - case VTERM_ATTR_REVERSE: - state->pen.reverse = val->boolean; - break; - case VTERM_ATTR_CONCEAL: - state->pen.conceal = val->boolean; - break; - case VTERM_ATTR_STRIKE: - state->pen.strike = val->boolean; - break; - case VTERM_ATTR_FONT: - state->pen.font = val->number; - break; - case VTERM_ATTR_FOREGROUND: - state->pen.fg = val->color; - break; - case VTERM_ATTR_BACKGROUND: - state->pen.bg = val->color; - break; - case VTERM_ATTR_SMALL: - state->pen.small = val->boolean; - break; - case VTERM_ATTR_BASELINE: - state->pen.baseline = val->number; - break; - case VTERM_ATTR_URI: - state->pen.uri = val->number; - break; - default: - return 0; - } - - if(state->callbacks && state->callbacks->setpenattr) - (*state->callbacks->setpenattr)(attr, val, state->cbdata); - - return 1; -} diff --git a/src/vterm/rect.h b/src/vterm/rect.h deleted file mode 100644 index 2114f24c1b..0000000000 --- a/src/vterm/rect.h +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Some utility functions on VTermRect structures - */ - -#define STRFrect "(%d,%d-%d,%d)" -#define ARGSrect(r) (r).start_row, (r).start_col, (r).end_row, (r).end_col - -/* Expand dst to contain src as well */ -static void rect_expand(VTermRect *dst, VTermRect *src) -{ - if(dst->start_row > src->start_row) dst->start_row = src->start_row; - if(dst->start_col > src->start_col) dst->start_col = src->start_col; - if(dst->end_row < src->end_row) dst->end_row = src->end_row; - if(dst->end_col < src->end_col) dst->end_col = src->end_col; -} - -/* Clip the dst to ensure it does not step outside of bounds */ -static void rect_clip(VTermRect *dst, VTermRect *bounds) -{ - if(dst->start_row < bounds->start_row) dst->start_row = bounds->start_row; - if(dst->start_col < bounds->start_col) dst->start_col = bounds->start_col; - if(dst->end_row > bounds->end_row) dst->end_row = bounds->end_row; - if(dst->end_col > bounds->end_col) dst->end_col = bounds->end_col; - /* Ensure it doesn't end up negatively-sized */ - if(dst->end_row < dst->start_row) dst->end_row = dst->start_row; - if(dst->end_col < dst->start_col) dst->end_col = dst->start_col; -} - -/* True if the two rectangles are equal */ -static int rect_equal(VTermRect *a, VTermRect *b) -{ - return (a->start_row == b->start_row) && - (a->start_col == b->start_col) && - (a->end_row == b->end_row) && - (a->end_col == b->end_col); -} - -/* True if small is contained entirely within big */ -static int rect_contains(VTermRect *big, VTermRect *small) -{ - if(small->start_row < big->start_row) return 0; - if(small->start_col < big->start_col) return 0; - if(small->end_row > big->end_row) return 0; - if(small->end_col > big->end_col) return 0; - return 1; -} - -/* True if the rectangles overlap at all */ -static int rect_intersects(VTermRect *a, VTermRect *b) -{ - if(a->start_row > b->end_row || b->start_row > a->end_row) - return 0; - if(a->start_col > b->end_col || b->start_col > a->end_col) - return 0; - return 1; -} diff --git a/src/vterm/screen.c b/src/vterm/screen.c deleted file mode 100644 index 7de345ca39..0000000000 --- a/src/vterm/screen.c +++ /dev/null @@ -1,1174 +0,0 @@ -#include "vterm_internal.h" - -#include <stdio.h> -#include <string.h> -#include "nvim/grid.h" -#include "nvim/mbyte.h" -#include "nvim/tui/termkey/termkey.h" - -#include "rect.h" - -#define UNICODE_SPACE 0x20 -#define UNICODE_LINEFEED 0x0a - -#undef DEBUG_REFLOW - -/* State of the pen at some moment in time, also used in a cell */ -typedef struct -{ - /* After the bitfield */ - VTermColor fg, bg; - - /* Opaque ID that maps to a URI in a set */ - int uri; - - unsigned int bold : 1; - unsigned int underline : 2; - unsigned int italic : 1; - unsigned int blink : 1; - unsigned int reverse : 1; - unsigned int conceal : 1; - unsigned int strike : 1; - unsigned int font : 4; /* 0 to 9 */ - unsigned int small : 1; - unsigned int baseline : 2; - - /* Extra state storage that isn't strictly pen-related */ - unsigned int protected_cell : 1; - unsigned int dwl : 1; /* on a DECDWL or DECDHL line */ - unsigned int dhl : 2; /* on a DECDHL line (1=top 2=bottom) */ -} ScreenPen; - -/* Internal representation of a screen cell */ -typedef struct -{ - schar_T schar; - ScreenPen pen; -} ScreenCell; - -struct VTermScreen -{ - VTerm *vt; - VTermState *state; - - const VTermScreenCallbacks *callbacks; - void *cbdata; - - VTermDamageSize damage_merge; - /* start_row == -1 => no damage */ - VTermRect damaged; - VTermRect pending_scrollrect; - int pending_scroll_downward, pending_scroll_rightward; - - int rows; - int cols; - - unsigned int global_reverse : 1; - unsigned int reflow : 1; - - /* Primary and Altscreen. buffers[1] is lazily allocated as needed */ - ScreenCell *buffers[2]; - - /* buffer will == buffers[0] or buffers[1], depending on altscreen */ - ScreenCell *buffer; - - /* buffer for a single screen row used in scrollback storage callbacks */ - VTermScreenCell *sb_buffer; - - ScreenPen pen; -}; - -static inline void clearcell(const VTermScreen *screen, ScreenCell *cell) -{ - cell->schar = 0; - cell->pen = screen->pen; -} - -static inline ScreenCell *getcell(const VTermScreen *screen, int row, int col) -{ - if(row < 0 || row >= screen->rows) - return NULL; - if(col < 0 || col >= screen->cols) - return NULL; - return screen->buffer + (screen->cols * row) + col; -} - -static ScreenCell *alloc_buffer(VTermScreen *screen, int rows, int cols) -{ - ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * rows * cols); - - for(int row = 0; row < rows; row++) { - for(int col = 0; col < cols; col++) { - clearcell(screen, &new_buffer[row * cols + col]); - } - } - - return new_buffer; -} - -static void damagerect(VTermScreen *screen, VTermRect rect) -{ - VTermRect emit; - - switch(screen->damage_merge) { - case VTERM_DAMAGE_CELL: - /* Always emit damage event */ - emit = rect; - break; - - case VTERM_DAMAGE_ROW: - /* Emit damage longer than one row. Try to merge with existing damage in - * the same row */ - if(rect.end_row > rect.start_row + 1) { - // Bigger than 1 line - flush existing, emit this - vterm_screen_flush_damage(screen); - emit = rect; - } - else if(screen->damaged.start_row == -1) { - // None stored yet - screen->damaged = rect; - return; - } - else if(rect.start_row == screen->damaged.start_row) { - // Merge with the stored line - if(screen->damaged.start_col > rect.start_col) - screen->damaged.start_col = rect.start_col; - if(screen->damaged.end_col < rect.end_col) - screen->damaged.end_col = rect.end_col; - return; - } - else { - // Emit the currently stored line, store a new one - emit = screen->damaged; - screen->damaged = rect; - } - break; - - case VTERM_DAMAGE_SCREEN: - case VTERM_DAMAGE_SCROLL: - /* Never emit damage event */ - if(screen->damaged.start_row == -1) - screen->damaged = rect; - else { - rect_expand(&screen->damaged, &rect); - } - return; - - default: - DEBUG_LOG("TODO: Maybe merge damage for level %d\n", screen->damage_merge); - return; - } - - if(screen->callbacks && screen->callbacks->damage) - (*screen->callbacks->damage)(emit, screen->cbdata); -} - -static void damagescreen(VTermScreen *screen) -{ - VTermRect rect = { - .start_row = 0, - .end_row = screen->rows, - .start_col = 0, - .end_col = screen->cols, - }; - - damagerect(screen, rect); -} - -static int putglyph(VTermGlyphInfo *info, VTermPos pos, void *user) -{ - VTermScreen *screen = user; - ScreenCell *cell = getcell(screen, pos.row, pos.col); - - if(!cell) - return 0; - - cell->schar = info->schar; - if (info->schar != 0) { - cell->pen = screen->pen; - } - - for(int col = 1; col < info->width; col++) - getcell(screen, pos.row, pos.col + col)->schar = (uint32_t)-1; - - VTermRect rect = { - .start_row = pos.row, - .end_row = pos.row+1, - .start_col = pos.col, - .end_col = pos.col+info->width, - }; - - cell->pen.protected_cell = info->protected_cell; - cell->pen.dwl = info->dwl; - cell->pen.dhl = info->dhl; - - damagerect(screen, rect); - - return 1; -} - -static void sb_pushline_from_row(VTermScreen *screen, int row) -{ - VTermPos pos = { .row = row }; - for(pos.col = 0; pos.col < screen->cols; pos.col++) - vterm_screen_get_cell(screen, pos, screen->sb_buffer + pos.col); - - (screen->callbacks->sb_pushline)(screen->cols, screen->sb_buffer, screen->cbdata); -} - -static int moverect_internal(VTermRect dest, VTermRect src, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->sb_pushline && - dest.start_row == 0 && dest.start_col == 0 && // starts top-left corner - dest.end_col == screen->cols && // full width - screen->buffer == screen->buffers[BUFIDX_PRIMARY]) { // not altscreen - for(int row = 0; row < src.start_row; row++) - sb_pushline_from_row(screen, row); - } - - int cols = src.end_col - src.start_col; - int downward = src.start_row - dest.start_row; - - int init_row, test_row, inc_row; - if(downward < 0) { - init_row = dest.end_row - 1; - test_row = dest.start_row - 1; - inc_row = -1; - } - else { - init_row = dest.start_row; - test_row = dest.end_row; - inc_row = +1; - } - - for(int row = init_row; row != test_row; row += inc_row) - memmove(getcell(screen, row, dest.start_col), - getcell(screen, row + downward, src.start_col), - cols * sizeof(ScreenCell)); - - return 1; -} - -static int moverect_user(VTermRect dest, VTermRect src, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->moverect) { - if(screen->damage_merge != VTERM_DAMAGE_SCROLL) - // Avoid an infinite loop - vterm_screen_flush_damage(screen); - - if((*screen->callbacks->moverect)(dest, src, screen->cbdata)) - return 1; - } - - damagerect(screen, dest); - - return 1; -} - -static int erase_internal(VTermRect rect, int selective, void *user) -{ - VTermScreen *screen = user; - - for(int row = rect.start_row; row < screen->state->rows && row < rect.end_row; row++) { - const VTermLineInfo *info = vterm_state_get_lineinfo(screen->state, row); - - for(int col = rect.start_col; col < rect.end_col; col++) { - ScreenCell *cell = getcell(screen, row, col); - - if(selective && cell->pen.protected_cell) - continue; - - cell->schar = 0; - cell->pen = (ScreenPen){ - /* Only copy .fg and .bg; leave things like rv in reset state */ - .fg = screen->pen.fg, - .bg = screen->pen.bg, - }; - cell->pen.dwl = info->doublewidth; - cell->pen.dhl = info->doubleheight; - } - } - - return 1; -} - -static int erase_user(VTermRect rect, int selective, void *user) -{ - VTermScreen *screen = user; - - damagerect(screen, rect); - - return 1; -} - -static int erase(VTermRect rect, int selective, void *user) -{ - erase_internal(rect, selective, user); - return erase_user(rect, 0, user); -} - -static int scrollrect(VTermRect rect, int downward, int rightward, void *user) -{ - VTermScreen *screen = user; - - if(screen->damage_merge != VTERM_DAMAGE_SCROLL) { - vterm_scroll_rect(rect, downward, rightward, - moverect_internal, erase_internal, screen); - - vterm_screen_flush_damage(screen); - - vterm_scroll_rect(rect, downward, rightward, - moverect_user, erase_user, screen); - - return 1; - } - - if(screen->damaged.start_row != -1 && - !rect_intersects(&rect, &screen->damaged)) { - vterm_screen_flush_damage(screen); - } - - if(screen->pending_scrollrect.start_row == -1) { - screen->pending_scrollrect = rect; - screen->pending_scroll_downward = downward; - screen->pending_scroll_rightward = rightward; - } - else if(rect_equal(&screen->pending_scrollrect, &rect) && - ((screen->pending_scroll_downward == 0 && downward == 0) || - (screen->pending_scroll_rightward == 0 && rightward == 0))) { - screen->pending_scroll_downward += downward; - screen->pending_scroll_rightward += rightward; - } - else { - vterm_screen_flush_damage(screen); - - screen->pending_scrollrect = rect; - screen->pending_scroll_downward = downward; - screen->pending_scroll_rightward = rightward; - } - - vterm_scroll_rect(rect, downward, rightward, - moverect_internal, erase_internal, screen); - - if(screen->damaged.start_row == -1) - return 1; - - if(rect_contains(&rect, &screen->damaged)) { - /* Scroll region entirely contains the damage; just move it */ - vterm_rect_move(&screen->damaged, -downward, -rightward); - rect_clip(&screen->damaged, &rect); - } - /* There are a number of possible cases here, but lets restrict this to only - * the common case where we might actually gain some performance by - * optimising it. Namely, a vertical scroll that neatly cuts the damage - * region in half. - */ - else if(rect.start_col <= screen->damaged.start_col && - rect.end_col >= screen->damaged.end_col && - rightward == 0) { - if(screen->damaged.start_row >= rect.start_row && - screen->damaged.start_row < rect.end_row) { - screen->damaged.start_row -= downward; - if(screen->damaged.start_row < rect.start_row) - screen->damaged.start_row = rect.start_row; - if(screen->damaged.start_row > rect.end_row) - screen->damaged.start_row = rect.end_row; - } - if(screen->damaged.end_row >= rect.start_row && - screen->damaged.end_row < rect.end_row) { - screen->damaged.end_row -= downward; - if(screen->damaged.end_row < rect.start_row) - screen->damaged.end_row = rect.start_row; - if(screen->damaged.end_row > rect.end_row) - screen->damaged.end_row = rect.end_row; - } - } - else { - DEBUG_LOG("TODO: Just flush and redo damaged=" STRFrect " rect=" STRFrect "\n", - ARGSrect(screen->damaged), ARGSrect(rect)); - } - - return 1; -} - -static int movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->movecursor) - return (*screen->callbacks->movecursor)(pos, oldpos, visible, screen->cbdata); - - return 0; -} - -static int setpenattr(VTermAttr attr, VTermValue *val, void *user) -{ - VTermScreen *screen = user; - - switch(attr) { - case VTERM_ATTR_BOLD: - screen->pen.bold = val->boolean; - return 1; - case VTERM_ATTR_UNDERLINE: - screen->pen.underline = val->number; - return 1; - case VTERM_ATTR_ITALIC: - screen->pen.italic = val->boolean; - return 1; - case VTERM_ATTR_BLINK: - screen->pen.blink = val->boolean; - return 1; - case VTERM_ATTR_REVERSE: - screen->pen.reverse = val->boolean; - return 1; - case VTERM_ATTR_CONCEAL: - screen->pen.conceal = val->boolean; - return 1; - case VTERM_ATTR_STRIKE: - screen->pen.strike = val->boolean; - return 1; - case VTERM_ATTR_FONT: - screen->pen.font = val->number; - return 1; - case VTERM_ATTR_FOREGROUND: - screen->pen.fg = val->color; - return 1; - case VTERM_ATTR_BACKGROUND: - screen->pen.bg = val->color; - return 1; - case VTERM_ATTR_SMALL: - screen->pen.small = val->boolean; - return 1; - case VTERM_ATTR_BASELINE: - screen->pen.baseline = val->number; - return 1; - case VTERM_ATTR_URI: - screen->pen.uri = val->number; - return 1; - - case VTERM_N_ATTRS: - return 0; - } - - return 0; -} - -static int settermprop(VTermProp prop, VTermValue *val, void *user) -{ - VTermScreen *screen = user; - - switch(prop) { - case VTERM_PROP_ALTSCREEN: - if(val->boolean && !screen->buffers[BUFIDX_ALTSCREEN]) - return 0; - - screen->buffer = val->boolean ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; - /* only send a damage event on disable; because during enable there's an - * erase that sends a damage anyway - */ - if(!val->boolean) - damagescreen(screen); - break; - case VTERM_PROP_REVERSE: - screen->global_reverse = val->boolean; - damagescreen(screen); - break; - default: - ; /* ignore */ - } - - if(screen->callbacks && screen->callbacks->settermprop) - return (*screen->callbacks->settermprop)(prop, val, screen->cbdata); - - return 1; -} - -static int bell(void *user) -{ - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->bell) - return (*screen->callbacks->bell)(screen->cbdata); - - return 0; -} - -/* How many cells are non-blank - * Returns the position of the first blank cell in the trailing blank end */ -static int line_popcount(ScreenCell *buffer, int row, int rows, int cols) -{ - int col = cols - 1; - while(col >= 0 && buffer[row * cols + col].schar == 0) - col--; - return col + 1; -} - -static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields) -{ - int old_rows = screen->rows; - int old_cols = screen->cols; - - ScreenCell *old_buffer = screen->buffers[bufidx]; - VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx]; - - ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols); - VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); - - int old_row = old_rows - 1; - int new_row = new_rows - 1; - - VTermPos old_cursor = statefields->pos; - VTermPos new_cursor = { -1, -1 }; - -#ifdef DEBUG_REFLOW - fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n", - old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row); -#endif - - /* Keep track of the final row that is knonw to be blank, so we know what - * spare space we have for scrolling into - */ - int final_blank_row = new_rows; - - while(old_row >= 0) { - int old_row_end = old_row; - /* TODO: Stop if dwl or dhl */ - while(screen->reflow && old_lineinfo && old_row > 0 && old_lineinfo[old_row].continuation) - old_row--; - int old_row_start = old_row; - - int width = 0; - for(int row = old_row_start; row <= old_row_end; row++) { - if(screen->reflow && row < (old_rows - 1) && old_lineinfo[row + 1].continuation) - width += old_cols; - else - width += line_popcount(old_buffer, row, old_rows, old_cols); - } - - if(final_blank_row == (new_row + 1) && width == 0) - final_blank_row = new_row; - - int new_height = screen->reflow - ? width ? (width + new_cols - 1) / new_cols : 1 - : 1; - - int new_row_end = new_row; - int new_row_start = new_row - new_height + 1; - - old_row = old_row_start; - int old_col = 0; - - int spare_rows = new_rows - final_blank_row; - - if(new_row_start < 0 && /* we'd fall off the top */ - spare_rows >= 0 && /* we actually have spare rows */ - (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows)) - { - /* Attempt to scroll content down into the blank rows at the bottom to - * make it fit - */ - int downwards = -new_row_start; - if(downwards > spare_rows) - downwards = spare_rows; - int rowcount = new_rows - downwards; - -#ifdef DEBUG_REFLOW - fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards); -#endif - - memmove(&new_buffer[downwards * new_cols], &new_buffer[0], (unsigned long) rowcount * new_cols * sizeof(ScreenCell)); - memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0])); - - new_row += downwards; - new_row_start += downwards; - new_row_end += downwards; - - if(new_cursor.row >= 0) - new_cursor.row += downwards; - - final_blank_row += downwards; - } - -#ifdef DEBUG_REFLOW - fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n", - new_row_start, new_row_end, old_row_start, old_row_end, width); -#endif - - if(new_row_start < 0) { - if(old_row_start <= old_cursor.row && old_cursor.row <= old_row_end) { - new_cursor.row = 0; - new_cursor.col = old_cursor.col; - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - break; - } - - for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) { - int count = width >= new_cols ? new_cols : width; - width -= count; - - int new_col = 0; - - while(count) { - /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */ - new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col]; - - if(old_cursor.row == old_row && old_cursor.col == old_col) - new_cursor.row = new_row, new_cursor.col = new_col; - - old_col++; - if(old_col == old_cols) { - old_row++; - - if(!screen->reflow) { - new_col++; - break; - } - old_col = 0; - } - - new_col++; - count--; - } - - if(old_cursor.row == old_row && old_cursor.col >= old_col) { - new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col); - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - - while(new_col < new_cols) { - clearcell(screen, &new_buffer[new_row * new_cols + new_col]); - new_col++; - } - - new_lineinfo[new_row].continuation = (new_row > new_row_start); - } - - old_row = old_row_start - 1; - new_row = new_row_start - 1; - } - - if(old_cursor.row <= old_row) { - /* cursor would have moved entirely off the top of the screen; lets just - * bring it within range */ - new_cursor.row = 0, new_cursor.col = old_cursor.col; - if(new_cursor.col >= new_cols) - new_cursor.col = new_cols-1; - } - - /* We really expect the cursor position to be set by now */ - if(active && (new_cursor.row == -1 || new_cursor.col == -1)) { - fprintf(stderr, "screen_resize failed to update cursor position\n"); - abort(); - } - - if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) { - /* Push spare lines to scrollback buffer */ - if(screen->callbacks && screen->callbacks->sb_pushline) - for(int row = 0; row <= old_row; row++) - sb_pushline_from_row(screen, row); - if(active) - statefields->pos.row -= (old_row + 1); - } - if(new_row >= 0 && bufidx == BUFIDX_PRIMARY && - screen->callbacks && screen->callbacks->sb_popline) { - /* Try to backfill rows by popping scrollback buffer */ - while(new_row >= 0) { - if(!(screen->callbacks->sb_popline(old_cols, screen->sb_buffer, screen->cbdata))) - break; - - VTermPos pos = { .row = new_row }; - for(pos.col = 0; pos.col < old_cols && pos.col < new_cols; pos.col += screen->sb_buffer[pos.col].width) { - VTermScreenCell *src = &screen->sb_buffer[pos.col]; - ScreenCell *dst = &new_buffer[pos.row * new_cols + pos.col]; - - dst->schar = src->schar; - - dst->pen.bold = src->attrs.bold; - dst->pen.underline = src->attrs.underline; - dst->pen.italic = src->attrs.italic; - dst->pen.blink = src->attrs.blink; - dst->pen.reverse = src->attrs.reverse ^ screen->global_reverse; - dst->pen.conceal = src->attrs.conceal; - dst->pen.strike = src->attrs.strike; - dst->pen.font = src->attrs.font; - dst->pen.small = src->attrs.small; - dst->pen.baseline = src->attrs.baseline; - - dst->pen.fg = src->fg; - dst->pen.bg = src->bg; - - dst->pen.uri = src->uri; - - if(src->width == 2 && pos.col < (new_cols-1)) - (dst + 1)->schar = (uint32_t) -1; - } - for( ; pos.col < new_cols; pos.col++) - clearcell(screen, &new_buffer[pos.row * new_cols + pos.col]); - new_row--; - - if(active) - statefields->pos.row++; - } - } - if(new_row >= 0) { - /* Scroll new rows back up to the top and fill in blanks at the bottom */ - int moverows = new_rows - new_row - 1; - memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], (unsigned long) moverows * new_cols * sizeof(ScreenCell)); - memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0])); - - new_cursor.row -= (new_row + 1); - - for(new_row = moverows; new_row < new_rows; new_row++) { - for(int col = 0; col < new_cols; col++) - clearcell(screen, &new_buffer[new_row * new_cols + col]); - new_lineinfo[new_row] = (VTermLineInfo){ 0 }; - } - } - - vterm_allocator_free(screen->vt, old_buffer); - screen->buffers[bufidx] = new_buffer; - - vterm_allocator_free(screen->vt, old_lineinfo); - statefields->lineinfos[bufidx] = new_lineinfo; - - if(active) - statefields->pos = new_cursor; - - return; -} - -static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user) -{ - VTermScreen *screen = user; - - int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]); - - int old_rows = screen->rows; - int old_cols = screen->cols; - - if(new_cols > old_cols) { - /* Ensure that ->sb_buffer is large enough for a new or and old row */ - if(screen->sb_buffer) - vterm_allocator_free(screen->vt, screen->sb_buffer); - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); - } - - resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields); - if(screen->buffers[BUFIDX_ALTSCREEN]) - resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields); - else if(new_rows != old_rows) { - /* We don't need a full resize of the altscreen because it isn't enabled - * but we should at least keep the lineinfo the right size */ - vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]); - - VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows); - for(int row = 0; row < new_rows; row++) - new_lineinfo[row] = (VTermLineInfo){ 0 }; - - fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo; - } - - screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY]; - - screen->rows = new_rows; - screen->cols = new_cols; - - if(new_cols <= old_cols) { - if(screen->sb_buffer) - vterm_allocator_free(screen->vt, screen->sb_buffer); - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * new_cols); - } - - /* TODO: Maaaaybe we can optimise this if there's no reflow happening */ - damagescreen(screen); - - if(screen->callbacks && screen->callbacks->resize) - return (*screen->callbacks->resize)(new_rows, new_cols, screen->cbdata); - - return 1; -} - -static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user) -{ - VTermScreen *screen = user; - - if(newinfo->doublewidth != oldinfo->doublewidth || - newinfo->doubleheight != oldinfo->doubleheight) { - for(int col = 0; col < screen->cols; col++) { - ScreenCell *cell = getcell(screen, row, col); - cell->pen.dwl = newinfo->doublewidth; - cell->pen.dhl = newinfo->doubleheight; - } - - VTermRect rect = { - .start_row = row, - .end_row = row + 1, - .start_col = 0, - .end_col = newinfo->doublewidth ? screen->cols / 2 : screen->cols, - }; - damagerect(screen, rect); - - if(newinfo->doublewidth) { - rect.start_col = screen->cols / 2; - rect.end_col = screen->cols; - - erase_internal(rect, 0, user); - } - } - - return 1; -} - -static int sb_clear(void *user) { - VTermScreen *screen = user; - - if(screen->callbacks && screen->callbacks->sb_clear) - if((*screen->callbacks->sb_clear)(screen->cbdata)) - return 1; - - return 0; -} - -static VTermStateCallbacks state_cbs = { - .putglyph = &putglyph, - .movecursor = &movecursor, - .scrollrect = &scrollrect, - .erase = &erase, - .setpenattr = &setpenattr, - .settermprop = &settermprop, - .bell = &bell, - .resize = &resize, - .setlineinfo = &setlineinfo, - .sb_clear = &sb_clear, -}; - -static VTermScreen *screen_new(VTerm *vt) -{ - VTermState *state = vterm_obtain_state(vt); - if(!state) - return NULL; - - VTermScreen *screen = vterm_allocator_malloc(vt, sizeof(VTermScreen)); - int rows, cols; - - vterm_get_size(vt, &rows, &cols); - - screen->vt = vt; - screen->state = state; - - screen->damage_merge = VTERM_DAMAGE_CELL; - screen->damaged.start_row = -1; - screen->pending_scrollrect.start_row = -1; - - screen->rows = rows; - screen->cols = cols; - - screen->global_reverse = false; - screen->reflow = false; - - screen->callbacks = NULL; - screen->cbdata = NULL; - - screen->buffers[BUFIDX_PRIMARY] = alloc_buffer(screen, rows, cols); - - screen->buffer = screen->buffers[BUFIDX_PRIMARY]; - - screen->sb_buffer = vterm_allocator_malloc(screen->vt, sizeof(VTermScreenCell) * cols); - - vterm_state_set_callbacks(screen->state, &state_cbs, screen); - - return screen; -} - -INTERNAL void vterm_screen_free(VTermScreen *screen) -{ - vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_PRIMARY]); - if(screen->buffers[BUFIDX_ALTSCREEN]) - vterm_allocator_free(screen->vt, screen->buffers[BUFIDX_ALTSCREEN]); - - vterm_allocator_free(screen->vt, screen->sb_buffer); - - vterm_allocator_free(screen->vt, screen); -} - -void vterm_screen_reset(VTermScreen *screen, int hard) -{ - screen->damaged.start_row = -1; - screen->pending_scrollrect.start_row = -1; - vterm_state_reset(screen->state, hard); - vterm_screen_flush_damage(screen); -} - -size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, const VTermRect rect) -{ - size_t outpos = 0; - int padding = 0; - -#define PUT(bytes, thislen) \ - if(true) { \ - if(buffer && outpos + thislen <= len) \ - memcpy((char *)buffer + outpos, bytes, thislen); \ - outpos += thislen; \ - } \ - - for(int row = rect.start_row; row < rect.end_row; row++) { - for(int col = rect.start_col; col < rect.end_col; col++) { - ScreenCell *cell = getcell(screen, row, col); - - if(cell->schar == 0) - // Erased cell, might need a space - padding++; - else if(cell->schar == (uint32_t)-1) - // Gap behind a double-width char, do nothing - ; - else { - while(padding) { - PUT(" ", 1); - padding--; - } - char buf[MAX_SCHAR_SIZE + 1]; - size_t thislen = schar_get(buf, cell->schar); - PUT(buf, thislen); - } - } - - if(row < rect.end_row - 1) { - PUT("\n", 1); - padding = 0; - } - } - - return outpos; -} - -/* Copy internal to external representation of a screen cell */ -int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell) -{ - ScreenCell *intcell = getcell(screen, pos.row, pos.col); - if(!intcell) - return 0; - - cell->schar = intcell->schar; - - cell->attrs.bold = intcell->pen.bold; - cell->attrs.underline = intcell->pen.underline; - cell->attrs.italic = intcell->pen.italic; - cell->attrs.blink = intcell->pen.blink; - cell->attrs.reverse = intcell->pen.reverse ^ screen->global_reverse; - cell->attrs.conceal = intcell->pen.conceal; - cell->attrs.strike = intcell->pen.strike; - cell->attrs.font = intcell->pen.font; - cell->attrs.small = intcell->pen.small; - cell->attrs.baseline = intcell->pen.baseline; - - cell->attrs.dwl = intcell->pen.dwl; - cell->attrs.dhl = intcell->pen.dhl; - - cell->fg = intcell->pen.fg; - cell->bg = intcell->pen.bg; - - cell->uri = intcell->pen.uri; - - if(pos.col < (screen->cols - 1) && - getcell(screen, pos.row, pos.col + 1)->schar == (uint32_t)-1) - cell->width = 2; - else - cell->width = 1; - - return 1; -} - -int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) -{ - /* This cell is EOL if this and every cell to the right is black */ - for(; pos.col < screen->cols; pos.col++) { - ScreenCell *cell = getcell(screen, pos.row, pos.col); - if(cell->schar != 0) - return 0; - } - - return 1; -} - -VTermScreen *vterm_obtain_screen(VTerm *vt) -{ - if(vt->screen) - return vt->screen; - - VTermScreen *screen = screen_new(vt); - vt->screen = screen; - - return screen; -} - -void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow) -{ - screen->reflow = reflow; -} - -#undef vterm_screen_set_reflow -void vterm_screen_set_reflow(VTermScreen *screen, bool reflow) -{ - vterm_screen_enable_reflow(screen, reflow); -} - -void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen) -{ - if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) { - int rows, cols; - vterm_get_size(screen->vt, &rows, &cols); - - screen->buffers[BUFIDX_ALTSCREEN] = alloc_buffer(screen, rows, cols); - } -} - -void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user) -{ - screen->callbacks = callbacks; - screen->cbdata = user; -} - -void *vterm_screen_get_cbdata(VTermScreen *screen) -{ - return screen->cbdata; -} - -void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user) -{ - vterm_state_set_unrecognised_fallbacks(screen->state, fallbacks, user); -} - -void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen) -{ - return vterm_state_get_unrecognised_fbdata(screen->state); -} - -void vterm_screen_flush_damage(VTermScreen *screen) -{ - if(screen->pending_scrollrect.start_row != -1) { - vterm_scroll_rect(screen->pending_scrollrect, screen->pending_scroll_downward, screen->pending_scroll_rightward, - moverect_user, erase_user, screen); - - screen->pending_scrollrect.start_row = -1; - } - - if(screen->damaged.start_row != -1) { - if(screen->callbacks && screen->callbacks->damage) - (*screen->callbacks->damage)(screen->damaged, screen->cbdata); - - screen->damaged.start_row = -1; - } -} - -void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size) -{ - vterm_screen_flush_damage(screen); - screen->damage_merge = size; -} - -static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) -{ - if((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) - return 1; - if((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) - return 1; - if((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) - return 1; - if((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) - return 1; - if((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) - return 1; - if((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) - return 1; - if((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) - return 1; - if((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) - return 1; - if((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) - return 1; - if((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) - return 1; - if((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) - return 1; - if((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) - return 1; - if((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) - return 1; - - return 0; -} - -int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs) -{ - ScreenCell *target = getcell(screen, pos.row, pos.col); - - // TODO: bounds check - extent->start_row = pos.row; - extent->end_row = pos.row + 1; - - if(extent->start_col < 0) - extent->start_col = 0; - if(extent->end_col < 0) - extent->end_col = screen->cols; - - int col; - - for(col = pos.col - 1; col >= extent->start_col; col--) - if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) - break; - extent->start_col = col + 1; - - for(col = pos.col + 1; col < extent->end_col; col++) - if(attrs_differ(attrs, target, getcell(screen, pos.row, col))) - break; - extent->end_col = col - 1; - - return 1; -} - -void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col) -{ - vterm_state_convert_color_to_rgb(screen->state, col); -} - -static void reset_default_colours(VTermScreen *screen, ScreenCell *buffer) -{ - for(int row = 0; row <= screen->rows - 1; row++) - for(int col = 0; col <= screen->cols - 1; col++) { - ScreenCell *cell = &buffer[row * screen->cols + col]; - if(VTERM_COLOR_IS_DEFAULT_FG(&cell->pen.fg)) - cell->pen.fg = screen->pen.fg; - if(VTERM_COLOR_IS_DEFAULT_BG(&cell->pen.bg)) - cell->pen.bg = screen->pen.bg; - } -} - -void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg) -{ - vterm_state_set_default_colors(screen->state, default_fg, default_bg); - - if(default_fg && VTERM_COLOR_IS_DEFAULT_FG(&screen->pen.fg)) { - screen->pen.fg = *default_fg; - screen->pen.fg.type = (screen->pen.fg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_FG; - } - - if(default_bg && VTERM_COLOR_IS_DEFAULT_BG(&screen->pen.bg)) { - screen->pen.bg = *default_bg; - screen->pen.bg.type = (screen->pen.bg.type & ~VTERM_COLOR_DEFAULT_MASK) - | VTERM_COLOR_DEFAULT_BG; - } - - reset_default_colours(screen, screen->buffers[0]); - if(screen->buffers[1]) - reset_default_colours(screen, screen->buffers[1]); -} diff --git a/src/vterm/state.c b/src/vterm/state.c deleted file mode 100644 index d546672e67..0000000000 --- a/src/vterm/state.c +++ /dev/null @@ -1,2281 +0,0 @@ -#include "vterm_internal.h" - -#include <stdio.h> -#include <string.h> - -#include "nvim/grid.h" -#include "nvim/mbyte.h" - -#define strneq(a,b,n) (strncmp(a,b,n)==0) - -#if defined(DEBUG) && DEBUG > 1 -# define DEBUG_GLYPH_COMBINE -#endif - -/* Some convenient wrappers to make callback functions easier */ - -static void putglyph(VTermState *state, const schar_T schar, int width, VTermPos pos) -{ - VTermGlyphInfo info = { - .schar = schar, - .width = width, - .protected_cell = state->protected_cell, - .dwl = state->lineinfo[pos.row].doublewidth, - .dhl = state->lineinfo[pos.row].doubleheight, - }; - - if(state->callbacks && state->callbacks->putglyph) - if((*state->callbacks->putglyph)(&info, pos, state->cbdata)) - return; - - DEBUG_LOG("libvterm: Unhandled putglyph U+%04x at (%d,%d)\n", chars[0], pos.col, pos.row); -} - -static void updatecursor(VTermState *state, VTermPos *oldpos, int cancel_phantom) -{ - if(state->pos.col == oldpos->col && state->pos.row == oldpos->row) - return; - - if(cancel_phantom) - state->at_phantom = 0; - - if(state->callbacks && state->callbacks->movecursor) - if((*state->callbacks->movecursor)(state->pos, *oldpos, state->mode.cursor_visible, state->cbdata)) - return; -} - -static void erase(VTermState *state, VTermRect rect, int selective) -{ - if(rect.end_col == state->cols) { - /* If we're erasing the final cells of any lines, cancel the continuation - * marker on the subsequent line - */ - for(int row = rect.start_row + 1; row < rect.end_row + 1 && row < state->rows; row++) - state->lineinfo[row].continuation = 0; - } - - if(state->callbacks && state->callbacks->erase) - if((*state->callbacks->erase)(rect, selective, state->cbdata)) - return; -} - -static VTermState *vterm_state_new(VTerm *vt) -{ - VTermState *state = vterm_allocator_malloc(vt, sizeof(VTermState)); - - state->vt = vt; - - state->rows = vt->rows; - state->cols = vt->cols; - - state->mouse_col = 0; - state->mouse_row = 0; - state->mouse_buttons = 0; - - state->mouse_protocol = MOUSE_X10; - - state->callbacks = NULL; - state->cbdata = NULL; - - state->selection.callbacks = NULL; - state->selection.user = NULL; - state->selection.buffer = NULL; - - vterm_state_newpen(state); - - state->bold_is_highbright = 0; - - state->combine_pos.row = -1; - - state->tabstops = vterm_allocator_malloc(state->vt, (state->cols + 7) / 8); - - state->lineinfos[BUFIDX_PRIMARY] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); - /* TODO: Make an 'enable' function */ - state->lineinfos[BUFIDX_ALTSCREEN] = vterm_allocator_malloc(state->vt, state->rows * sizeof(VTermLineInfo)); - state->lineinfo = state->lineinfos[BUFIDX_PRIMARY]; - - state->encoding_utf8.enc = vterm_lookup_encoding(ENC_UTF8, 'u'); - if(*state->encoding_utf8.enc->init) - (*state->encoding_utf8.enc->init)(state->encoding_utf8.enc, state->encoding_utf8.data); - - return state; -} - -INTERNAL void vterm_state_free(VTermState *state) -{ - vterm_allocator_free(state->vt, state->tabstops); - vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_PRIMARY]); - if(state->lineinfos[BUFIDX_ALTSCREEN]) - vterm_allocator_free(state->vt, state->lineinfos[BUFIDX_ALTSCREEN]); - vterm_allocator_free(state->vt, state); -} - -static void scroll(VTermState *state, VTermRect rect, int downward, int rightward) -{ - if(!downward && !rightward) - return; - - int rows = rect.end_row - rect.start_row; - if(downward > rows) - downward = rows; - else if(downward < -rows) - downward = -rows; - - int cols = rect.end_col - rect.start_col; - if(rightward > cols) - rightward = cols; - else if(rightward < -cols) - rightward = -cols; - - // Update lineinfo if full line - if(rect.start_col == 0 && rect.end_col == state->cols && rightward == 0) { - int height = rect.end_row - rect.start_row - abs(downward); - - if(downward > 0) { - memmove(state->lineinfo + rect.start_row, - state->lineinfo + rect.start_row + downward, - height * sizeof(state->lineinfo[0])); - for(int row = rect.end_row - downward; row < rect.end_row; row++) - state->lineinfo[row] = (VTermLineInfo){ 0 }; - } - else { - memmove(state->lineinfo + rect.start_row - downward, - state->lineinfo + rect.start_row, - height * sizeof(state->lineinfo[0])); - for(int row = rect.start_row; row < rect.start_row - downward; row++) - state->lineinfo[row] = (VTermLineInfo){ 0 }; - } - } - - if(state->callbacks && state->callbacks->scrollrect) - if((*state->callbacks->scrollrect)(rect, downward, rightward, state->cbdata)) - return; - - if(state->callbacks) - vterm_scroll_rect(rect, downward, rightward, - state->callbacks->moverect, state->callbacks->erase, state->cbdata); -} - -static void linefeed(VTermState *state) -{ - if(state->pos.row == SCROLLREGION_BOTTOM(state) - 1) { - VTermRect rect = { - .start_row = state->scrollregion_top, - .end_row = SCROLLREGION_BOTTOM(state), - .start_col = SCROLLREGION_LEFT(state), - .end_col = SCROLLREGION_RIGHT(state), - }; - - scroll(state, rect, 1, 0); - } - else if(state->pos.row < state->rows-1) - state->pos.row++; -} - -static void set_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - state->tabstops[col >> 3] |= mask; -} - -static void clear_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - state->tabstops[col >> 3] &= ~mask; -} - -static int is_col_tabstop(VTermState *state, int col) -{ - unsigned char mask = 1 << (col & 7); - return state->tabstops[col >> 3] & mask; -} - -static int is_cursor_in_scrollregion(const VTermState *state) -{ - if(state->pos.row < state->scrollregion_top || - state->pos.row >= SCROLLREGION_BOTTOM(state)) - return 0; - if(state->pos.col < SCROLLREGION_LEFT(state) || - state->pos.col >= SCROLLREGION_RIGHT(state)) - return 0; - - return 1; -} - -static void tab(VTermState *state, int count, int direction) -{ - while(count > 0) { - if(direction > 0) { - if(state->pos.col >= THISROWWIDTH(state)-1) - return; - - state->pos.col++; - } - else if(direction < 0) { - if(state->pos.col < 1) - return; - - state->pos.col--; - } - - if(is_col_tabstop(state, state->pos.col)) - count--; - } -} - -#define NO_FORCE 0 -#define FORCE 1 - -#define DWL_OFF 0 -#define DWL_ON 1 - -#define DHL_OFF 0 -#define DHL_TOP 1 -#define DHL_BOTTOM 2 - -static void set_lineinfo(VTermState *state, int row, int force, int dwl, int dhl) -{ - VTermLineInfo info = state->lineinfo[row]; - - if(dwl == DWL_OFF) - info.doublewidth = DWL_OFF; - else if(dwl == DWL_ON) - info.doublewidth = DWL_ON; - // else -1 to ignore - - if(dhl == DHL_OFF) - info.doubleheight = DHL_OFF; - else if(dhl == DHL_TOP) - info.doubleheight = DHL_TOP; - else if(dhl == DHL_BOTTOM) - info.doubleheight = DHL_BOTTOM; - - if((state->callbacks && - state->callbacks->setlineinfo && - (*state->callbacks->setlineinfo)(row, &info, state->lineinfo + row, state->cbdata)) - || force) - state->lineinfo[row] = info; -} - -static int on_text(const char bytes[], size_t len, void *user) -{ - VTermState *state = user; - - VTermPos oldpos = state->pos; - - uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer); - size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t); - - int npoints = 0; - size_t eaten = 0; - - VTermEncodingInstance *encoding = - state->gsingle_set ? &state->encoding[state->gsingle_set] : - !(bytes[eaten] & 0x80) ? &state->encoding[state->gl_set] : - state->vt->mode.utf8 ? &state->encoding_utf8 : - &state->encoding[state->gr_set]; - - (*encoding->enc->decode)(encoding->enc, encoding->data, - codepoints, &npoints, state->gsingle_set ? 1 : maxpoints, - bytes, &eaten, len); - - /* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet - * for even a single codepoint - */ - if(!npoints) - return eaten; - - if(state->gsingle_set && npoints) - state->gsingle_set = 0; - - int i = 0; - GraphemeState grapheme_state = GRAPHEME_STATE_INIT; - size_t grapheme_len = 0; - bool recombine = false; - - /* See if the cursor has moved since */ - if(state->pos.row == state->combine_pos.row && state->pos.col == state->combine_pos.col + state->combine_width) { - /* This is a combining char. that needs to be merged with the previous - * glyph output */ - if(utf_iscomposing(state->grapheme_last, codepoints[i], &state->grapheme_state)) { - /* Find where we need to append these combining chars */ - grapheme_len = state->grapheme_len; - grapheme_state = state->grapheme_state; - state->pos.col = state->combine_pos.col; - recombine = true; - } else { - DEBUG_LOG("libvterm: TODO: Skip over split char+combining\n"); - } - } - - while(i < npoints) { - // Try to find combining characters following this - do { - if (grapheme_len < sizeof(state->grapheme_buf) - 4) { - grapheme_len += utf_char2bytes(codepoints[i], state->grapheme_buf + grapheme_len); - } - i++; - } while(i < npoints && utf_iscomposing(codepoints[i-1], codepoints[i], &grapheme_state)); - - int width = utf_ptr2cells_len(state->grapheme_buf, grapheme_len); - - if(state->at_phantom || state->pos.col + width > THISROWWIDTH(state)) { - linefeed(state); - state->pos.col = 0; - state->at_phantom = 0; - state->lineinfo[state->pos.row].continuation = 1; - } - - if(state->mode.insert && !recombine) { - /* TODO: This will be a little inefficient for large bodies of text, as - * it'll have to 'ICH' effectively before every glyph. We should scan - * ahead and ICH as many times as required - */ - VTermRect rect = { - .start_row = state->pos.row, - .end_row = state->pos.row + 1, - .start_col = state->pos.col, - .end_col = THISROWWIDTH(state), - }; - scroll(state, rect, 0, -1); - } - - schar_T sc = schar_from_buf(state->grapheme_buf, grapheme_len); - putglyph(state, sc, width, state->pos); - - if(i == npoints) { - /* End of the buffer. Save the chars in case we have to combine with - * more on the next call */ - state->grapheme_len = grapheme_len; - state->grapheme_last = codepoints[i-1]; - state->grapheme_state = grapheme_state; - state->combine_width = width; - state->combine_pos = state->pos; - } else { - grapheme_len = 0; - recombine = false; - } - - if(state->pos.col + width >= THISROWWIDTH(state)) { - if(state->mode.autowrap) - state->at_phantom = 1; - } - else { - state->pos.col += width; - } - } - - updatecursor(state, &oldpos, 0); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after text: (%d,%d)\n", - state->pos.row, state->pos.col); - abort(); - } -#endif - - return eaten; -} - -static int on_control(unsigned char control, void *user) -{ - VTermState *state = user; - - VTermPos oldpos = state->pos; - - switch(control) { - case 0x07: // BEL - ECMA-48 8.3.3 - if(state->callbacks && state->callbacks->bell) - (*state->callbacks->bell)(state->cbdata); - break; - - case 0x08: // BS - ECMA-48 8.3.5 - if(state->pos.col > 0) - state->pos.col--; - break; - - case 0x09: // HT - ECMA-48 8.3.60 - tab(state, 1, +1); - break; - - case 0x0a: // LF - ECMA-48 8.3.74 - case 0x0b: // VT - case 0x0c: // FF - linefeed(state); - if(state->mode.newline) - state->pos.col = 0; - break; - - case 0x0d: // CR - ECMA-48 8.3.15 - state->pos.col = 0; - break; - - case 0x0e: // LS1 - ECMA-48 8.3.76 - state->gl_set = 1; - break; - - case 0x0f: // LS0 - ECMA-48 8.3.75 - state->gl_set = 0; - break; - - case 0x84: // IND - DEPRECATED but implemented for completeness - linefeed(state); - break; - - case 0x85: // NEL - ECMA-48 8.3.86 - linefeed(state); - state->pos.col = 0; - break; - - case 0x88: // HTS - ECMA-48 8.3.62 - set_col_tabstop(state, state->pos.col); - break; - - case 0x8d: // RI - ECMA-48 8.3.104 - if(state->pos.row == state->scrollregion_top) { - VTermRect rect = { - .start_row = state->scrollregion_top, - .end_row = SCROLLREGION_BOTTOM(state), - .start_col = SCROLLREGION_LEFT(state), - .end_col = SCROLLREGION_RIGHT(state), - }; - - scroll(state, rect, -1, 0); - } - else if(state->pos.row > 0) - state->pos.row--; - break; - - case 0x8e: // SS2 - ECMA-48 8.3.141 - state->gsingle_set = 2; - break; - - case 0x8f: // SS3 - ECMA-48 8.3.142 - state->gsingle_set = 3; - break; - - default: - if(state->fallbacks && state->fallbacks->control) - if((*state->fallbacks->control)(control, state->fbdata)) - return 1; - - return 0; - } - - updatecursor(state, &oldpos, 1); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after Ctrl %02x: (%d,%d)\n", - control, state->pos.row, state->pos.col); - abort(); - } -#endif - - return 1; -} - -static int settermprop_bool(VTermState *state, VTermProp prop, int v) -{ - VTermValue val = { .boolean = v }; - return vterm_state_set_termprop(state, prop, &val); -} - -static int settermprop_int(VTermState *state, VTermProp prop, int v) -{ - VTermValue val = { .number = v }; - return vterm_state_set_termprop(state, prop, &val); -} - -static int settermprop_string(VTermState *state, VTermProp prop, VTermStringFragment frag) -{ - VTermValue val = { .string = frag }; - return vterm_state_set_termprop(state, prop, &val); -} - -static void savecursor(VTermState *state, int save) -{ - if(save) { - state->saved.pos = state->pos; - state->saved.mode.cursor_visible = state->mode.cursor_visible; - state->saved.mode.cursor_blink = state->mode.cursor_blink; - state->saved.mode.cursor_shape = state->mode.cursor_shape; - - vterm_state_savepen(state, 1); - } - else { - VTermPos oldpos = state->pos; - - state->pos = state->saved.pos; - - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, state->saved.mode.cursor_visible); - settermprop_bool(state, VTERM_PROP_CURSORBLINK, state->saved.mode.cursor_blink); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, state->saved.mode.cursor_shape); - - vterm_state_savepen(state, 0); - - updatecursor(state, &oldpos, 1); - } -} - -static int on_escape(const char *bytes, size_t len, void *user) -{ - VTermState *state = user; - - /* Easier to decode this from the first byte, even though the final - * byte terminates it - */ - switch(bytes[0]) { - case ' ': - if(len != 2) - return 0; - - switch(bytes[1]) { - case 'F': // S7C1T - state->vt->mode.ctrl8bit = 0; - break; - - case 'G': // S8C1T - state->vt->mode.ctrl8bit = 1; - break; - - default: - return 0; - } - return 2; - - case '#': - if(len != 2) - return 0; - - switch(bytes[1]) { - case '3': // DECDHL top - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_TOP); - break; - - case '4': // DECDHL bottom - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_BOTTOM); - break; - - case '5': // DECSWL - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_OFF, DHL_OFF); - break; - - case '6': // DECDWL - if(state->mode.leftrightmargin) - break; - set_lineinfo(state, state->pos.row, NO_FORCE, DWL_ON, DHL_OFF); - break; - - case '8': // DECALN - { - VTermPos pos; - schar_T E = schar_from_ascii('E'); // E - for(pos.row = 0; pos.row < state->rows; pos.row++) - for(pos.col = 0; pos.col < ROWWIDTH(state, pos.row); pos.col++) - putglyph(state, E, 1, pos); - break; - } - - default: - return 0; - } - return 2; - - case '(': case ')': case '*': case '+': // SCS - if(len != 2) - return 0; - - { - int setnum = bytes[0] - 0x28; - VTermEncoding *newenc = vterm_lookup_encoding(ENC_SINGLE_94, bytes[1]); - - if(newenc) { - state->encoding[setnum].enc = newenc; - - if(newenc->init) - (*newenc->init)(newenc, state->encoding[setnum].data); - } - } - - return 2; - - case '7': // DECSC - savecursor(state, 1); - return 1; - - case '8': // DECRC - savecursor(state, 0); - return 1; - - case '<': // Ignored by VT100. Used in VT52 mode to switch up to VT100 - return 1; - - case '=': // DECKPAM - state->mode.keypad = 1; - return 1; - - case '>': // DECKPNM - state->mode.keypad = 0; - return 1; - - case 'c': // RIS - ECMA-48 8.3.105 - { - VTermPos oldpos = state->pos; - vterm_state_reset(state, 1); - if(state->callbacks && state->callbacks->movecursor) - (*state->callbacks->movecursor)(state->pos, oldpos, state->mode.cursor_visible, state->cbdata); - return 1; - } - - case 'n': // LS2 - ECMA-48 8.3.78 - state->gl_set = 2; - return 1; - - case 'o': // LS3 - ECMA-48 8.3.80 - state->gl_set = 3; - return 1; - - case '~': // LS1R - ECMA-48 8.3.77 - state->gr_set = 1; - return 1; - - case '}': // LS2R - ECMA-48 8.3.79 - state->gr_set = 2; - return 1; - - case '|': // LS3R - ECMA-48 8.3.81 - state->gr_set = 3; - return 1; - - default: - return 0; - } -} - -static void set_mode(VTermState *state, int num, int val) -{ - switch(num) { - case 4: // IRM - ECMA-48 7.2.10 - state->mode.insert = val; - break; - - case 20: // LNM - ANSI X3.4-1977 - state->mode.newline = val; - break; - - default: - DEBUG_LOG("libvterm: Unknown mode %d\n", num); - return; - } -} - -static void set_dec_mode(VTermState *state, int num, int val) -{ - switch(num) { - case 1: - state->mode.cursor = val; - break; - - case 5: // DECSCNM - screen mode - settermprop_bool(state, VTERM_PROP_REVERSE, val); - break; - - case 6: // DECOM - origin mode - { - VTermPos oldpos = state->pos; - state->mode.origin = val; - state->pos.row = state->mode.origin ? state->scrollregion_top : 0; - state->pos.col = state->mode.origin ? SCROLLREGION_LEFT(state) : 0; - updatecursor(state, &oldpos, 1); - } - break; - - case 7: - state->mode.autowrap = val; - break; - - case 12: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, val); - break; - - case 25: - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, val); - break; - - case 69: // DECVSSM - vertical split screen mode - // DECLRMM - left/right margin mode - state->mode.leftrightmargin = val; - if(val) { - // Setting DECVSSM must clear doublewidth/doubleheight state of every line - for(int row = 0; row < state->rows; row++) - set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); - } - - break; - - case 1000: - case 1002: - case 1003: - settermprop_int(state, VTERM_PROP_MOUSE, - !val ? VTERM_PROP_MOUSE_NONE : - (num == 1000) ? VTERM_PROP_MOUSE_CLICK : - (num == 1002) ? VTERM_PROP_MOUSE_DRAG : - VTERM_PROP_MOUSE_MOVE); - break; - - case 1004: - settermprop_bool(state, VTERM_PROP_FOCUSREPORT, val); - state->mode.report_focus = val; - break; - - case 1005: - state->mouse_protocol = val ? MOUSE_UTF8 : MOUSE_X10; - break; - - case 1006: - state->mouse_protocol = val ? MOUSE_SGR : MOUSE_X10; - break; - - case 1015: - state->mouse_protocol = val ? MOUSE_RXVT : MOUSE_X10; - break; - - case 1047: - settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); - break; - - case 1048: - savecursor(state, val); - break; - - case 1049: - settermprop_bool(state, VTERM_PROP_ALTSCREEN, val); - savecursor(state, val); - break; - - case 2004: - state->mode.bracketpaste = val; - break; - - default: - DEBUG_LOG("libvterm: Unknown DEC mode %d\n", num); - return; - } -} - -static void request_dec_mode(VTermState *state, int num) -{ - int reply; - - switch(num) { - case 1: - reply = state->mode.cursor; - break; - - case 5: - reply = state->mode.screen; - break; - - case 6: - reply = state->mode.origin; - break; - - case 7: - reply = state->mode.autowrap; - break; - - case 12: - reply = state->mode.cursor_blink; - break; - - case 25: - reply = state->mode.cursor_visible; - break; - - case 69: - reply = state->mode.leftrightmargin; - break; - - case 1000: - reply = state->mouse_flags == MOUSE_WANT_CLICK; - break; - - case 1002: - reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_DRAG); - break; - - case 1003: - reply = state->mouse_flags == (MOUSE_WANT_CLICK|MOUSE_WANT_MOVE); - break; - - case 1004: - reply = state->mode.report_focus; - break; - - case 1005: - reply = state->mouse_protocol == MOUSE_UTF8; - break; - - case 1006: - reply = state->mouse_protocol == MOUSE_SGR; - break; - - case 1015: - reply = state->mouse_protocol == MOUSE_RXVT; - break; - - case 1047: - reply = state->mode.alt_screen; - break; - - case 2004: - reply = state->mode.bracketpaste; - break; - - default: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, 0); - return; - } - - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2); -} - -static void request_version_string(VTermState *state) -{ - vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)", - VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR); -} - -static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user) -{ - VTermState *state = user; - int leader_byte = 0; - int intermed_byte = 0; - int cancel_phantom = 1; - - if(leader && leader[0]) { - if(leader[1]) // longer than 1 char - return 0; - - switch(leader[0]) { - case '?': - case '>': - leader_byte = leader[0]; - break; - default: - return 0; - } - } - - if(intermed && intermed[0]) { - if(intermed[1]) // longer than 1 char - return 0; - - switch(intermed[0]) { - case ' ': - case '!': - case '"': - case '$': - case '\'': - intermed_byte = intermed[0]; - break; - default: - return 0; - } - } - - VTermPos oldpos = state->pos; - - // Some temporaries for later code - int count, val; - int row, col; - VTermRect rect; - int selective; - -#define LBOUND(v,min) if((v) < (min)) (v) = (min) -#define UBOUND(v,max) if((v) > (max)) (v) = (max) - -#define LEADER(l,b) ((l << 8) | b) -#define INTERMED(i,b) ((i << 16) | b) - - switch(intermed_byte << 16 | leader_byte << 8 | command) { - case 0x40: // ICH - ECMA-48 8.3.64 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - if(state->mode.leftrightmargin) - rect.end_col = SCROLLREGION_RIGHT(state); - else - rect.end_col = THISROWWIDTH(state); - - scroll(state, rect, 0, -count); - - break; - - case 0x41: // CUU - ECMA-48 8.3.22 - count = CSI_ARG_COUNT(args[0]); - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x42: // CUD - ECMA-48 8.3.19 - count = CSI_ARG_COUNT(args[0]); - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x43: // CUF - ECMA-48 8.3.20 - count = CSI_ARG_COUNT(args[0]); - state->pos.col += count; - state->at_phantom = 0; - break; - - case 0x44: // CUB - ECMA-48 8.3.18 - count = CSI_ARG_COUNT(args[0]); - state->pos.col -= count; - state->at_phantom = 0; - break; - - case 0x45: // CNL - ECMA-48 8.3.12 - count = CSI_ARG_COUNT(args[0]); - state->pos.col = 0; - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x46: // CPL - ECMA-48 8.3.13 - count = CSI_ARG_COUNT(args[0]); - state->pos.col = 0; - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x47: // CHA - ECMA-48 8.3.9 - val = CSI_ARG_OR(args[0], 1); - state->pos.col = val-1; - state->at_phantom = 0; - break; - - case 0x48: // CUP - ECMA-48 8.3.21 - row = CSI_ARG_OR(args[0], 1); - col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); - // zero-based - state->pos.row = row-1; - state->pos.col = col-1; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - state->at_phantom = 0; - break; - - case 0x49: // CHT - ECMA-48 8.3.10 - count = CSI_ARG_COUNT(args[0]); - tab(state, count, +1); - break; - - case 0x4a: // ED - ECMA-48 8.3.39 - case LEADER('?', 0x4a): // DECSED - Selective Erase in Display - selective = (leader_byte == '?'); - switch(CSI_ARG(args[0])) { - case CSI_ARG_MISSING: - case 0: - rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; rect.end_col = state->cols; - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - rect.start_row = state->pos.row + 1; rect.end_row = state->rows; - rect.start_col = 0; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - if(rect.end_row > rect.start_row) - erase(state, rect, selective); - break; - - case 1: - rect.start_row = 0; rect.end_row = state->pos.row; - rect.start_col = 0; rect.end_col = state->cols; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - rect.start_row = state->pos.row; rect.end_row = state->pos.row + 1; - rect.end_col = state->pos.col + 1; - if(rect.end_row > rect.start_row) - erase(state, rect, selective); - break; - - case 2: - rect.start_row = 0; rect.end_row = state->rows; - rect.start_col = 0; rect.end_col = state->cols; - for(int row_ = rect.start_row; row_ < rect.end_row; row_++) - set_lineinfo(state, row_, FORCE, DWL_OFF, DHL_OFF); - erase(state, rect, selective); - break; - - case 3: - if(state->callbacks && state->callbacks->sb_clear) - if((*state->callbacks->sb_clear)(state->cbdata)) - return 1; - break; - } - break; - - case 0x4b: // EL - ECMA-48 8.3.41 - case LEADER('?', 0x4b): // DECSEL - Selective Erase in Line - selective = (leader_byte == '?'); - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - - switch(CSI_ARG(args[0])) { - case CSI_ARG_MISSING: - case 0: - rect.start_col = state->pos.col; rect.end_col = THISROWWIDTH(state); break; - case 1: - rect.start_col = 0; rect.end_col = state->pos.col + 1; break; - case 2: - rect.start_col = 0; rect.end_col = THISROWWIDTH(state); break; - default: - return 0; - } - - if(rect.end_col > rect.start_col) - erase(state, rect, selective); - - break; - - case 0x4c: // IL - ECMA-48 8.3.67 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, -count, 0); - - break; - - case 0x4d: // DL - ECMA-48 8.3.32 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, count, 0); - - break; - - case 0x50: // DCH - ECMA-48 8.3.26 - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - if(state->mode.leftrightmargin) - rect.end_col = SCROLLREGION_RIGHT(state); - else - rect.end_col = THISROWWIDTH(state); - - scroll(state, rect, 0, count); - - break; - - case 0x53: // SU - ECMA-48 8.3.147 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, count, 0); - - break; - - case 0x54: // SD - ECMA-48 8.3.113 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = SCROLLREGION_LEFT(state); - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, -count, 0); - - break; - - case 0x58: // ECH - ECMA-48 8.3.38 - count = CSI_ARG_COUNT(args[0]); - - rect.start_row = state->pos.row; - rect.end_row = state->pos.row + 1; - rect.start_col = state->pos.col; - rect.end_col = state->pos.col + count; - UBOUND(rect.end_col, THISROWWIDTH(state)); - - erase(state, rect, 0); - break; - - case 0x5a: // CBT - ECMA-48 8.3.7 - count = CSI_ARG_COUNT(args[0]); - tab(state, count, -1); - break; - - case 0x60: // HPA - ECMA-48 8.3.57 - col = CSI_ARG_OR(args[0], 1); - state->pos.col = col-1; - state->at_phantom = 0; - break; - - case 0x61: // HPR - ECMA-48 8.3.59 - count = CSI_ARG_COUNT(args[0]); - state->pos.col += count; - state->at_phantom = 0; - break; - - case 0x62: { // REP - ECMA-48 8.3.103 - const int row_width = THISROWWIDTH(state); - count = CSI_ARG_COUNT(args[0]); - col = state->pos.col + count; - UBOUND(col, row_width); - schar_T sc = schar_from_buf(state->grapheme_buf, state->grapheme_len); - while (state->pos.col < col) { - putglyph(state, sc, state->combine_width, state->pos); - state->pos.col += state->combine_width; - } - if (state->pos.col + state->combine_width >= row_width) { - if (state->mode.autowrap) { - state->at_phantom = 1; - cancel_phantom = 0; - } - } - break; - } - - case 0x63: // DA - ECMA-48 8.3.24 - val = CSI_ARG_OR(args[0], 0); - if(val == 0) - // DEC VT100 response - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?1;2c"); - break; - - case LEADER('>', 0x63): // DEC secondary Device Attributes - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, ">%d;%d;%dc", 0, 100, 0); - break; - - case 0x64: // VPA - ECMA-48 8.3.158 - row = CSI_ARG_OR(args[0], 1); - state->pos.row = row-1; - if(state->mode.origin) - state->pos.row += state->scrollregion_top; - state->at_phantom = 0; - break; - - case 0x65: // VPR - ECMA-48 8.3.160 - count = CSI_ARG_COUNT(args[0]); - state->pos.row += count; - state->at_phantom = 0; - break; - - case 0x66: // HVP - ECMA-48 8.3.63 - row = CSI_ARG_OR(args[0], 1); - col = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? 1 : CSI_ARG(args[1]); - // zero-based - state->pos.row = row-1; - state->pos.col = col-1; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - state->at_phantom = 0; - break; - - case 0x67: // TBC - ECMA-48 8.3.154 - val = CSI_ARG_OR(args[0], 0); - - switch(val) { - case 0: - clear_col_tabstop(state, state->pos.col); - break; - case 3: - case 5: - for(col = 0; col < state->cols; col++) - clear_col_tabstop(state, col); - break; - case 1: - case 2: - case 4: - break; - /* TODO: 1, 2 and 4 aren't meaningful yet without line tab stops */ - default: - return 0; - } - break; - - case 0x68: // SM - ECMA-48 8.3.125 - if(!CSI_ARG_IS_MISSING(args[0])) - set_mode(state, CSI_ARG(args[0]), 1); - break; - - case LEADER('?', 0x68): // DEC private mode set - for(int i = 0; i < argcount; i++) { - if(!CSI_ARG_IS_MISSING(args[i])) - set_dec_mode(state, CSI_ARG(args[i]), 1); - } - break; - - case 0x6a: // HPB - ECMA-48 8.3.58 - count = CSI_ARG_COUNT(args[0]); - state->pos.col -= count; - state->at_phantom = 0; - break; - - case 0x6b: // VPB - ECMA-48 8.3.159 - count = CSI_ARG_COUNT(args[0]); - state->pos.row -= count; - state->at_phantom = 0; - break; - - case 0x6c: // RM - ECMA-48 8.3.106 - if(!CSI_ARG_IS_MISSING(args[0])) - set_mode(state, CSI_ARG(args[0]), 0); - break; - - case LEADER('?', 0x6c): // DEC private mode reset - for(int i = 0; i < argcount; i++) { - if(!CSI_ARG_IS_MISSING(args[i])) - set_dec_mode(state, CSI_ARG(args[i]), 0); - } - break; - - case 0x6d: // SGR - ECMA-48 8.3.117 - vterm_state_setpen(state, args, argcount); - break; - - case LEADER('?', 0x6d): // DECSGR - /* No actual DEC terminal recognised these, but some printers did. These - * are alternative ways to request subscript/superscript/off - */ - for(int argi = 0; argi < argcount; argi++) { - long arg; - switch(arg = CSI_ARG(args[argi])) { - case 4: // Superscript on - arg = 73; - vterm_state_setpen(state, &arg, 1); - break; - case 5: // Subscript on - arg = 74; - vterm_state_setpen(state, &arg, 1); - break; - case 24: // Super+subscript off - arg = 75; - vterm_state_setpen(state, &arg, 1); - break; - } - } - break; - - case 0x6e: // DSR - ECMA-48 8.3.35 - case LEADER('?', 0x6e): // DECDSR - val = CSI_ARG_OR(args[0], 0); - - { - char *qmark = (leader_byte == '?') ? "?" : ""; - - switch(val) { - case 0: case 1: case 2: case 3: case 4: - // ignore - these are replies - break; - case 5: - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s0n", qmark); - break; - case 6: // CPR - cursor position report - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "%s%d;%dR", qmark, state->pos.row + 1, state->pos.col + 1); - break; - } - } - break; - - - case INTERMED('!', 0x70): // DECSTR - DEC soft terminal reset - vterm_state_reset(state, 0); - break; - - case LEADER('?', INTERMED('$', 0x70)): - request_dec_mode(state, CSI_ARG(args[0])); - break; - - case LEADER('>', 0x71): // XTVERSION - xterm query version string - request_version_string(state); - break; - - case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape - val = CSI_ARG_OR(args[0], 1); - - switch(val) { - case 0: case 1: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - break; - case 2: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - break; - case 3: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); - break; - case 4: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_UNDERLINE); - break; - case 5: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); - break; - case 6: - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 0); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BAR_LEFT); - break; - } - - break; - - case INTERMED('"', 0x71): // DECSCA - DEC select character protection attribute - val = CSI_ARG_OR(args[0], 0); - - switch(val) { - case 0: case 2: - state->protected_cell = 0; - break; - case 1: - state->protected_cell = 1; - break; - } - - break; - - case 0x72: // DECSTBM - DEC custom - state->scrollregion_top = CSI_ARG_OR(args[0], 1) - 1; - state->scrollregion_bottom = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_top, 0); - UBOUND(state->scrollregion_top, state->rows); - LBOUND(state->scrollregion_bottom, -1); - if(state->scrollregion_top == 0 && state->scrollregion_bottom == state->rows) - state->scrollregion_bottom = -1; - else - UBOUND(state->scrollregion_bottom, state->rows); - - if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { - // Invalid - state->scrollregion_top = 0; - state->scrollregion_bottom = -1; - } - - // Setting the scrolling region restores the cursor to the home position - state->pos.row = 0; - state->pos.col = 0; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - - break; - - case 0x73: // DECSLRM - DEC custom - // Always allow setting these margins, just they won't take effect without DECVSSM - state->scrollregion_left = CSI_ARG_OR(args[0], 1) - 1; - state->scrollregion_right = argcount < 2 || CSI_ARG_IS_MISSING(args[1]) ? -1 : CSI_ARG(args[1]); - LBOUND(state->scrollregion_left, 0); - UBOUND(state->scrollregion_left, state->cols); - LBOUND(state->scrollregion_right, -1); - if(state->scrollregion_left == 0 && state->scrollregion_right == state->cols) - state->scrollregion_right = -1; - else - UBOUND(state->scrollregion_right, state->cols); - - if(state->scrollregion_right > -1 && - state->scrollregion_right <= state->scrollregion_left) { - // Invalid - state->scrollregion_left = 0; - state->scrollregion_right = -1; - } - - // Setting the scrolling region restores the cursor to the home position - state->pos.row = 0; - state->pos.col = 0; - if(state->mode.origin) { - state->pos.row += state->scrollregion_top; - state->pos.col += SCROLLREGION_LEFT(state); - } - - break; - - case INTERMED('\'', 0x7D): // DECIC - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = state->pos.col; - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, 0, -count); - - break; - - case INTERMED('\'', 0x7E): // DECDC - count = CSI_ARG_COUNT(args[0]); - - if(!is_cursor_in_scrollregion(state)) - break; - - rect.start_row = state->scrollregion_top; - rect.end_row = SCROLLREGION_BOTTOM(state); - rect.start_col = state->pos.col; - rect.end_col = SCROLLREGION_RIGHT(state); - - scroll(state, rect, 0, count); - - break; - - default: - if(state->fallbacks && state->fallbacks->csi) - if((*state->fallbacks->csi)(leader, args, argcount, intermed, command, state->fbdata)) - return 1; - - return 0; - } - - if(state->mode.origin) { - LBOUND(state->pos.row, state->scrollregion_top); - UBOUND(state->pos.row, SCROLLREGION_BOTTOM(state)-1); - LBOUND(state->pos.col, SCROLLREGION_LEFT(state)); - UBOUND(state->pos.col, SCROLLREGION_RIGHT(state)-1); - } - else { - LBOUND(state->pos.row, 0); - UBOUND(state->pos.row, state->rows-1); - LBOUND(state->pos.col, 0); - UBOUND(state->pos.col, THISROWWIDTH(state)-1); - } - - updatecursor(state, &oldpos, cancel_phantom); - -#ifdef DEBUG - if(state->pos.row < 0 || state->pos.row >= state->rows || - state->pos.col < 0 || state->pos.col >= state->cols) { - fprintf(stderr, "Position out of bounds after CSI %c: (%d,%d)\n", - command, state->pos.row, state->pos.col); - abort(); - } - - if(SCROLLREGION_BOTTOM(state) <= state->scrollregion_top) { - fprintf(stderr, "Scroll region height out of bounds after CSI %c: %d <= %d\n", - command, SCROLLREGION_BOTTOM(state), state->scrollregion_top); - abort(); - } - - if(SCROLLREGION_RIGHT(state) <= SCROLLREGION_LEFT(state)) { - fprintf(stderr, "Scroll region width out of bounds after CSI %c: %d <= %d\n", - command, SCROLLREGION_RIGHT(state), SCROLLREGION_LEFT(state)); - abort(); - } -#endif - - return 1; -} - -static char base64_one(uint8_t b) -{ - if(b < 26) - return 'A' + b; - else if(b < 52) - return 'a' + b - 26; - else if(b < 62) - return '0' + b - 52; - else if(b == 62) - return '+'; - else if(b == 63) - return '/'; - return 0; -} - -static uint8_t unbase64one(char c) -{ - if(c >= 'A' && c <= 'Z') - return c - 'A'; - else if(c >= 'a' && c <= 'z') - return c - 'a' + 26; - else if(c >= '0' && c <= '9') - return c - '0' + 52; - else if(c == '+') - return 62; - else if(c == '/') - return 63; - - return 0xFF; -} - -static void osc_selection(VTermState *state, VTermStringFragment frag) -{ - if(frag.initial) { - state->tmp.selection.mask = 0; - state->tmp.selection.state = SELECTION_INITIAL; - } - - while(!state->tmp.selection.state && frag.len) { - /* Parse selection parameter */ - switch(frag.str[0]) { - case 'c': - state->tmp.selection.mask |= VTERM_SELECTION_CLIPBOARD; - break; - case 'p': - state->tmp.selection.mask |= VTERM_SELECTION_PRIMARY; - break; - case 'q': - state->tmp.selection.mask |= VTERM_SELECTION_SECONDARY; - break; - case 's': - state->tmp.selection.mask |= VTERM_SELECTION_SELECT; - break; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - state->tmp.selection.mask |= (VTERM_SELECTION_CUT0 << (frag.str[0] - '0')); - break; - - case ';': - state->tmp.selection.state = SELECTION_SELECTED; - if(!state->tmp.selection.mask) - state->tmp.selection.mask = VTERM_SELECTION_SELECT|VTERM_SELECTION_CUT0; - break; - } - - frag.str++; - frag.len--; - } - - if(!frag.len) { - /* Clear selection if we're already finished but didn't do anything */ - if(frag.final && state->selection.callbacks->set) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = NULL, - .len = 0, - .initial = state->tmp.selection.state != SELECTION_SET, - .final = true, - }, state->selection.user); - } - return; - } - - if(state->tmp.selection.state == SELECTION_SELECTED) { - if(frag.str[0] == '?') { - state->tmp.selection.state = SELECTION_QUERY; - } - else { - state->tmp.selection.state = SELECTION_SET_INITIAL; - state->tmp.selection.recvpartial = 0; - } - } - - if(state->tmp.selection.state == SELECTION_QUERY) { - if(state->selection.callbacks->query) - (*state->selection.callbacks->query)(state->tmp.selection.mask, state->selection.user); - return; - } - - if(state->tmp.selection.state == SELECTION_INVALID) - return; - - if(state->selection.callbacks->set) { - size_t bufcur = 0; - char *buffer = state->selection.buffer; - - uint32_t x = 0; /* Current decoding value */ - int n = 0; /* Number of sextets consumed */ - - if(state->tmp.selection.recvpartial) { - n = state->tmp.selection.recvpartial >> 24; - x = state->tmp.selection.recvpartial & 0x03FFFF; /* could be up to 18 bits of state in here */ - - state->tmp.selection.recvpartial = 0; - } - - while((state->selection.buflen - bufcur) >= 3 && frag.len) { - if(frag.str[0] == '=') { - if(n == 2) { - buffer[0] = (x >> 4) & 0xFF; - buffer += 1, bufcur += 1; - } - if(n == 3) { - buffer[0] = (x >> 10) & 0xFF; - buffer[1] = (x >> 2) & 0xFF; - buffer += 2, bufcur += 2; - } - - while(frag.len && frag.str[0] == '=') - frag.str++, frag.len--; - - n = 0; - } - else { - uint8_t b = unbase64one(frag.str[0]); - if(b == 0xFF) { - DEBUG_LOG("base64decode bad input %02X\n", (uint8_t)frag.str[0]); - - state->tmp.selection.state = SELECTION_INVALID; - if(state->selection.callbacks->set) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = NULL, - .len = 0, - .initial = true, - .final = true, - }, state->selection.user); - } - break; - } - - x = (x << 6) | b; - n++; - frag.str++, frag.len--; - - if(n == 4) { - buffer[0] = (x >> 16) & 0xFF; - buffer[1] = (x >> 8) & 0xFF; - buffer[2] = (x >> 0) & 0xFF; - - buffer += 3, bufcur += 3; - x = 0; - n = 0; - } - } - - if(!frag.len || (state->selection.buflen - bufcur) < 3) { - if(bufcur) { - (*state->selection.callbacks->set)(state->tmp.selection.mask, (VTermStringFragment){ - .str = state->selection.buffer, - .len = bufcur, - .initial = state->tmp.selection.state == SELECTION_SET_INITIAL, - .final = frag.final && !frag.len, - }, state->selection.user); - state->tmp.selection.state = SELECTION_SET; - } - - buffer = state->selection.buffer; - bufcur = 0; - } - } - - if(n) - state->tmp.selection.recvpartial = (n << 24) | x; - } -} - -static int on_osc(int command, VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - switch(command) { - case 0: - settermprop_string(state, VTERM_PROP_ICONNAME, frag); - settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; - - case 1: - settermprop_string(state, VTERM_PROP_ICONNAME, frag); - return 1; - - case 2: - settermprop_string(state, VTERM_PROP_TITLE, frag); - return 1; - - case 52: - if(state->selection.callbacks) - osc_selection(state, frag); - - return 1; - - default: - if(state->fallbacks && state->fallbacks->osc) - if((*state->fallbacks->osc)(command, frag, state->fbdata)) - return 1; - } - - return 0; -} - -static void request_status_string(VTermState *state, VTermStringFragment frag) -{ - VTerm *vt = state->vt; - - char *tmp = state->tmp.decrqss; - - if(frag.initial) - tmp[0] = tmp[1] = tmp[2] = tmp[3] = 0; - - int i = 0; - while(i < sizeof(state->tmp.decrqss)-1 && tmp[i]) - i++; - while(i < sizeof(state->tmp.decrqss)-1 && frag.len--) - tmp[i++] = (frag.str++)[0]; - tmp[i] = 0; - - if(!frag.final) - return; - - switch(tmp[0] | tmp[1]<<8 | tmp[2]<<16) { - case 'm': { - // Query SGR - long args[20]; - int argc = vterm_state_getpen(state, args, sizeof(args)/sizeof(args[0])); - size_t cur = 0; - - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "\x90" "1$r" : ESC_S "P" "1$r"); // DCS 1$r ... - if(cur >= vt->tmpbuffer_len) - return; - - for(int argi = 0; argi < argc; argi++) { - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - argi == argc - 1 ? "%ld" : - CSI_ARG_HAS_MORE(args[argi]) ? "%ld:" : - "%ld;", - CSI_ARG(args[argi])); - if(cur >= vt->tmpbuffer_len) - return; - } - - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "m" "\x9C" : "m" ESC_S "\\"); // ... m ST - if(cur >= vt->tmpbuffer_len) - return; - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); - return; - } - - case 'r': - // Query DECSTBM - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d;%dr", state->scrollregion_top+1, SCROLLREGION_BOTTOM(state)); - return; - - case 's': - // Query DECSLRM - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d;%ds", SCROLLREGION_LEFT(state)+1, SCROLLREGION_RIGHT(state)); - return; - - case ' '|('q'<<8): { - // Query DECSCUSR - int reply; - switch(state->mode.cursor_shape) { - case VTERM_PROP_CURSORSHAPE_BLOCK: reply = 2; break; - case VTERM_PROP_CURSORSHAPE_UNDERLINE: reply = 4; break; - case VTERM_PROP_CURSORSHAPE_BAR_LEFT: reply = 6; break; - } - if(state->mode.cursor_blink) - reply--; - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d q", reply); - return; - } - - case '\"'|('q'<<8): - // Query DECSCA - vterm_push_output_sprintf_str(vt, C1_DCS, true, - "1$r%d\"q", state->protected_cell ? 1 : 2); - return; - } - - vterm_push_output_sprintf_str(state->vt, C1_DCS, true, "0$r"); -} - -static int on_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(commandlen == 2 && strneq(command, "$q", 2)) { - request_status_string(state, frag); - return 1; - } - else if(state->fallbacks && state->fallbacks->dcs) - if((*state->fallbacks->dcs)(command, commandlen, frag, state->fbdata)) - return 1; - - DEBUG_LOG("libvterm: Unhandled DCS %.*s\n", (int)commandlen, command); - return 0; -} - -static int on_apc(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->apc) - if((*state->fallbacks->apc)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all APCs are unhandled */ - return 0; -} - -static int on_pm(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->pm) - if((*state->fallbacks->pm)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all PMs are unhandled */ - return 0; -} - -static int on_sos(VTermStringFragment frag, void *user) -{ - VTermState *state = user; - - if(state->fallbacks && state->fallbacks->sos) - if((*state->fallbacks->sos)(frag, state->fbdata)) - return 1; - - /* No DEBUG_LOG because all SOSs are unhandled */ - return 0; -} - -static int on_resize(int rows, int cols, void *user) -{ - VTermState *state = user; - VTermPos oldpos = state->pos; - - if(cols != state->cols) { - unsigned char *newtabstops = vterm_allocator_malloc(state->vt, (cols + 7) / 8); - - /* TODO: This can all be done much more efficiently bytewise */ - int col; - for(col = 0; col < state->cols && col < cols; col++) { - unsigned char mask = 1 << (col & 7); - if(state->tabstops[col >> 3] & mask) - newtabstops[col >> 3] |= mask; - else - newtabstops[col >> 3] &= ~mask; - } - - for( ; col < cols; col++) { - unsigned char mask = 1 << (col & 7); - if(col % 8 == 0) - newtabstops[col >> 3] |= mask; - else - newtabstops[col >> 3] &= ~mask; - } - - vterm_allocator_free(state->vt, state->tabstops); - state->tabstops = newtabstops; - } - - state->rows = rows; - state->cols = cols; - - if(state->scrollregion_bottom > -1) - UBOUND(state->scrollregion_bottom, state->rows); - if(state->scrollregion_right > -1) - UBOUND(state->scrollregion_right, state->cols); - - VTermStateFields fields = { - .pos = state->pos, - .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] }, - }; - - if(state->callbacks && state->callbacks->resize) { - (*state->callbacks->resize)(rows, cols, &fields, state->cbdata); - state->pos = fields.pos; - - state->lineinfos[0] = fields.lineinfos[0]; - state->lineinfos[1] = fields.lineinfos[1]; - } - else { - if(rows != state->rows) { - for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) { - VTermLineInfo *oldlineinfo = state->lineinfos[bufidx]; - if(!oldlineinfo) - continue; - - VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo)); - - int row; - for(row = 0; row < state->rows && row < rows; row++) { - newlineinfo[row] = oldlineinfo[row]; - } - - for( ; row < rows; row++) { - newlineinfo[row] = (VTermLineInfo){ - .doublewidth = 0, - }; - } - - vterm_allocator_free(state->vt, state->lineinfos[bufidx]); - state->lineinfos[bufidx] = newlineinfo; - } - } - } - - state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; - - if(state->at_phantom && state->pos.col < cols-1) { - state->at_phantom = 0; - state->pos.col++; - } - - if(state->pos.row < 0) - state->pos.row = 0; - if(state->pos.row >= rows) - state->pos.row = rows - 1; - if(state->pos.col < 0) - state->pos.col = 0; - if(state->pos.col >= cols) - state->pos.col = cols - 1; - - updatecursor(state, &oldpos, 1); - - return 1; -} - -static const VTermParserCallbacks parser_callbacks = { - .text = on_text, - .control = on_control, - .escape = on_escape, - .csi = on_csi, - .osc = on_osc, - .dcs = on_dcs, - .apc = on_apc, - .pm = on_pm, - .sos = on_sos, - .resize = on_resize, -}; - -VTermState *vterm_obtain_state(VTerm *vt) -{ - if(vt->state) - return vt->state; - - VTermState *state = vterm_state_new(vt); - vt->state = state; - - vterm_parser_set_callbacks(vt, &parser_callbacks, state); - - return state; -} - -void vterm_state_reset(VTermState *state, int hard) -{ - state->scrollregion_top = 0; - state->scrollregion_bottom = -1; - state->scrollregion_left = 0; - state->scrollregion_right = -1; - - state->mode.keypad = 0; - state->mode.cursor = 0; - state->mode.autowrap = 1; - state->mode.insert = 0; - state->mode.newline = 0; - state->mode.alt_screen = 0; - state->mode.origin = 0; - state->mode.leftrightmargin = 0; - state->mode.bracketpaste = 0; - state->mode.report_focus = 0; - - state->mouse_flags = 0; - - state->vt->mode.ctrl8bit = 0; - - for(int col = 0; col < state->cols; col++) - if(col % 8 == 0) - set_col_tabstop(state, col); - else - clear_col_tabstop(state, col); - - for(int row = 0; row < state->rows; row++) - set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF); - - if(state->callbacks && state->callbacks->initpen) - (*state->callbacks->initpen)(state->cbdata); - - vterm_state_resetpen(state); - - VTermEncoding *default_enc = state->vt->mode.utf8 ? - vterm_lookup_encoding(ENC_UTF8, 'u') : - vterm_lookup_encoding(ENC_SINGLE_94, 'B'); - - for(int i = 0; i < 4; i++) { - state->encoding[i].enc = default_enc; - if(default_enc->init) - (*default_enc->init)(default_enc, state->encoding[i].data); - } - - state->gl_set = 0; - state->gr_set = 1; - state->gsingle_set = 0; - - state->protected_cell = 0; - - // Initialise the props - settermprop_bool(state, VTERM_PROP_CURSORVISIBLE, 1); - settermprop_bool(state, VTERM_PROP_CURSORBLINK, 1); - settermprop_int (state, VTERM_PROP_CURSORSHAPE, VTERM_PROP_CURSORSHAPE_BLOCK); - - if(hard) { - state->pos.row = 0; - state->pos.col = 0; - state->at_phantom = 0; - - VTermRect rect = { 0, state->rows, 0, state->cols }; - erase(state, rect, 0); - } -} - -void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) -{ - *cursorpos = state->pos; -} - -void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user) -{ - if(callbacks) { - state->callbacks = callbacks; - state->cbdata = user; - - if(state->callbacks && state->callbacks->initpen) - (*state->callbacks->initpen)(state->cbdata); - } - else { - state->callbacks = NULL; - state->cbdata = NULL; - } -} - -void *vterm_state_get_cbdata(VTermState *state) -{ - return state->cbdata; -} - -void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user) -{ - if(fallbacks) { - state->fallbacks = fallbacks; - state->fbdata = user; - } - else { - state->fallbacks = NULL; - state->fbdata = NULL; - } -} - -void *vterm_state_get_unrecognised_fbdata(VTermState *state) -{ - return state->fbdata; -} - -int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val) -{ - /* Only store the new value of the property if usercode said it was happy. - * This is especially important for altscreen switching */ - if(state->callbacks && state->callbacks->settermprop) - if(!(*state->callbacks->settermprop)(prop, val, state->cbdata)) - return 0; - - switch(prop) { - case VTERM_PROP_TITLE: - case VTERM_PROP_ICONNAME: - // we don't store these, just transparently pass through - return 1; - case VTERM_PROP_CURSORVISIBLE: - state->mode.cursor_visible = val->boolean; - return 1; - case VTERM_PROP_CURSORBLINK: - state->mode.cursor_blink = val->boolean; - return 1; - case VTERM_PROP_CURSORSHAPE: - state->mode.cursor_shape = val->number; - return 1; - case VTERM_PROP_REVERSE: - state->mode.screen = val->boolean; - return 1; - case VTERM_PROP_ALTSCREEN: - state->mode.alt_screen = val->boolean; - state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY]; - if(state->mode.alt_screen) { - VTermRect rect = { - .start_row = 0, - .start_col = 0, - .end_row = state->rows, - .end_col = state->cols, - }; - erase(state, rect, 0); - } - return 1; - case VTERM_PROP_MOUSE: - state->mouse_flags = 0; - if(val->number) - state->mouse_flags |= MOUSE_WANT_CLICK; - if(val->number == VTERM_PROP_MOUSE_DRAG) - state->mouse_flags |= MOUSE_WANT_DRAG; - if(val->number == VTERM_PROP_MOUSE_MOVE) - state->mouse_flags |= MOUSE_WANT_MOVE; - return 1; - case VTERM_PROP_FOCUSREPORT: - state->mode.report_focus = val->boolean; - return 1; - - case VTERM_N_PROPS: - return 0; - } - - return 0; -} - -void vterm_state_focus_in(VTermState *state) -{ - if(state->mode.report_focus) - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "I"); -} - -void vterm_state_focus_out(VTermState *state) -{ - if(state->mode.report_focus) - vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "O"); -} - -const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row) -{ - return state->lineinfo + row; -} - -void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, - char *buffer, size_t buflen) -{ - if(buflen && !buffer) - buffer = vterm_allocator_malloc(state->vt, buflen); - - state->selection.callbacks = callbacks; - state->selection.user = user; - state->selection.buffer = buffer; - state->selection.buflen = buflen; -} - -void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag) -{ - VTerm *vt = state->vt; - - if(frag.initial) { - /* TODO: support sending more than one mask bit */ - static const char selection_chars[] = "cpqs"; - int idx; - for(idx = 0; idx < 4; idx++) - if(mask & (1 << idx)) - break; - - vterm_push_output_sprintf_str(vt, C1_OSC, false, "52;%c;", selection_chars[idx]); - - state->tmp.selection.sendpartial = 0; - } - - if(frag.len) { - size_t bufcur = 0; - char *buffer = state->selection.buffer; - - uint32_t x = 0; - int n = 0; - - if(state->tmp.selection.sendpartial) { - n = state->tmp.selection.sendpartial >> 24; - x = state->tmp.selection.sendpartial & 0xFFFFFF; - - state->tmp.selection.sendpartial = 0; - } - - while((state->selection.buflen - bufcur) >= 4 && frag.len) { - x = (x << 8) | frag.str[0]; - n++; - frag.str++, frag.len--; - - if(n == 3) { - buffer[0] = base64_one((x >> 18) & 0x3F); - buffer[1] = base64_one((x >> 12) & 0x3F); - buffer[2] = base64_one((x >> 6) & 0x3F); - buffer[3] = base64_one((x >> 0) & 0x3F); - - buffer += 4, bufcur += 4; - x = 0; - n = 0; - } - - if(!frag.len || (state->selection.buflen - bufcur) < 4) { - if(bufcur) - vterm_push_output_bytes(vt, state->selection.buffer, bufcur); - - buffer = state->selection.buffer; - bufcur = 0; - } - } - - if(n) - state->tmp.selection.sendpartial = (n << 24) | x; - } - - if(frag.final) { - if(state->tmp.selection.sendpartial) { - int n = state->tmp.selection.sendpartial >> 24; - uint32_t x = state->tmp.selection.sendpartial & 0xFFFFFF; - char *buffer = state->selection.buffer; - - /* n is either 1 or 2 now */ - x <<= (n == 1) ? 16 : 8; - - buffer[0] = base64_one((x >> 18) & 0x3F); - buffer[1] = base64_one((x >> 12) & 0x3F); - buffer[2] = (n == 1) ? '=' : base64_one((x >> 6) & 0x3F); - buffer[3] = '='; - - vterm_push_output_sprintf_str(vt, 0, true, "%.*s", 4, buffer); - } - else - vterm_push_output_sprintf_str(vt, 0, true, ""); - } -} diff --git a/src/vterm/vterm.c b/src/vterm/vterm.c deleted file mode 100644 index 530c513755..0000000000 --- a/src/vterm/vterm.c +++ /dev/null @@ -1,432 +0,0 @@ -#include "vterm_internal.h" - -#include "auto/config.h" -#include <stdarg.h> -#include <stdio.h> -#include <stdlib.h> -#include <string.h> - -/***************** - * API functions * - *****************/ - -static void *default_malloc(size_t size, void *allocdata) -{ - void *ptr = malloc(size); - if(ptr) - memset(ptr, 0, size); - return ptr; -} - -static void default_free(void *ptr, void *allocdata) -{ - free(ptr); -} - -static VTermAllocatorFunctions default_allocator = { - .malloc = &default_malloc, - .free = &default_free, -}; - -VTerm *vterm_new(int rows, int cols) -{ - return vterm_build(&(const struct VTermBuilder){ - .rows = rows, - .cols = cols, - }); -} - -VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata) -{ - return vterm_build(&(const struct VTermBuilder){ - .rows = rows, - .cols = cols, - .allocator = funcs, - .allocdata = allocdata, - }); -} - -/* A handy macro for defaulting values out of builder fields */ -#define DEFAULT(v, def) ((v) ? (v) : (def)) - -VTerm *vterm_build(const struct VTermBuilder *builder) -{ - const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator); - - /* Need to bootstrap using the allocator function directly */ - VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata); - - vt->allocator = allocator; - vt->allocdata = builder->allocdata; - - vt->rows = builder->rows; - vt->cols = builder->cols; - - vt->parser.state = NORMAL; - - vt->parser.callbacks = NULL; - vt->parser.cbdata = NULL; - - vt->parser.emit_nul = false; - - vt->outfunc = NULL; - vt->outdata = NULL; - - vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096); - vt->outbuffer_cur = 0; - vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len); - - vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096); - vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len); - - return vt; -} - -void vterm_free(VTerm *vt) -{ - if(vt->screen) - vterm_screen_free(vt->screen); - - if(vt->state) - vterm_state_free(vt->state); - - vterm_allocator_free(vt, vt->outbuffer); - vterm_allocator_free(vt, vt->tmpbuffer); - - vterm_allocator_free(vt, vt); -} - -INTERNAL void *vterm_allocator_malloc(VTerm *vt, size_t size) -{ - return (*vt->allocator->malloc)(size, vt->allocdata); -} - -INTERNAL void vterm_allocator_free(VTerm *vt, void *ptr) -{ - (*vt->allocator->free)(ptr, vt->allocdata); -} - -void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp) -{ - if(rowsp) - *rowsp = vt->rows; - if(colsp) - *colsp = vt->cols; -} - -void vterm_set_size(VTerm *vt, int rows, int cols) -{ - if(rows < 1 || cols < 1) - return; - - vt->rows = rows; - vt->cols = cols; - - if(vt->parser.callbacks && vt->parser.callbacks->resize) - (*vt->parser.callbacks->resize)(rows, cols, vt->parser.cbdata); -} - -int vterm_get_utf8(const VTerm *vt) -{ - return vt->mode.utf8; -} - -void vterm_set_utf8(VTerm *vt, int is_utf8) -{ - vt->mode.utf8 = is_utf8; -} - -void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user) -{ - vt->outfunc = func; - vt->outdata = user; -} - -INTERNAL void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len) -{ - if(vt->outfunc) { - (vt->outfunc)(bytes, len, vt->outdata); - return; - } - - if(len > vt->outbuffer_len - vt->outbuffer_cur) - return; - - memcpy(vt->outbuffer + vt->outbuffer_cur, bytes, len); - vt->outbuffer_cur += len; -} - -INTERNAL void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args) -{ - size_t len = vsnprintf(vt->tmpbuffer, vt->tmpbuffer_len, - format, args); - - vterm_push_output_bytes(vt, vt->tmpbuffer, len); -} - -INTERNAL void vterm_push_output_sprintf(VTerm *vt, const char *format, ...) -{ - va_list args; - va_start(args, format); - vterm_push_output_vsprintf(vt, format, args); - va_end(args); -} - -INTERNAL void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...) -{ - size_t cur; - - if(ctrl >= 0x80 && !vt->mode.ctrl8bit) - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - ESC_S "%c", ctrl - 0x40); - else - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - "%c", ctrl); - - if(cur >= vt->tmpbuffer_len) - return; - - va_list args; - va_start(args, fmt); - cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - fmt, args); - va_end(args); - - if(cur >= vt->tmpbuffer_len) - return; - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); -} - -INTERNAL void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...) -{ - size_t cur = 0; - - if(ctrl) { - if(ctrl >= 0x80 && !vt->mode.ctrl8bit) - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - ESC_S "%c", ctrl - 0x40); - else - cur = snprintf(vt->tmpbuffer, vt->tmpbuffer_len, - "%c", ctrl); - - if(cur >= vt->tmpbuffer_len) - return; - } - - va_list args; - va_start(args, fmt); - cur += vsnprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - fmt, args); - va_end(args); - - if(cur >= vt->tmpbuffer_len) - return; - - if(term) { - cur += snprintf(vt->tmpbuffer + cur, vt->tmpbuffer_len - cur, - vt->mode.ctrl8bit ? "\x9C" : ESC_S "\\"); // ST - - if(cur >= vt->tmpbuffer_len) - return; - } - - vterm_push_output_bytes(vt, vt->tmpbuffer, cur); -} - -size_t vterm_output_get_buffer_size(const VTerm *vt) -{ - return vt->outbuffer_len; -} - -size_t vterm_output_get_buffer_current(const VTerm *vt) -{ - return vt->outbuffer_cur; -} - -size_t vterm_output_get_buffer_remaining(const VTerm *vt) -{ - return vt->outbuffer_len - vt->outbuffer_cur; -} - -size_t vterm_output_read(VTerm *vt, char *buffer, size_t len) -{ - if(len > vt->outbuffer_cur) - len = vt->outbuffer_cur; - - memcpy(buffer, vt->outbuffer, len); - - if(len < vt->outbuffer_cur) - memmove(vt->outbuffer, vt->outbuffer + len, vt->outbuffer_cur - len); - - vt->outbuffer_cur -= len; - - return len; -} - -VTermValueType vterm_get_attr_type(VTermAttr attr) -{ - switch(attr) { - case VTERM_ATTR_BOLD: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_UNDERLINE: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_ITALIC: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_BLINK: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_REVERSE: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_CONCEAL: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_STRIKE: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR; - case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR; - case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL; - case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT; - case VTERM_ATTR_URI: return VTERM_VALUETYPE_INT; - - case VTERM_N_ATTRS: return 0; - } - return 0; /* UNREACHABLE */ -} - -VTermValueType vterm_get_prop_type(VTermProp prop) -{ - switch(prop) { - case VTERM_PROP_CURSORVISIBLE: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_CURSORBLINK: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_ALTSCREEN: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_TITLE: return VTERM_VALUETYPE_STRING; - case VTERM_PROP_ICONNAME: return VTERM_VALUETYPE_STRING; - case VTERM_PROP_REVERSE: return VTERM_VALUETYPE_BOOL; - case VTERM_PROP_CURSORSHAPE: return VTERM_VALUETYPE_INT; - case VTERM_PROP_MOUSE: return VTERM_VALUETYPE_INT; - case VTERM_PROP_FOCUSREPORT: return VTERM_VALUETYPE_BOOL; - - case VTERM_N_PROPS: return 0; - } - return 0; /* UNREACHABLE */ -} - -void vterm_scroll_rect(VTermRect rect, - int downward, - int rightward, - int (*moverect)(VTermRect src, VTermRect dest, void *user), - int (*eraserect)(VTermRect rect, int selective, void *user), - void *user) -{ - VTermRect src; - VTermRect dest; - - if(abs(downward) >= rect.end_row - rect.start_row || - abs(rightward) >= rect.end_col - rect.start_col) { - /* Scroll more than area; just erase the lot */ - (*eraserect)(rect, 0, user); - return; - } - - if(rightward >= 0) { - /* rect: [XXX................] - * src: [----------------] - * dest: [----------------] - */ - dest.start_col = rect.start_col; - dest.end_col = rect.end_col - rightward; - src.start_col = rect.start_col + rightward; - src.end_col = rect.end_col; - } - else { - /* rect: [................XXX] - * src: [----------------] - * dest: [----------------] - */ - int leftward = -rightward; - dest.start_col = rect.start_col + leftward; - dest.end_col = rect.end_col; - src.start_col = rect.start_col; - src.end_col = rect.end_col - leftward; - } - - if(downward >= 0) { - dest.start_row = rect.start_row; - dest.end_row = rect.end_row - downward; - src.start_row = rect.start_row + downward; - src.end_row = rect.end_row; - } - else { - int upward = -downward; - dest.start_row = rect.start_row + upward; - dest.end_row = rect.end_row; - src.start_row = rect.start_row; - src.end_row = rect.end_row - upward; - } - - if(moverect) - (*moverect)(dest, src, user); - - if(downward > 0) - rect.start_row = rect.end_row - downward; - else if(downward < 0) - rect.end_row = rect.start_row - downward; - - if(rightward > 0) - rect.start_col = rect.end_col - rightward; - else if(rightward < 0) - rect.end_col = rect.start_col - rightward; - - (*eraserect)(rect, 0, user); -} - -void vterm_copy_cells(VTermRect dest, - VTermRect src, - void (*copycell)(VTermPos dest, VTermPos src, void *user), - void *user) -{ - int downward = src.start_row - dest.start_row; - int rightward = src.start_col - dest.start_col; - - int init_row, test_row, init_col, test_col; - int inc_row, inc_col; - - if(downward < 0) { - init_row = dest.end_row - 1; - test_row = dest.start_row - 1; - inc_row = -1; - } - else /* downward >= 0 */ { - init_row = dest.start_row; - test_row = dest.end_row; - inc_row = +1; - } - - if(rightward < 0) { - init_col = dest.end_col - 1; - test_col = dest.start_col - 1; - inc_col = -1; - } - else /* rightward >= 0 */ { - init_col = dest.start_col; - test_col = dest.end_col; - inc_col = +1; - } - - VTermPos pos; - for(pos.row = init_row; pos.row != test_row; pos.row += inc_row) - for(pos.col = init_col; pos.col != test_col; pos.col += inc_col) { - VTermPos srcpos = { pos.row + downward, pos.col + rightward }; - (*copycell)(pos, srcpos, user); - } -} - -void vterm_check_version(int major, int minor) -{ - if(major != VTERM_VERSION_MAJOR) { - fprintf(stderr, "libvterm major version mismatch; %d (wants) != %d (library)\n", - major, VTERM_VERSION_MAJOR); - exit(1); - } - - if(minor > VTERM_VERSION_MINOR) { - fprintf(stderr, "libvterm minor version mismatch; %d (wants) > %d (library)\n", - minor, VTERM_VERSION_MINOR); - exit(1); - } - - // Happy -} diff --git a/src/vterm/vterm.h b/src/vterm/vterm.h deleted file mode 100644 index 4de1d885b8..0000000000 --- a/src/vterm/vterm.h +++ /dev/null @@ -1,638 +0,0 @@ -#ifndef __VTERM_H__ -#define __VTERM_H__ - -#ifdef __cplusplus -extern "C" { -#endif - -#include <stdint.h> -#include <stdlib.h> -#include <stdbool.h> - -#include "nvim/macros_defs.h" -#include "nvim/types_defs.h" -#include "vterm_keycodes.h" - -#define VTERM_VERSION_MAJOR 0 -#define VTERM_VERSION_MINOR 3 -#define VTERM_VERSION_PATCH 3 - -#define VTERM_CHECK_VERSION \ - vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR) - -typedef struct VTerm VTerm; -typedef struct VTermState VTermState; -typedef struct VTermScreen VTermScreen; - -typedef struct { - int row; - int col; -} VTermPos; - -/* some small utility functions; we can just keep these static here */ - -/* order points by on-screen flow order */ -static inline int vterm_pos_cmp(VTermPos a, VTermPos b) -{ - return (a.row == b.row) ? a.col - b.col : a.row - b.row; -} - -typedef struct { - int start_row; - int end_row; - int start_col; - int end_col; -} VTermRect; - -/* true if the rect contains the point */ -static inline int vterm_rect_contains(VTermRect r, VTermPos p) -{ - return p.row >= r.start_row && p.row < r.end_row && - p.col >= r.start_col && p.col < r.end_col; -} - -/* move a rect */ -static inline void vterm_rect_move(VTermRect *rect, int row_delta, int col_delta) -{ - rect->start_row += row_delta; rect->end_row += row_delta; - rect->start_col += col_delta; rect->end_col += col_delta; -} - -/** - * Bit-field describing the content of the tagged union `VTermColor`. - */ -typedef enum { - /** - * If the lower bit of `type` is not set, the colour is 24-bit RGB. - */ - VTERM_COLOR_RGB = 0x00, - - /** - * The colour is an index into a palette of 256 colours. - */ - VTERM_COLOR_INDEXED = 0x01, - - /** - * Mask that can be used to extract the RGB/Indexed bit. - */ - VTERM_COLOR_TYPE_MASK = 0x01, - - /** - * If set, indicates that this colour should be the default foreground - * color, i.e. there was no SGR request for another colour. When - * rendering this colour it is possible to ignore "idx" and just use a - * colour that is not in the palette. - */ - VTERM_COLOR_DEFAULT_FG = 0x02, - - /** - * If set, indicates that this colour should be the default background - * color, i.e. there was no SGR request for another colour. A common - * option when rendering this colour is to not render a background at - * all, for example by rendering the window transparently at this spot. - */ - VTERM_COLOR_DEFAULT_BG = 0x04, - - /** - * Mask that can be used to extract the default foreground/background bit. - */ - VTERM_COLOR_DEFAULT_MASK = 0x06 -} VTermColorType; - -/** - * Returns true if the VTERM_COLOR_RGB `type` flag is set, indicating that the - * given VTermColor instance is an indexed colour. - */ -#define VTERM_COLOR_IS_INDEXED(col) \ - (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_INDEXED) - -/** - * Returns true if the VTERM_COLOR_INDEXED `type` flag is set, indicating that - * the given VTermColor instance is an rgb colour. - */ -#define VTERM_COLOR_IS_RGB(col) \ - (((col)->type & VTERM_COLOR_TYPE_MASK) == VTERM_COLOR_RGB) - -/** - * Returns true if the VTERM_COLOR_DEFAULT_FG `type` flag is set, indicating - * that the given VTermColor instance corresponds to the default foreground - * color. - */ -#define VTERM_COLOR_IS_DEFAULT_FG(col) \ - (!!((col)->type & VTERM_COLOR_DEFAULT_FG)) - -/** - * Returns true if the VTERM_COLOR_DEFAULT_BG `type` flag is set, indicating - * that the given VTermColor instance corresponds to the default background - * color. - */ -#define VTERM_COLOR_IS_DEFAULT_BG(col) \ - (!!((col)->type & VTERM_COLOR_DEFAULT_BG)) - -/** - * Tagged union storing either an RGB color or an index into a colour palette. - * In order to convert indexed colours to RGB, you may use the - * vterm_state_convert_color_to_rgb() or vterm_screen_convert_color_to_rgb() - * functions which lookup the RGB colour from the palette maintained by a - * VTermState or VTermScreen instance. - */ -typedef union { - /** - * Tag indicating which union member is actually valid. This variable - * coincides with the `type` member of the `rgb` and the `indexed` struct - * in memory. Please use the `VTERM_COLOR_IS_*` test macros to check whether - * a particular type flag is set. - */ - uint8_t type; - - /** - * Valid if `VTERM_COLOR_IS_RGB(type)` is true. Holds the RGB colour values. - */ - struct { - /** - * Same as the top-level `type` member stored in VTermColor. - */ - uint8_t type; - - /** - * The actual 8-bit red, green, blue colour values. - */ - uint8_t red, green, blue; - } rgb; - - /** - * If `VTERM_COLOR_IS_INDEXED(type)` is true, this member holds the index into - * the colour palette. - */ - struct { - /** - * Same as the top-level `type` member stored in VTermColor. - */ - uint8_t type; - - /** - * Index into the colour map. - */ - uint8_t idx; - } indexed; -} VTermColor; - -/** - * Constructs a new VTermColor instance representing the given RGB values. - */ -static inline void vterm_color_rgb(VTermColor *col, uint8_t red, uint8_t green, - uint8_t blue) -{ - col->type = VTERM_COLOR_RGB; - col->rgb.red = red; - col->rgb.green = green; - col->rgb.blue = blue; -} - -/** - * Construct a new VTermColor instance representing an indexed color with the - * given index. - */ -static inline void vterm_color_indexed(VTermColor *col, uint8_t idx) -{ - col->type = VTERM_COLOR_INDEXED; - col->indexed.idx = idx; -} - -/** - * Compares two colours. Returns true if the colors are equal, false otherwise. - */ -int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); - -typedef enum { - /* VTERM_VALUETYPE_NONE = 0 */ - VTERM_VALUETYPE_BOOL = 1, - VTERM_VALUETYPE_INT, - VTERM_VALUETYPE_STRING, - VTERM_VALUETYPE_COLOR, - - VTERM_N_VALUETYPES -} VTermValueType; - -typedef struct { - const char *str; - size_t len : 30; - bool initial : 1; - bool final : 1; -} VTermStringFragment; - -typedef union { - int boolean; - int number; - VTermStringFragment string; - VTermColor color; -} VTermValue; - -typedef enum { - /* VTERM_ATTR_NONE = 0 */ - VTERM_ATTR_BOLD = 1, // bool: 1, 22 - VTERM_ATTR_UNDERLINE, // number: 4, 21, 24 - VTERM_ATTR_ITALIC, // bool: 3, 23 - VTERM_ATTR_BLINK, // bool: 5, 25 - VTERM_ATTR_REVERSE, // bool: 7, 27 - VTERM_ATTR_CONCEAL, // bool: 8, 28 - VTERM_ATTR_STRIKE, // bool: 9, 29 - VTERM_ATTR_FONT, // number: 10-19 - VTERM_ATTR_FOREGROUND, // color: 30-39 90-97 - VTERM_ATTR_BACKGROUND, // color: 40-49 100-107 - VTERM_ATTR_SMALL, // bool: 73, 74, 75 - VTERM_ATTR_BASELINE, // number: 73, 74, 75 - VTERM_ATTR_URI, // number - - VTERM_N_ATTRS -} VTermAttr; - -typedef enum { - /* VTERM_PROP_NONE = 0 */ - VTERM_PROP_CURSORVISIBLE = 1, // bool - VTERM_PROP_CURSORBLINK, // bool - VTERM_PROP_ALTSCREEN, // bool - VTERM_PROP_TITLE, // string - VTERM_PROP_ICONNAME, // string - VTERM_PROP_REVERSE, // bool - VTERM_PROP_CURSORSHAPE, // number - VTERM_PROP_MOUSE, // number - VTERM_PROP_FOCUSREPORT, // bool - - VTERM_N_PROPS -} VTermProp; - -enum { - VTERM_PROP_CURSORSHAPE_BLOCK = 1, - VTERM_PROP_CURSORSHAPE_UNDERLINE, - VTERM_PROP_CURSORSHAPE_BAR_LEFT, - - VTERM_N_PROP_CURSORSHAPES -}; - -enum { - VTERM_PROP_MOUSE_NONE = 0, - VTERM_PROP_MOUSE_CLICK, - VTERM_PROP_MOUSE_DRAG, - VTERM_PROP_MOUSE_MOVE, - - VTERM_N_PROP_MOUSES -}; - -typedef enum { - VTERM_SELECTION_CLIPBOARD = (1<<0), - VTERM_SELECTION_PRIMARY = (1<<1), - VTERM_SELECTION_SECONDARY = (1<<2), - VTERM_SELECTION_SELECT = (1<<3), - VTERM_SELECTION_CUT0 = (1<<4), /* also CUT1 .. CUT7 by bitshifting */ -} VTermSelectionMask; - -typedef struct { - schar_T schar; - int width; - unsigned int protected_cell:1; /* DECSCA-protected against DECSEL/DECSED */ - unsigned int dwl:1; /* DECDWL or DECDHL double-width line */ - unsigned int dhl:2; /* DECDHL double-height line (1=top 2=bottom) */ -} VTermGlyphInfo; - -typedef struct { - unsigned int doublewidth:1; /* DECDWL or DECDHL line */ - unsigned int doubleheight:2; /* DECDHL line (1=top 2=bottom) */ - unsigned int continuation:1; /* Line is a flow continuation of the previous */ -} VTermLineInfo; - -/* Copies of VTermState fields that the 'resize' callback might have reason to - * edit. 'resize' callback gets total control of these fields and may - * free-and-reallocate them if required. They will be copied back from the - * struct after the callback has returned. - */ -typedef struct { - VTermPos pos; /* current cursor position */ - VTermLineInfo *lineinfos[2]; /* [1] may be NULL */ -} VTermStateFields; - -typedef struct { - /* libvterm relies on this memory to be zeroed out before it is returned - * by the allocator. */ - void *(*malloc)(size_t size, void *allocdata); - void (*free)(void *ptr, void *allocdata); -} VTermAllocatorFunctions; - -void vterm_check_version(int major, int minor); - -struct VTermBuilder { - int ver; /* currently unused but reserved for some sort of ABI version flag */ - - int rows, cols; - - const VTermAllocatorFunctions *allocator; - void *allocdata; - - /* Override default sizes for various structures */ - size_t outbuffer_len; /* default: 4096 */ - size_t tmpbuffer_len; /* default: 4096 */ -}; - -VTerm *vterm_build(const struct VTermBuilder *builder); - -/* A convenient shortcut for default cases */ -VTerm *vterm_new(int rows, int cols); -/* This shortcuts are generally discouraged in favour of just using vterm_build() */ -VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata); - -void vterm_free(VTerm* vt); - -void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp); -void vterm_set_size(VTerm *vt, int rows, int cols); - -int vterm_get_utf8(const VTerm *vt); -void vterm_set_utf8(VTerm *vt, int is_utf8); - -size_t vterm_input_write(VTerm *vt, const char *bytes, size_t len); - -/* Setting output callback will override the buffer logic */ -typedef void VTermOutputCallback(const char *s, size_t len, void *user); -void vterm_output_set_callback(VTerm *vt, VTermOutputCallback *func, void *user); - -/* These buffer functions only work if output callback is NOT set - * These are deprecated and will be removed in a later version */ -size_t vterm_output_get_buffer_size(const VTerm *vt); -size_t vterm_output_get_buffer_current(const VTerm *vt); -size_t vterm_output_get_buffer_remaining(const VTerm *vt); - -/* This too */ -size_t vterm_output_read(VTerm *vt, char *buffer, size_t len); - -void vterm_keyboard_unichar(VTerm *vt, uint32_t c, VTermModifier mod); -void vterm_keyboard_key(VTerm *vt, VTermKey key, VTermModifier mod); - -void vterm_keyboard_start_paste(VTerm *vt); -void vterm_keyboard_end_paste(VTerm *vt); - -void vterm_mouse_move(VTerm *vt, int row, int col, VTermModifier mod); -void vterm_mouse_button(VTerm *vt, int button, bool pressed, VTermModifier mod); - -// ------------ -// Parser layer -// ------------ - -/* Flag to indicate non-final subparameters in a single CSI parameter. - * Consider - * CSI 1;2:3:4;5a - * 1 4 and 5 are final. - * 2 and 3 are non-final and will have this bit set - * - * Don't confuse this with the final byte of the CSI escape; 'a' in this case. - */ -#define CSI_ARG_FLAG_MORE (1U<<31) -#define CSI_ARG_MASK (~(1U<<31)) - -#define CSI_ARG_HAS_MORE(a) ((a) & CSI_ARG_FLAG_MORE) -#define CSI_ARG(a) ((a) & CSI_ARG_MASK) - -/* Can't use -1 to indicate a missing argument; use this instead */ -#define CSI_ARG_MISSING ((1UL<<31)-1) - -#define CSI_ARG_IS_MISSING(a) (CSI_ARG(a) == CSI_ARG_MISSING) -#define CSI_ARG_OR(a,def) (CSI_ARG(a) == CSI_ARG_MISSING ? (def) : CSI_ARG(a)) -#define CSI_ARG_COUNT(a) (CSI_ARG(a) == CSI_ARG_MISSING || CSI_ARG(a) == 0 ? 1 : CSI_ARG(a)) - -typedef struct { - int (*text)(const char *bytes, size_t len, void *user); - int (*control)(unsigned char control, void *user); - int (*escape)(const char *bytes, size_t len, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); - int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); - int (*apc)(VTermStringFragment frag, void *user); - int (*pm)(VTermStringFragment frag, void *user); - int (*sos)(VTermStringFragment frag, void *user); - int (*resize)(int rows, int cols, void *user); -} VTermParserCallbacks; - -void vterm_parser_set_callbacks(VTerm *vt, const VTermParserCallbacks *callbacks, void *user); -void *vterm_parser_get_cbdata(VTerm *vt); - -/* Normally NUL, CAN, SUB and DEL are ignored. Setting this true causes them - * to be emitted by the 'control' callback - */ -void vterm_parser_set_emit_nul(VTerm *vt, bool emit); - -// ----------- -// State layer -// ----------- - -typedef struct { - int (*putglyph)(VTermGlyphInfo *info, VTermPos pos, void *user); - int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); - int (*scrollrect)(VTermRect rect, int downward, int rightward, void *user); - int (*moverect)(VTermRect dest, VTermRect src, void *user); - int (*erase)(VTermRect rect, int selective, void *user); - int (*initpen)(void *user); - int (*setpenattr)(VTermAttr attr, VTermValue *val, void *user); - int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*bell)(void *user); - int (*resize)(int rows, int cols, VTermStateFields *fields, void *user); - int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user); - int (*sb_clear)(void *user); -} VTermStateCallbacks; - -typedef struct { - int (*control)(unsigned char control, void *user); - int (*csi)(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); - int (*osc)(int command, VTermStringFragment frag, void *user); - int (*dcs)(const char *command, size_t commandlen, VTermStringFragment frag, void *user); - int (*apc)(VTermStringFragment frag, void *user); - int (*pm)(VTermStringFragment frag, void *user); - int (*sos)(VTermStringFragment frag, void *user); -} VTermStateFallbacks; - -typedef struct { - int (*set)(VTermSelectionMask mask, VTermStringFragment frag, void *user); - int (*query)(VTermSelectionMask mask, void *user); -} VTermSelectionCallbacks; - -VTermState *vterm_obtain_state(VTerm *vt); - -void vterm_state_set_callbacks(VTermState *state, const VTermStateCallbacks *callbacks, void *user); -void *vterm_state_get_cbdata(VTermState *state); - -void vterm_state_set_unrecognised_fallbacks(VTermState *state, const VTermStateFallbacks *fallbacks, void *user); -void *vterm_state_get_unrecognised_fbdata(VTermState *state); - -void vterm_state_reset(VTermState *state, int hard); -void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); -void vterm_state_get_default_colors(const VTermState *state, VTermColor *default_fg, VTermColor *default_bg); -void vterm_state_get_palette_color(const VTermState *state, int index, VTermColor *col); -void vterm_state_set_default_colors(VTermState *state, const VTermColor *default_fg, const VTermColor *default_bg); -void vterm_state_set_palette_color(VTermState *state, int index, const VTermColor *col); -void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); -int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); -int vterm_state_set_penattr(VTermState *state, VTermAttr attr, VTermValueType type, VTermValue *val); -int vterm_state_set_termprop(VTermState *state, VTermProp prop, VTermValue *val); -void vterm_state_focus_in(VTermState *state); -void vterm_state_focus_out(VTermState *state); -const VTermLineInfo *vterm_state_get_lineinfo(const VTermState *state, int row); - -/** - * Makes sure that the given color `col` is indeed an RGB colour. After this - * function returns, VTERM_COLOR_IS_RGB(col) will return true, while all other - * flags stored in `col->type` will have been reset. - * - * @param state is the VTermState instance from which the colour palette should - * be extracted. - * @param col is a pointer at the VTermColor instance that should be converted - * to an RGB colour. - */ -void vterm_state_convert_color_to_rgb(const VTermState *state, VTermColor *col); - -void vterm_state_set_selection_callbacks(VTermState *state, const VTermSelectionCallbacks *callbacks, void *user, - char *buffer, size_t buflen); - -void vterm_state_send_selection(VTermState *state, VTermSelectionMask mask, VTermStringFragment frag); - -// ------------ -// Screen layer -// ------------ - -typedef struct { - unsigned int bold : 1; - unsigned int underline : 2; - unsigned int italic : 1; - unsigned int blink : 1; - unsigned int reverse : 1; - unsigned int conceal : 1; - unsigned int strike : 1; - unsigned int font : 4; /* 0 to 9 */ - unsigned int dwl : 1; /* On a DECDWL or DECDHL line */ - unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */ - unsigned int small : 1; - unsigned int baseline : 2; -} VTermScreenCellAttrs; - -enum { - VTERM_UNDERLINE_OFF, - VTERM_UNDERLINE_SINGLE, - VTERM_UNDERLINE_DOUBLE, - VTERM_UNDERLINE_CURLY, -}; - -enum { - VTERM_BASELINE_NORMAL, - VTERM_BASELINE_RAISE, - VTERM_BASELINE_LOWER, -}; - -typedef struct { - schar_T schar; - char width; - VTermScreenCellAttrs attrs; - VTermColor fg, bg; - int uri; -} VTermScreenCell; - -typedef struct { - int (*damage)(VTermRect rect, void *user); - int (*moverect)(VTermRect dest, VTermRect src, void *user); - int (*movecursor)(VTermPos pos, VTermPos oldpos, int visible, void *user); - int (*settermprop)(VTermProp prop, VTermValue *val, void *user); - int (*bell)(void *user); - int (*resize)(int rows, int cols, void *user); - int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user); - int (*sb_popline)(int cols, VTermScreenCell *cells, void *user); - int (*sb_clear)(void* user); -} VTermScreenCallbacks; - -VTermScreen *vterm_obtain_screen(VTerm *vt); - -void vterm_screen_set_callbacks(VTermScreen *screen, const VTermScreenCallbacks *callbacks, void *user); -void *vterm_screen_get_cbdata(VTermScreen *screen); - -void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user); -void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen); - -void vterm_screen_enable_reflow(VTermScreen *screen, bool reflow); - -// Back-compat alias for the brief time it was in 0.3-RC1 -#define vterm_screen_set_reflow vterm_screen_enable_reflow - -void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen); - -typedef enum { - VTERM_DAMAGE_CELL, /* every cell */ - VTERM_DAMAGE_ROW, /* entire rows */ - VTERM_DAMAGE_SCREEN, /* entire screen */ - VTERM_DAMAGE_SCROLL, /* entire screen + scrollrect */ - - VTERM_N_DAMAGES -} VTermDamageSize; - -void vterm_screen_flush_damage(VTermScreen *screen); -void vterm_screen_set_damage_merge(VTermScreen *screen, VTermDamageSize size); - -void vterm_screen_reset(VTermScreen *screen, int hard); - -/* Neither of these functions NUL-terminate the buffer */ -size_t vterm_screen_get_chars(const VTermScreen *screen, uint32_t *chars, size_t len, const VTermRect rect); -size_t vterm_screen_get_text(const VTermScreen *screen, char *str, size_t len, const VTermRect rect); - -typedef enum { - VTERM_ATTR_BOLD_MASK = 1 << 0, - VTERM_ATTR_UNDERLINE_MASK = 1 << 1, - VTERM_ATTR_ITALIC_MASK = 1 << 2, - VTERM_ATTR_BLINK_MASK = 1 << 3, - VTERM_ATTR_REVERSE_MASK = 1 << 4, - VTERM_ATTR_STRIKE_MASK = 1 << 5, - VTERM_ATTR_FONT_MASK = 1 << 6, - VTERM_ATTR_FOREGROUND_MASK = 1 << 7, - VTERM_ATTR_BACKGROUND_MASK = 1 << 8, - VTERM_ATTR_CONCEAL_MASK = 1 << 9, - VTERM_ATTR_SMALL_MASK = 1 << 10, - VTERM_ATTR_BASELINE_MASK = 1 << 11, - VTERM_ATTR_URI_MASK = 1 << 12, - - VTERM_ALL_ATTRS_MASK = (1 << 13) - 1 -} VTermAttrMask; - -int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); - -int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCell *cell); - -int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); - -/** - * Same as vterm_state_convert_color_to_rgb(), but takes a `screen` instead of a `state` - * instance. - */ -void vterm_screen_convert_color_to_rgb(const VTermScreen *screen, VTermColor *col); - -/** - * Similar to vterm_state_set_default_colors(), but also resets colours in the - * screen buffer(s) - */ -void vterm_screen_set_default_colors(VTermScreen *screen, const VTermColor *default_fg, const VTermColor *default_bg); - -// --------- -// Utilities -// --------- - -VTermValueType vterm_get_attr_type(VTermAttr attr); -VTermValueType vterm_get_prop_type(VTermProp prop); - -void vterm_scroll_rect(VTermRect rect, - int downward, - int rightward, - int (*moverect)(VTermRect src, VTermRect dest, void *user), - int (*eraserect)(VTermRect rect, int selective, void *user), - void *user); - -void vterm_copy_cells(VTermRect dest, - VTermRect src, - void (*copycell)(VTermPos dest, VTermPos src, void *user), - void *user); - -#ifdef __cplusplus -} -#endif - -#endif diff --git a/src/vterm/vterm_internal.h b/src/vterm/vterm_internal.h deleted file mode 100644 index 8f1722dd93..0000000000 --- a/src/vterm/vterm_internal.h +++ /dev/null @@ -1,298 +0,0 @@ -#ifndef __VTERM_INTERNAL_H__ -#define __VTERM_INTERNAL_H__ - -#include "vterm.h" - -#include <stdarg.h> -#include "nvim/mbyte.h" - -#if defined(__GNUC__) -# define INTERNAL __attribute__((visibility("internal"))) -#else -# define INTERNAL -#endif - -#ifdef DEBUG -# define DEBUG_LOG(...) fprintf(stderr, __VA_ARGS__) -#else -# define DEBUG_LOG(...) -#endif - -#define ESC_S "\x1b" - -#define INTERMED_MAX 16 - -#define CSI_ARGS_MAX 16 -#define CSI_LEADER_MAX 16 - -#define BUFIDX_PRIMARY 0 -#define BUFIDX_ALTSCREEN 1 - -typedef struct VTermEncoding VTermEncoding; - -typedef struct { - VTermEncoding *enc; - - // This size should be increased if required by other stateful encodings - char data[4*sizeof(uint32_t)]; -} VTermEncodingInstance; - -struct VTermPen -{ - VTermColor fg; - VTermColor bg; - int uri; - unsigned int bold:1; - unsigned int underline:2; - unsigned int italic:1; - unsigned int blink:1; - unsigned int reverse:1; - unsigned int conceal:1; - unsigned int strike:1; - unsigned int font:4; /* To store 0-9 */ - unsigned int small:1; - unsigned int baseline:2; -}; - -struct VTermState -{ - VTerm *vt; - - const VTermStateCallbacks *callbacks; - void *cbdata; - - const VTermStateFallbacks *fallbacks; - void *fbdata; - - int rows; - int cols; - - /* Current cursor position */ - VTermPos pos; - - int at_phantom; /* True if we're on the "81st" phantom column to defer a wraparound */ - - int scrollregion_top; - int scrollregion_bottom; /* -1 means unbounded */ -#define SCROLLREGION_BOTTOM(state) ((state)->scrollregion_bottom > -1 ? (state)->scrollregion_bottom : (state)->rows) - int scrollregion_left; -#define SCROLLREGION_LEFT(state) ((state)->mode.leftrightmargin ? (state)->scrollregion_left : 0) - int scrollregion_right; /* -1 means unbounded */ -#define SCROLLREGION_RIGHT(state) ((state)->mode.leftrightmargin && (state)->scrollregion_right > -1 ? (state)->scrollregion_right : (state)->cols) - - /* Bitvector of tab stops */ - unsigned char *tabstops; - - /* Primary and Altscreen; lineinfos[1] is lazily allocated as needed */ - VTermLineInfo *lineinfos[2]; - - /* lineinfo will == lineinfos[0] or lineinfos[1], depending on altscreen */ - VTermLineInfo *lineinfo; -#define ROWWIDTH(state,row) ((state)->lineinfo[(row)].doublewidth ? ((state)->cols / 2) : (state)->cols) -#define THISROWWIDTH(state) ROWWIDTH(state, (state)->pos.row) - - /* Mouse state */ - int mouse_col, mouse_row; - int mouse_buttons; - int mouse_flags; -#define MOUSE_WANT_CLICK 0x01 -#define MOUSE_WANT_DRAG 0x02 -#define MOUSE_WANT_MOVE 0x04 - - enum { MOUSE_X10, MOUSE_UTF8, MOUSE_SGR, MOUSE_RXVT } mouse_protocol; - - /* Last glyph output, for Unicode recombining purposes */ - char grapheme_buf[MAX_SCHAR_SIZE]; - size_t grapheme_len; - uint32_t grapheme_last; // last added UTF-32 char - GraphemeState grapheme_state; - int combine_width; // The width of the glyph above - VTermPos combine_pos; // Position before movement - - struct { - unsigned int keypad:1; - unsigned int cursor:1; - unsigned int autowrap:1; - unsigned int insert:1; - unsigned int newline:1; - unsigned int cursor_visible:1; - unsigned int cursor_blink:1; - unsigned int cursor_shape:2; - unsigned int alt_screen:1; - unsigned int origin:1; - unsigned int screen:1; - unsigned int leftrightmargin:1; - unsigned int bracketpaste:1; - unsigned int report_focus:1; - } mode; - - VTermEncodingInstance encoding[4], encoding_utf8; - int gl_set, gr_set, gsingle_set; - - struct VTermPen pen; - - VTermColor default_fg; - VTermColor default_bg; - VTermColor colors[16]; // Store the 8 ANSI and the 8 ANSI high-brights only - - int bold_is_highbright; - - unsigned int protected_cell : 1; - - /* Saved state under DEC mode 1048/1049 */ - struct { - VTermPos pos; - struct VTermPen pen; - - struct { - unsigned int cursor_visible:1; - unsigned int cursor_blink:1; - unsigned int cursor_shape:2; - } mode; - } saved; - - /* Temporary state for DECRQSS parsing */ - union { - char decrqss[4]; - struct { - uint16_t mask; - enum { - SELECTION_INITIAL, - SELECTION_SELECTED, - SELECTION_QUERY, - SELECTION_SET_INITIAL, - SELECTION_SET, - SELECTION_INVALID, - } state : 8; - uint32_t recvpartial; - uint32_t sendpartial; - } selection; - } tmp; - - struct { - const VTermSelectionCallbacks *callbacks; - void *user; - char *buffer; - size_t buflen; - } selection; -}; - -struct VTerm -{ - const VTermAllocatorFunctions *allocator; - void *allocdata; - - int rows; - int cols; - - struct { - unsigned int utf8:1; - unsigned int ctrl8bit:1; - } mode; - - struct { - enum VTermParserState { - NORMAL, - CSI_LEADER, - CSI_ARGS, - CSI_INTERMED, - DCS_COMMAND, - /* below here are the "string states" */ - OSC_COMMAND, - OSC, - DCS, - APC, - PM, - SOS, - } state; - - bool in_esc : 1; - - int intermedlen; - char intermed[INTERMED_MAX]; - - union { - struct { - int leaderlen; - char leader[CSI_LEADER_MAX]; - - int argi; - long args[CSI_ARGS_MAX]; - } csi; - struct { - int command; - } osc; - struct { - int commandlen; - char command[CSI_LEADER_MAX]; - } dcs; - } v; - - const VTermParserCallbacks *callbacks; - void *cbdata; - - bool string_initial; - - bool emit_nul; - } parser; - - /* len == malloc()ed size; cur == number of valid bytes */ - - VTermOutputCallback *outfunc; - void *outdata; - - char *outbuffer; - size_t outbuffer_len; - size_t outbuffer_cur; - - char *tmpbuffer; - size_t tmpbuffer_len; - - VTermState *state; - VTermScreen *screen; -}; - -struct VTermEncoding { - void (*init) (VTermEncoding *enc, void *data); - void (*decode)(VTermEncoding *enc, void *data, - uint32_t cp[], int *cpi, int cplen, - const char bytes[], size_t *pos, size_t len); -}; - -typedef enum { - ENC_UTF8, - ENC_SINGLE_94 -} VTermEncodingType; - -void *vterm_allocator_malloc(VTerm *vt, size_t size); -void vterm_allocator_free(VTerm *vt, void *ptr); - -void vterm_push_output_bytes(VTerm *vt, const char *bytes, size_t len); -void vterm_push_output_vsprintf(VTerm *vt, const char *format, va_list args); -void vterm_push_output_sprintf(VTerm *vt, const char *format, ...); -void vterm_push_output_sprintf_ctrl(VTerm *vt, unsigned char ctrl, const char *fmt, ...); -void vterm_push_output_sprintf_str(VTerm *vt, unsigned char ctrl, bool term, const char *fmt, ...); - -void vterm_state_free(VTermState *state); - -void vterm_state_newpen(VTermState *state); -void vterm_state_resetpen(VTermState *state); -void vterm_state_setpen(VTermState *state, const long args[], int argcount); -int vterm_state_getpen(VTermState *state, long args[], int argcount); -void vterm_state_savepen(VTermState *state, int save); - -enum { - C1_SS3 = 0x8f, - C1_DCS = 0x90, - C1_CSI = 0x9b, - C1_ST = 0x9c, - C1_OSC = 0x9d, -}; - -void vterm_state_push_output_sprintf_CSI(VTermState *vts, const char *format, ...); - -void vterm_screen_free(VTermScreen *screen); - -VTermEncoding *vterm_lookup_encoding(VTermEncodingType type, char designation); - -#endif diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c index 8755e32e7a..7522962a05 100644 --- a/test/unit/fixtures/vterm_test.c +++ b/test/unit/fixtures/vterm_test.c @@ -1,7 +1,11 @@ #include <stdio.h> +#include <string.h> + #include "nvim/grid.h" #include "nvim/mbyte.h" - +#include "nvim/vterm/pen.h" +#include "nvim/vterm/screen.h" +#include "nvim/vterm/vterm_internal_defs.h" #include "vterm_test.h" int parser_text(const char bytes[], size_t len, void *user) @@ -204,7 +208,8 @@ int selection_query(VTermSelectionMask mask, void *user) return 1; } -static void print_schar(FILE *f, schar_T schar) { +static void print_schar(FILE *f, schar_T schar) +{ char buf[MAX_SCHAR_SIZE]; schar_get(buf, schar); StrCharInfo ci = utf_ptr2StrCharInfo(buf); @@ -319,6 +324,34 @@ void print_color(const VTermColor *col) fclose(f); } +static VTermValueType vterm_get_prop_type(VTermProp prop) +{ + switch (prop) { + case VTERM_PROP_CURSORVISIBLE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORBLINK: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_ALTSCREEN: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_TITLE: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_ICONNAME: + return VTERM_VALUETYPE_STRING; + case VTERM_PROP_REVERSE: + return VTERM_VALUETYPE_BOOL; + case VTERM_PROP_CURSORSHAPE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_MOUSE: + return VTERM_VALUETYPE_INT; + case VTERM_PROP_FOCUSREPORT: + return VTERM_VALUETYPE_BOOL; + + case VTERM_N_PROPS: + return 0; + } + return 0; // UNREACHABLE +} + bool want_state_settermprop; int state_settermprop(VTermProp prop, VTermValue *val, void *user) { @@ -463,14 +496,14 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user) } int eol = cols; - while (eol && !cells[eol-1].schar) { + while (eol && !cells[eol - 1].schar) { eol--; } FILE *f = fopen(VTERM_TEST_FILE, "a"); fprintf(f, "sb_pushline %d =", cols); for (int c = 0; c < eol; c++) { - fprintf(f, " "); + fprintf(f, " "); print_schar(f, cells[c].schar); } fprintf(f, "\n"); @@ -488,7 +521,7 @@ int screen_sb_popline(int cols, VTermScreenCell *cells, void *user) // All lines of scrollback contain "ABCDE" for (int col = 0; col < cols; col++) { - if(col < 5) { + if (col < 5) { cells[col].schar = schar_from_ascii((uint32_t)('A' + col)); } else { cells[col].schar = 0; @@ -524,3 +557,231 @@ void term_output(const char *s, size_t len, void *user) } fclose(f); } + +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val) +{ + switch (attr) { + case VTERM_ATTR_BOLD: + val->boolean = state->pen.bold; + return 1; + + case VTERM_ATTR_UNDERLINE: + val->number = state->pen.underline; + return 1; + + case VTERM_ATTR_ITALIC: + val->boolean = state->pen.italic; + return 1; + + case VTERM_ATTR_BLINK: + val->boolean = state->pen.blink; + return 1; + + case VTERM_ATTR_REVERSE: + val->boolean = state->pen.reverse; + return 1; + + case VTERM_ATTR_CONCEAL: + val->boolean = state->pen.conceal; + return 1; + + case VTERM_ATTR_STRIKE: + val->boolean = state->pen.strike; + return 1; + + case VTERM_ATTR_FONT: + val->number = state->pen.font; + return 1; + + case VTERM_ATTR_FOREGROUND: + val->color = state->pen.fg; + return 1; + + case VTERM_ATTR_BACKGROUND: + val->color = state->pen.bg; + return 1; + + case VTERM_ATTR_SMALL: + val->boolean = state->pen.small; + return 1; + + case VTERM_ATTR_BASELINE: + val->number = state->pen.baseline; + return 1; + + case VTERM_ATTR_URI: + val->number = state->pen.uri; + return 1; + + case VTERM_N_ATTRS: + return 0; + } + + return 0; +} + +static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b) +{ + if ((attrs & VTERM_ATTR_BOLD_MASK) && (a->pen.bold != b->pen.bold)) { + return 1; + } + if ((attrs & VTERM_ATTR_UNDERLINE_MASK) && (a->pen.underline != b->pen.underline)) { + return 1; + } + if ((attrs & VTERM_ATTR_ITALIC_MASK) && (a->pen.italic != b->pen.italic)) { + return 1; + } + if ((attrs & VTERM_ATTR_BLINK_MASK) && (a->pen.blink != b->pen.blink)) { + return 1; + } + if ((attrs & VTERM_ATTR_REVERSE_MASK) && (a->pen.reverse != b->pen.reverse)) { + return 1; + } + if ((attrs & VTERM_ATTR_CONCEAL_MASK) && (a->pen.conceal != b->pen.conceal)) { + return 1; + } + if ((attrs & VTERM_ATTR_STRIKE_MASK) && (a->pen.strike != b->pen.strike)) { + return 1; + } + if ((attrs & VTERM_ATTR_FONT_MASK) && (a->pen.font != b->pen.font)) { + return 1; + } + if ((attrs & VTERM_ATTR_FOREGROUND_MASK) && !vterm_color_is_equal(&a->pen.fg, &b->pen.fg)) { + return 1; + } + if ((attrs & VTERM_ATTR_BACKGROUND_MASK) && !vterm_color_is_equal(&a->pen.bg, &b->pen.bg)) { + return 1; + } + if ((attrs & VTERM_ATTR_SMALL_MASK) && (a->pen.small != b->pen.small)) { + return 1; + } + if ((attrs & VTERM_ATTR_BASELINE_MASK) && (a->pen.baseline != b->pen.baseline)) { + return 1; + } + if ((attrs & VTERM_ATTR_URI_MASK) && (a->pen.uri != b->pen.uri)) { + return 1; + } + + return 0; +} + +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, + VTermAttrMask attrs) +{ + ScreenCell *target = getcell(screen, pos.row, pos.col); + + // TODO(vterm): bounds check + extent->start_row = pos.row; + extent->end_row = pos.row + 1; + + if (extent->start_col < 0) { + extent->start_col = 0; + } + if (extent->end_col < 0) { + extent->end_col = screen->cols; + } + + int col; + + for (col = pos.col - 1; col >= extent->start_col; col--) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->start_col = col + 1; + + for (col = pos.col + 1; col < extent->end_col; col++) { + if (attrs_differ(attrs, target, getcell(screen, pos.row, col))) { + break; + } + } + extent->end_col = col - 1; + + return 1; +} + +/// Does not NUL-terminate the buffer +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, + const VTermRect rect) +{ + size_t outpos = 0; + int padding = 0; + +#define PUT(bytes, thislen) \ + if (true) { \ + if (buffer && outpos + thislen <= len) \ + memcpy((char *)buffer + outpos, bytes, thislen); \ + outpos += thislen; \ + } \ + + for (int row = rect.start_row; row < rect.end_row; row++) { + for (int col = rect.start_col; col < rect.end_col; col++) { + ScreenCell *cell = getcell(screen, row, col); + + if (cell->schar == 0) { + // Erased cell, might need a space + padding++; + } else if (cell->schar == (uint32_t)-1) { + // Gap behind a double-width char, do nothing + } else { + while (padding) { + PUT(" ", 1); + padding--; + } + char buf[MAX_SCHAR_SIZE + 1]; + size_t thislen = schar_get(buf, cell->schar); + PUT(buf, thislen); + } + } + + if (row < rect.end_row - 1) { + PUT("\n", 1); + padding = 0; + } + } + + return outpos; +} + +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos) +{ + // This cell is EOL if this and every cell to the right is black + for (; pos.col < screen->cols; pos.col++) { + ScreenCell *cell = getcell(screen, pos.row, pos.col); + if (cell->schar != 0) { + return 0; + } + } + + return 1; +} + +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos) +{ + *cursorpos = state->pos; +} + +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright) +{ + state->bold_is_highbright = bold_is_highbright; +} + +/// Compares two colours. Returns true if the colors are equal, false otherwise. +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b) +{ + // First make sure that the two colours are of the same type (RGB/Indexed) + if (a->type != b->type) { + return false; + } + + // Depending on the type inspect the corresponding members + if (VTERM_COLOR_IS_INDEXED(a)) { + return a->indexed.idx == b->indexed.idx; + } else if (VTERM_COLOR_IS_RGB(a)) { + return (a->rgb.red == b->rgb.red) + && (a->rgb.green == b->rgb.green) + && (a->rgb.blue == b->rgb.blue); + } + + return 0; +} diff --git a/test/unit/fixtures/vterm_test.h b/test/unit/fixtures/vterm_test.h index a05e7d499e..ef6463af6d 100644 --- a/test/unit/fixtures/vterm_test.h +++ b/test/unit/fixtures/vterm_test.h @@ -2,8 +2,17 @@ #include <stdint.h> #include "nvim/macros_defs.h" -#include "vterm/vterm.h" +#include "nvim/vterm/vterm.h" +EXTERN VTermPos state_pos; +EXTERN bool want_state_putglyph INIT (=false); +EXTERN bool want_state_movecursor INIT(= false); +EXTERN bool want_state_erase INIT(= false); +EXTERN bool want_state_scrollrect INIT(= false); +EXTERN bool want_state_moverect INIT(= false); +EXTERN bool want_state_settermprop INIT(= false); +EXTERN bool want_state_scrollback INIT(= false); +EXTERN bool want_screen_scrollback INIT(= false); int parser_text(const char bytes[], size_t len, void *user); int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user); @@ -27,12 +36,10 @@ int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user); int screen_sb_popline(int cols, VTermScreenCell *cells, void *user); int screen_sb_clear(void *user); void term_output(const char *s, size_t len, void *user); -EXTERN VTermPos state_pos; -EXTERN bool want_state_putglyph INIT (=false); -EXTERN bool want_state_movecursor INIT(= false); -EXTERN bool want_state_erase INIT(= false); -EXTERN bool want_state_scrollrect INIT(= false); -EXTERN bool want_state_moverect INIT(= false); -EXTERN bool want_state_settermprop INIT(= false); -EXTERN bool want_state_scrollback INIT(= false); -EXTERN bool want_screen_scrollback INIT(= false); +int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue *val); +int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs); +size_t vterm_screen_get_text(const VTermScreen *screen, char *buffer, size_t len, VTermRect rect); +int vterm_screen_is_eol(const VTermScreen *screen, VTermPos pos); +void vterm_state_get_cursorpos(const VTermState *state, VTermPos *cursorpos); +void vterm_state_set_bold_highbright(VTermState *state, int bold_is_highbright); +int vterm_color_is_equal(const VTermColor *a, const VTermColor *b); diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 2457525fb7..db0aa3c575 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -61,7 +61,6 @@ local bit = require('bit') --- @field vterm_screen_enable_reflow function --- @field vterm_screen_get_attrs_extent function --- @field vterm_screen_get_cell function ---- @field vterm_screen_get_chars fun(any, any, any, any):any --- @field vterm_screen_get_text fun(any, any, any, any):any --- @field vterm_screen_is_eol fun(any, any):any --- @field vterm_screen_reset function @@ -79,10 +78,17 @@ local bit = require('bit') --- @field vterm_state_set_selection_callbacks function --- @field vterm_state_set_unrecognised_fallbacks function local vterm = t.cimport( - './src/nvim/mbyte.h', './src/nvim/grid.h', - './src/vterm/vterm.h', - './src/vterm/vterm_internal.h', + './src/nvim/mbyte.h', + './src/nvim/vterm/encoding.h', + './src/nvim/vterm/keyboard.h', + './src/nvim/vterm/mouse.h', + './src/nvim/vterm/parser.h', + './src/nvim/vterm/pen.h', + './src/nvim/vterm/screen.h', + './src/nvim/vterm/state.h', + './src/nvim/vterm/vterm.h', + './src/nvim/vterm/vterm_internal.h', './test/unit/fixtures/vterm_test.h' ) |