aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordundargoc <gocdundar@gmail.com>2025-01-03 15:40:46 +0100
committerdundargoc <33953936+dundargoc@users.noreply.github.com>2025-01-07 12:35:24 +0100
commitd8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8 (patch)
tree3dd3eb1954d36db51a1eab82ea890d18cf085189
parent4d9405991963a53ed31089ed456d9f4cfebc325d (diff)
downloadrneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.tar.gz
rneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.tar.bz2
rneovim-d8bc08db7fd8d0efbf2e9ebf39fecb6f732f84e8.zip
refactor: adopt vterm
We have changed too much to consider it a mere bundled dependency (such as unicode handling in e3bfcf2fd4a4ebf00b104b082cfe83c8144a842d), and can consider it our own at this point.
-rw-r--r--MAINTAIN.md2
-rw-r--r--src/nvim/CMakeLists.txt5
-rw-r--r--src/nvim/mbyte.h1
-rw-r--r--src/nvim/mbyte_defs.h3
-rw-r--r--src/nvim/terminal.c10
-rw-r--r--src/nvim/vterm/LICENSE (renamed from src/vterm/LICENSE)0
-rw-r--r--src/nvim/vterm/README.md1
-rw-r--r--src/nvim/vterm/encoding.c278
-rw-r--r--src/nvim/vterm/encoding.h10
-rw-r--r--src/nvim/vterm/keyboard.c252
-rw-r--r--src/nvim/vterm/keyboard.h10
-rw-r--r--src/nvim/vterm/mouse.c113
-rw-r--r--src/nvim/vterm/mouse.h10
-rw-r--r--src/nvim/vterm/parser.c411
-rw-r--r--src/nvim/vterm/parser.h9
-rw-r--r--src/nvim/vterm/pen.c644
-rw-r--r--src/nvim/vterm/pen.h9
-rw-r--r--src/nvim/vterm/screen.c1103
-rw-r--r--src/nvim/vterm/screen.h9
-rw-r--r--src/nvim/vterm/state.c2310
-rw-r--r--src/nvim/vterm/state.h7
-rw-r--r--src/nvim/vterm/vterm.c335
-rw-r--r--src/nvim/vterm/vterm.h161
-rw-r--r--src/nvim/vterm/vterm_defs.h319
-rw-r--r--src/nvim/vterm/vterm_internal_defs.h266
-rw-r--r--src/nvim/vterm/vterm_keycodes_defs.h (renamed from src/vterm/vterm_keycodes.h)13
-rw-r--r--src/vterm/encoding.c230
-rw-r--r--src/vterm/encoding/DECdrawing.inc36
-rw-r--r--src/vterm/encoding/uk.inc6
-rw-r--r--src/vterm/keyboard.c225
-rw-r--r--src/vterm/mouse.c99
-rw-r--r--src/vterm/parser.c408
-rw-r--r--src/vterm/pen.c678
-rw-r--r--src/vterm/rect.h56
-rw-r--r--src/vterm/screen.c1174
-rw-r--r--src/vterm/state.c2281
-rw-r--r--src/vterm/vterm.c432
-rw-r--r--src/vterm/vterm.h638
-rw-r--r--src/vterm/vterm_internal.h298
-rw-r--r--test/unit/fixtures/vterm_test.c271
-rw-r--r--test/unit/fixtures/vterm_test.h27
-rw-r--r--test/unit/vterm_spec.lua14
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'
)