aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt1
-rw-r--r--cmake.config/config.h.in2
-rw-r--r--src/vterm/vterm.c509
-rw-r--r--src/vterm/vterm.h37
-rw-r--r--test/cmakeconfig/paths.lua.in1
-rw-r--r--test/unit/testutil.lua7
-rw-r--r--test/unit/vterm_spec.lua3591
7 files changed, 4146 insertions, 2 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f6c5427c7d..764d670973 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -46,6 +46,7 @@ set(DEPS_IGNORE_SHA FALSE)
#-------------------------------------------------------------------------------
set(FUNCS_DATA ${PROJECT_BINARY_DIR}/funcs_data.mpack)
set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches)
+set(VTERM_TEST_FILE ${PROJECT_BINARY_DIR}/test/vterm_test_output)
file(GLOB DOCFILES CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/runtime/doc/*.txt)
diff --git a/cmake.config/config.h.in b/cmake.config/config.h.in
index fd712032c3..833218abf9 100644
--- a/cmake.config/config.h.in
+++ b/cmake.config/config.h.in
@@ -52,3 +52,5 @@
#cmakedefine HAVE_BUILTIN_ADD_OVERFLOW
#cmakedefine HAVE_WIMPLICIT_FALLTHROUGH_FLAG
#cmakedefine HAVE_BITSCANFORWARD64
+
+#define VTERM_TEST_FILE "@VTERM_TEST_FILE@"
diff --git a/src/vterm/vterm.c b/src/vterm/vterm.c
index 870a61566e..e8c87929e2 100644
--- a/src/vterm/vterm.c
+++ b/src/vterm/vterm.c
@@ -1,8 +1,9 @@
#include "vterm_internal.h"
+#include "auto/config.h"
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
-#include <stdarg.h>
#include <string.h>
/*****************
@@ -429,3 +430,509 @@ void vterm_check_version(int major, int minor)
// Happy
}
+
+// For unit tests.
+#ifndef NDEBUG
+
+int parser_text(const char bytes[], size_t len, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "text ");
+ int i;
+ for(i = 0; i < len; i++) {
+ unsigned char b = bytes[i];
+ if(b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) {
+ break;
+ }
+ fprintf(f, i ? ",%x" : "%x", b);
+ }
+ fprintf(f, "\n");
+ fclose(f);
+
+ return i;
+}
+
+static void printchars(const char *s, size_t len, FILE *f)
+{
+ while(len--) {
+ fprintf(f, "%c", (s++)[0]);
+ }
+}
+
+int parser_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "csi %02x", command);
+
+ if(leader && leader[0]) {
+ fprintf(f, " L=");
+ for(int i = 0; leader[i]; i++) {
+ fprintf(f, "%02x", leader[i]);
+ }
+ }
+
+ for(int i = 0; i < argcount; i++) {
+ char sep = i ? ',' : ' ';
+
+ if(args[i] == CSI_ARG_MISSING) {
+ fprintf(f, "%c*", sep);
+ } else {
+ fprintf(f, "%c%ld%s", sep, CSI_ARG(args[i]), CSI_ARG_HAS_MORE(args[i]) ? "+" : "");
+ }
+ }
+
+ if(intermed && intermed[0]) {
+ fprintf(f, " I=");
+ for(int i = 0; intermed[i]; i++) {
+ fprintf(f, "%02x", intermed[i]);
+ }
+ }
+
+ fprintf(f, "\n");
+
+ fclose(f);
+
+ return 1;
+}
+
+int parser_osc(int command, VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "osc ");
+
+ if(frag.initial) {
+ if(command == -1) {
+ fprintf(f, "[");
+ } else {
+ fprintf(f, "[%d;", command);
+ }
+ }
+
+ printchars(frag.str, frag.len, f);
+
+ if(frag.final) {
+ fprintf(f, "]");
+ }
+
+ fprintf(f, "\n");
+ fclose(f);
+
+ return 1;
+}
+
+int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "dcs ");
+
+ if(frag.initial) {
+ fprintf(f, "[");
+ for(int i = 0; i < commandlen; i++) {
+ fprintf(f, "%c", command[i]);
+ }
+ }
+
+ printchars(frag.str, frag.len,f);
+
+ if(frag.final) {
+ fprintf(f, "]");
+ }
+
+ fprintf(f, "\n");
+ fclose(f);
+
+ return 1;
+}
+
+int parser_apc(VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "apc ");
+
+ if(frag.initial) {
+ fprintf(f, "[");
+ }
+
+ printchars(frag.str, frag.len, f);
+
+ if(frag.final) {
+ fprintf(f, "]");
+ }
+
+ fprintf(f, "\n");
+ fclose(f);
+
+ return 1;
+}
+
+int parser_pm(VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "pm ");
+
+ if(frag.initial) {
+ fprintf(f, "[");
+ }
+
+ printchars(frag.str, frag.len,f);
+
+ if(frag.final) {
+ fprintf(f, "]");
+ }
+
+ fprintf(f, "\n");
+ fclose(f);
+
+ return 1;
+}
+
+int parser_sos(VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "sos ");
+
+ if(frag.initial) {
+ fprintf(f, "[");
+ }
+
+ printchars(frag.str, frag.len,f);
+
+ if(frag.final) {
+ fprintf(f, "]");
+ }
+
+ fprintf(f, "\n");
+ fclose(f);
+
+ return 1;
+}
+
+int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "selection-set mask=%04X ", mask);
+ if(frag.initial) {
+ fprintf(f, "[");
+}
+ printchars(frag.str, frag.len, f);
+ if(frag.final) {
+ fprintf(f, "]");
+}
+ fprintf(f,"\n");
+
+ fclose(f);
+ return 1;
+}
+
+int selection_query(VTermSelectionMask mask, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f,"selection-query mask=%04X\n", mask);
+
+ fclose(f);
+ return 1;
+}
+
+bool want_state_putglyph;
+int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user)
+{
+ if(!want_state_putglyph) {
+ return 1;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "putglyph ");
+ for(int i = 0; i < VTERM_MAX_CHARS_PER_CELL && info->chars[i]; i++) {
+ fprintf(f, i ? ",%x" : "%x", info->chars[i]);
+ }
+ fprintf(f, " %d %d,%d", info->width, pos.row, pos.col);
+ if(info->protected_cell) {
+ fprintf(f, " prot");
+ }
+ if(info->dwl) {
+ fprintf(f, " dwl");
+ }
+ if(info->dhl) {
+ fprintf(f, " dhl-%s", info->dhl == 1 ? "top" : info->dhl == 2 ? "bottom" : "?" );
+ }
+ fprintf(f, "\n");
+
+ fclose(f);
+
+ return 1;
+}
+
+bool want_state_movecursor;
+VTermPos state_pos;
+int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ state_pos = pos;
+
+ if(want_state_movecursor) {
+ fprintf(f,"movecursor %d,%d\n", pos.row, pos.col);
+ }
+
+ fclose(f);
+ return 1;
+}
+
+bool want_state_scrollrect;
+int state_scrollrect(VTermRect rect, int downward, int rightward, void *user)
+{
+ if(!want_state_scrollrect) {
+ return 0;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+ fprintf(f,"scrollrect %d..%d,%d..%d => %+d,%+d\n",
+ rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+ downward, rightward);
+
+ fclose(f);
+ return 1;
+}
+
+bool want_state_moverect;
+int state_moverect(VTermRect dest, VTermRect src, void *user)
+{
+ if(!want_state_moverect) {
+ return 0;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f,"moverect %d..%d,%d..%d -> %d..%d,%d..%d\n",
+ src.start_row, src.end_row, src.start_col, src.end_col,
+ dest.start_row, dest.end_row, dest.start_col, dest.end_col);
+
+ fclose(f);
+ return 1;
+}
+
+void print_color(const VTermColor *col)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ if (VTERM_COLOR_IS_RGB(col)) {
+ fprintf(f,"rgb(%d,%d,%d", col->rgb.red, col->rgb.green, col->rgb.blue);
+ }
+ else if (VTERM_COLOR_IS_INDEXED(col)) {
+ fprintf(f,"idx(%d", col->indexed.idx);
+ }
+ else {
+ fprintf(f,"invalid(%d", col->type);
+ }
+ if (VTERM_COLOR_IS_DEFAULT_FG(col)) {
+ fprintf(f,",is_default_fg");
+ }
+ if (VTERM_COLOR_IS_DEFAULT_BG(col)) {
+ fprintf(f,",is_default_bg");
+ }
+ fprintf(f,")");
+ fclose(f);
+}
+
+bool want_state_settermprop;
+int state_settermprop(VTermProp prop, VTermValue *val, void *user)
+{
+ if(!want_state_settermprop) {
+ return 1;
+ }
+
+ int errcode = 0;
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+ VTermValueType type = vterm_get_prop_type(prop);
+ switch(type) {
+ case VTERM_VALUETYPE_BOOL:
+ fprintf(f,"settermprop %d %s\n", prop, val->boolean ? "true" : "false");
+ errcode = 1;
+ goto end;
+ case VTERM_VALUETYPE_INT:
+ fprintf(f,"settermprop %d %d\n", prop, val->number);
+ errcode = 1;
+ goto end;
+ case VTERM_VALUETYPE_STRING:
+ fprintf(f,"settermprop %d %s\"%.*s\"%s\n", prop,
+ val->string.initial ? "[" : "", (int)val->string.len, val->string.str, val->string.final ? "]" : "");
+ errcode=0;
+ goto end;
+ case VTERM_VALUETYPE_COLOR:
+ fprintf(f,"settermprop %d ", prop);
+ print_color(&val->color);
+ fprintf(f,"\n");
+ errcode=1;
+ goto end;
+ case VTERM_N_VALUETYPES:
+ goto end;
+ }
+
+end:
+ fclose(f);
+ return errcode;
+}
+
+bool want_state_erase;
+int state_erase(VTermRect rect, int selective, void *user)
+{
+ if(!want_state_erase) {
+ return 1;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+
+ fprintf(f,"erase %d..%d,%d..%d%s\n",
+ rect.start_row, rect.end_row, rect.start_col, rect.end_col,
+ selective ? " selective" : "");
+
+ fclose(f);
+ return 1;
+}
+
+struct {
+ int bold;
+ int underline;
+ int italic;
+ int blink;
+ int reverse;
+ int conceal;
+ int strike;
+ int font;
+ int small;
+ int baseline;
+ VTermColor foreground;
+ VTermColor background;
+} state_pen;
+
+int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
+{
+ 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_SMALL:
+ state_pen.small = val->boolean;
+ break;
+ case VTERM_ATTR_BASELINE:
+ state_pen.baseline = val->number;
+ break;
+ case VTERM_ATTR_FOREGROUND:
+ state_pen.foreground = val->color;
+ break;
+ case VTERM_ATTR_BACKGROUND:
+ state_pen.background = val->color;
+ break;
+
+ case VTERM_N_ATTRS:
+ return 0;
+ default:
+ break;
+ }
+
+ return 1;
+}
+
+bool want_state_scrollback;
+int state_sb_clear(void *user) {
+ if(!want_state_scrollback) {
+ return 1;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f,"sb_clear\n");
+ fclose(f);
+
+ return 0;
+}
+
+bool want_screen_scrollback;
+int screen_sb_pushline(int cols, const VTermScreenCell *cells, void *user)
+{
+ if(!want_screen_scrollback) {
+ return 1;
+ }
+
+ int eol = cols;
+ while(eol && !cells[eol-1].chars[0]) {
+ eol--;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "sb_pushline %d =", cols);
+ for(int c = 0; c < eol; c++) {
+ fprintf(f, " %02X", cells[c].chars[0]);
+ }
+ fprintf(f, "\n");
+
+ fclose(f);
+
+ return 1;
+}
+
+int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
+{
+ if(!want_screen_scrollback) {
+ return 0;
+ }
+
+ // All lines of scrollback contain "ABCDE"
+ for(int col = 0; col < cols; col++) {
+ if(col < 5) {
+ cells[col].chars[0] = 'A' + col;
+ } else {
+ cells[col].chars[0] = 0;
+ }
+
+ cells[col].width = 1;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f,"sb_popline %d\n", cols);
+ fclose(f);
+ return 1;
+}
+
+int screen_sb_clear(void *user)
+{
+ if(!want_screen_scrollback) {
+ return 1;
+ }
+
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "sb_clear\n");
+ fclose(f);
+ return 0;
+}
+
+void term_output(const char *s, size_t len, void *user)
+{
+ FILE *f = fopen(VTERM_TEST_FILE, "a");
+ fprintf(f, "output ");
+ for(int i = 0; i < len; i++) {
+ fprintf(f, "%x%s", (unsigned char)s[i], i < len-1 ? "," : "\n");
+ }
+ fclose(f);
+}
+
+#endif
diff --git a/src/vterm/vterm.h b/src/vterm/vterm.h
index 929418c63a..df6878f744 100644
--- a/src/vterm/vterm.h
+++ b/src/vterm/vterm.h
@@ -9,6 +9,7 @@ extern "C" {
#include <stdlib.h>
#include <stdbool.h>
+#include "nvim/macros_defs.h"
#include "vterm_keycodes.h"
#define VTERM_VERSION_MAJOR 0
@@ -21,7 +22,7 @@ extern "C" {
/* Any cell can contain at most one basic printing character and 5 combining
* characters. This number could be changed but will be ABI-incompatible if
* you do */
-#define VTERM_MAX_CHARS_PER_CELL 6
+enum{ VTERM_MAX_CHARS_PER_CELL=6};
typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
@@ -634,6 +635,40 @@ void vterm_copy_cells(VTermRect dest,
void (*copycell)(VTermPos dest, VTermPos src, void *user),
void *user);
+#ifndef NDEBUG
+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);
+int parser_osc(int command, VTermStringFragment frag, void *user);
+int parser_dcs(const char *command, size_t commandlen, VTermStringFragment frag, void *user);
+int parser_apc(VTermStringFragment frag, void *user);
+int parser_pm(VTermStringFragment frag, void *user);
+int parser_sos(VTermStringFragment frag, void *user);
+int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user);
+int selection_query(VTermSelectionMask mask, void *user);
+int state_putglyph(VTermGlyphInfo *info, VTermPos pos, void *user);
+int state_movecursor(VTermPos pos, VTermPos oldpos, int visible, void *user);
+int state_scrollrect(VTermRect rect, int downward, int rightward, void *user);
+int state_moverect(VTermRect dest, VTermRect src, void *user);
+int state_settermprop(VTermProp prop, VTermValue *val, void *user);
+int state_erase(VTermRect rect, int selective, void *user);
+int state_setpenattr(VTermAttr attr, VTermValue *val, void *user);
+int state_sb_clear(void *user);
+void print_color(const VTermColor *col);
+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);
+#endif
+
#ifdef __cplusplus
}
#endif
diff --git a/test/cmakeconfig/paths.lua.in b/test/cmakeconfig/paths.lua.in
index ce0eb870e0..78c66a4eac 100644
--- a/test/cmakeconfig/paths.lua.in
+++ b/test/cmakeconfig/paths.lua.in
@@ -5,6 +5,7 @@ for p in ("${TEST_INCLUDE_DIRS}" .. ";"):gmatch("[^;]+") do
table.insert(M.include_paths, p)
end
+M.vterm_test_file = "${VTERM_TEST_FILE}"
M.test_build_dir = "${CMAKE_BINARY_DIR}"
M.test_source_path = "${CMAKE_SOURCE_DIR}"
M.test_lua_prg = "${LUA_PRG}"
diff --git a/test/unit/testutil.lua b/test/unit/testutil.lua
index a6db7beab1..4720d4d730 100644
--- a/test/unit/testutil.lua
+++ b/test/unit/testutil.lua
@@ -151,6 +151,13 @@ local function filter_complex_blocks(body)
or string.find(line, 'mach_vm_range_recipe')
)
then
+ -- HACK: remove bitfields from specific structs as luajit can't seem to handle them.
+ if line:find('struct VTermState') then
+ line = string.gsub(line, 'state : 8;', 'state;')
+ end
+ if line:find('VTermStringFragment') then
+ line = string.gsub(line, 'size_t.*len : 30;', 'size_t len;')
+ end
result[#result + 1] = line
end
end
diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua
new file mode 100644
index 0000000000..f0cf450e6c
--- /dev/null
+++ b/test/unit/vterm_spec.lua
@@ -0,0 +1,3591 @@
+local t = require('test.unit.testutil')
+local itp = t.gen_itp(it)
+local bit = require('bit')
+
+--- @class vterm
+--- @field ENC_UTF8 integer
+--- @field VTERM_ATTR_BLINK integer
+--- @field VTERM_ATTR_BOLD integer
+--- @field VTERM_ATTR_FONT integer
+--- @field VTERM_ATTR_ITALIC integer
+--- @field VTERM_ATTR_REVERSE integer
+--- @field VTERM_ATTR_UNDERLINE integer
+--- @field VTERM_BASELINE_RAISE integer
+--- @field VTERM_KEY_ENTER integer
+--- @field VTERM_KEY_FUNCTION_0 integer
+--- @field VTERM_KEY_KP_0 integer
+--- @field VTERM_KEY_NONE integer
+--- @field VTERM_KEY_TAB integer
+--- @field VTERM_KEY_UP integer
+--- @field VTERM_MAX_CHARS_PER_CELL integer
+--- @field VTERM_MOD_ALT integer
+--- @field VTERM_MOD_CTRL integer
+--- @field VTERM_MOD_SHIFT integer
+--- @field parser_apc function
+--- @field parser_csi function
+--- @field parser_dcs function
+--- @field parser_osc function
+--- @field parser_pm function
+--- @field parser_sos function
+--- @field parser_text function
+--- @field print_color function
+--- @field screen_sb_clear function
+--- @field screen_sb_popline function
+--- @field screen_sb_pushline function
+--- @field selection_query function
+--- @field selection_set function
+--- @field state_erase function
+--- @field state_movecursor function
+--- @field state_moverect function
+--- @field state_pos function
+--- @field state_putglyph function
+--- @field state_sb_clear function
+--- @field state_scrollrect function
+--- @field state_setpenattr function
+--- @field state_settermprop function
+--- @field term_output function
+--- @field vterm_input_write function
+--- @field vterm_keyboard_end_paste function
+--- @field vterm_keyboard_key function
+--- @field vterm_keyboard_start_paste function
+--- @field vterm_keyboard_unichar function
+--- @field vterm_lookup_encoding fun(any, any):any
+--- @field vterm_mouse_button function
+--- @field vterm_mouse_move function
+--- @field vterm_new fun(any, any):any
+--- @field vterm_obtain_screen fun(any):any
+--- @field vterm_obtain_state fun(any): any
+--- @field vterm_output_set_callback function
+--- @field vterm_parser_set_callbacks fun(any, any, any):any
+--- @field vterm_screen_convert_color_to_rgb function
+--- @field vterm_screen_enable_altscreen function
+--- @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
+--- @field vterm_screen_set_callbacks function
+--- @field vterm_set_size function
+--- @field vterm_set_utf8 fun(any, any, any):any
+--- @field vterm_state_focus_in function
+--- @field vterm_state_focus_out function
+--- @field vterm_state_get_cursorpos fun(any, any)
+--- @field vterm_state_get_lineinfo fun(any, any):any
+--- @field vterm_state_get_penattr function
+--- @field vterm_state_reset function
+--- @field vterm_state_set_bold_highbright function
+--- @field vterm_state_set_callbacks function
+--- @field vterm_state_set_selection_callbacks function
+--- @field vterm_state_set_unrecognised_fallbacks function
+local vterm = t.cimport('./src/vterm/vterm.h', './src/vterm/vterm_internal.h')
+
+--- @return string
+local function read_rm()
+ local f = assert(io.open(t.paths.vterm_test_file, 'rb'))
+ local text = f:read('*a')
+ f:close()
+ vim.fs.rm(t.paths.vterm_test_file, { force = true })
+ return text
+end
+
+local function append(str)
+ local f = assert(io.open(t.paths.vterm_test_file, 'a'))
+ f:write(str)
+ f:close()
+ return 1
+end
+
+local function parser_control(control)
+ return append(string.format('control %02x\n', control))
+end
+
+local function parser_escape(bytes)
+ return append(string.format('escape %s\n', t.ffi.string(bytes)))
+end
+
+local function wantparser(vt)
+ assert(vt)
+
+ local parser_cbs = t.ffi.new('VTermParserCallbacks')
+ parser_cbs['text'] = vterm.parser_text
+ parser_cbs['control'] = parser_control
+ parser_cbs['escape'] = parser_escape
+ parser_cbs['csi'] = vterm.parser_csi
+ parser_cbs['osc'] = vterm.parser_osc
+ parser_cbs['dcs'] = vterm.parser_dcs
+ parser_cbs['apc'] = vterm.parser_apc
+ parser_cbs['pm'] = vterm.parser_pm
+ parser_cbs['sos'] = vterm.parser_sos
+
+ vterm.vterm_parser_set_callbacks(vt, parser_cbs, nil)
+end
+
+--- @return any
+local function init()
+ local vt = vterm.vterm_new(25, 80)
+ vterm.vterm_output_set_callback(vt, vterm.term_output, nil)
+ vterm.vterm_set_utf8(vt, true)
+ return vt
+end
+
+local function state_setlineinfo()
+ return 1
+end
+
+--- @return any
+local function wantstate(vt, opts)
+ opts = opts or {}
+ assert(vt)
+ local state = vterm.vterm_obtain_state(vt)
+
+ local state_cbs = t.ffi.new('VTermStateCallbacks')
+ state_cbs['putglyph'] = vterm.state_putglyph
+ state_cbs['movecursor'] = vterm.state_movecursor
+ state_cbs['scrollrect'] = vterm.state_scrollrect
+ state_cbs['moverect'] = vterm.state_moverect
+ state_cbs['erase'] = vterm.state_erase
+ state_cbs['setpenattr'] = vterm.state_setpenattr
+ state_cbs['settermprop'] = vterm.state_settermprop
+ state_cbs['setlineinfo'] = state_setlineinfo
+ state_cbs['sb_clear'] = vterm.state_sb_clear
+
+ local selection_cbs = t.ffi.new('VTermSelectionCallbacks')
+ selection_cbs['set'] = vterm.selection_set
+ selection_cbs['query'] = vterm.selection_query
+
+ vterm.vterm_state_set_callbacks(state, state_cbs, nil)
+
+ -- In some tests we want to check the behaviour of overflowing the buffer, so make it nicely small
+ vterm.vterm_state_set_selection_callbacks(state, selection_cbs, nil, nil, 16)
+ vterm.vterm_state_set_bold_highbright(state, 1)
+ vterm.vterm_state_reset(state, 1)
+
+ local fallbacks = t.ffi.new('VTermStateFallbacks')
+ fallbacks['control'] = parser_control
+ fallbacks['csi'] = vterm.parser_csi
+ fallbacks['osc'] = vterm.parser_osc
+ fallbacks['dcs'] = vterm.parser_dcs
+ fallbacks['apc'] = vterm.parser_apc
+ fallbacks['pm'] = vterm.parser_pm
+ fallbacks['sos'] = vterm.parser_sos
+
+ vterm.want_state_scrollback = opts.b or false
+ vterm.want_state_erase = opts.e or false
+ vterm.vterm_state_set_unrecognised_fallbacks(state, opts.f and fallbacks or nil, nil)
+ vterm.want_state_putglyph = opts.g or false
+ vterm.want_state_moverect = opts.m or false
+ vterm.want_state_settermprop = opts.p or false
+ vterm.want_state_scrollrect = opts.s or false
+
+ return state
+end
+
+--- @return any
+local function wantscreen(vt, opts)
+ opts = opts or {}
+ local screen = vterm.vterm_obtain_screen(vt)
+ local screen_cbs = t.ffi.new('VTermScreenCallbacks')
+
+ -- TODO(dundargoc): fix
+ -- screen_cbs['damage'] = vterm.screen_damage
+ screen_cbs['moverect'] = vterm.state_moverect
+ screen_cbs['movecursor'] = vterm.state_movecursor
+ screen_cbs['settermprop'] = vterm.state_settermprop
+ screen_cbs['sb_pushline'] = vterm.screen_sb_pushline
+ screen_cbs['sb_popline'] = vterm.screen_sb_popline
+ screen_cbs['sb_clear'] = vterm.screen_sb_clear
+
+ vterm.vterm_screen_set_callbacks(screen, screen_cbs, nil)
+
+ if opts.a then
+ vterm.vterm_screen_enable_altscreen(screen, 1)
+ end
+ vterm.want_screen_scrollback = opts.b or false
+ vterm.want_state_movecursor = opts.c or false
+ -- TODO(dundargoc): fix
+ -- vterm.want_screen_damage = opts.d or opts.D or false
+ -- vterm.want_screen_cells = opts.D or false
+ vterm.want_state_moverect = opts.m or false
+ vterm.want_state_settermprop = opts.p or false
+ if opts.r then
+ vterm.vterm_screen_enable_reflow(screen, true)
+ end
+
+ return screen
+end
+
+local function reset(state, screen)
+ if state then
+ vterm.vterm_state_reset(state, 1)
+ vterm.vterm_state_get_cursorpos(state, vterm.state_pos)
+ end
+ if screen then
+ vterm.vterm_screen_reset(screen, 1)
+ end
+end
+
+local function push(input, vt)
+ vterm.vterm_input_write(vt, input, string.len(input))
+end
+
+local function expect(expected)
+ local actual = read_rm()
+ t.eq(expected .. '\n', actual)
+end
+
+local function expect_output(expected_preformat)
+ local actual = read_rm()
+ local expected = 'output '
+
+ for c in string.gmatch(expected_preformat, '.') do
+ if expected ~= 'output ' then
+ expected = expected .. ','
+ end
+ expected = string.format('%s%x', expected, string.byte(c))
+ end
+
+ t.eq(expected .. '\n', actual)
+end
+
+local function cursor(row, col, state)
+ local pos = t.ffi.new('VTermPos') --- @type {row: integer, col: integer}
+ vterm.vterm_state_get_cursorpos(state, pos)
+ t.eq(row, pos.row)
+ t.eq(col, pos.col)
+end
+
+local function lineinfo(row, expected, state)
+ local info = vterm.vterm_state_get_lineinfo(state, row)
+ local dwl = info.doublewidth == 1
+ local dhl = info.doubleheight == 1
+ local cont = info.continuation == 1
+
+ t.eq(dwl, expected.dwl or false)
+ t.eq(dhl, expected.dhl or false)
+ t.eq(cont, expected.cont or false)
+end
+
+local function pen(attribute, expected, state)
+ local is_bool = { bold = true, italic = true, blink = true, reverse = true }
+ local vterm_attribute = {
+ bold = vterm.VTERM_ATTR_BOLD,
+ underline = vterm.VTERM_ATTR_UNDERLINE,
+ italic = vterm.VTERM_ATTR_ITALIC,
+ blink = vterm.VTERM_ATTR_BLINK,
+ reverse = vterm.VTERM_ATTR_REVERSE,
+ font = vterm.VTERM_ATTR_FONT,
+ }
+
+ local val = t.ffi.new('VTermValue') --- @type {boolean: integer}
+ vterm.vterm_state_get_penattr(state, vterm_attribute[attribute], val)
+ local actual = val.boolean --- @type integer|boolean
+ if is_bool[attribute] then
+ actual = val.boolean == 1
+ end
+ t.eq(expected, actual)
+end
+
+local function resize(rows, cols, vt)
+ vterm.vterm_set_size(vt, rows, cols)
+end
+
+local function screen_chars(start_row, start_col, end_row, end_col, expected, screen)
+ local rect = t.ffi.new('VTermRect')
+ rect['start_row'] = start_row
+ rect['start_col'] = start_col
+ rect['end_row'] = end_row
+ rect['end_col'] = end_col
+
+ local len = vterm.vterm_screen_get_chars(screen, nil, 0, rect)
+
+ local chars = t.ffi.new('uint32_t[?]', len)
+ vterm.vterm_screen_get_chars(screen, chars, len, rect)
+
+ local actual = ''
+ for i = 0, tonumber(len) - 1 do
+ actual = actual .. string.char(chars[i])
+ end
+
+ t.eq(expected, actual)
+end
+
+local function screen_text(start_row, start_col, end_row, end_col, expected, screen)
+ local rect = t.ffi.new('VTermRect')
+ rect['start_row'] = start_row
+ rect['start_col'] = start_col
+ rect['end_row'] = end_row
+ rect['end_col'] = end_col
+
+ local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
+
+ local text = t.ffi.new('unsigned char[?]', len)
+ vterm.vterm_screen_get_text(screen, text, len, rect)
+
+ local actual = ''
+ for i = 0, tonumber(len) - 1 do
+ actual = string.format('%s%02x,', actual, text[i])
+ end
+ actual = actual:sub(1, -2)
+
+ t.eq(expected, actual)
+end
+
+--- @param row integer
+local function screen_row(row, expected, screen, end_col)
+ local rect = t.ffi.new('VTermRect')
+ rect['start_row'] = row
+ rect['start_col'] = 0
+ rect['end_row'] = row + 1
+ rect['end_col'] = end_col or 80
+
+ local len = vterm.vterm_screen_get_text(screen, nil, 0, rect)
+
+ local text = t.ffi.new('unsigned char[?]', len)
+ vterm.vterm_screen_get_text(screen, text, len, rect)
+
+ t.eq(expected, t.ffi.string(text))
+end
+
+local function screen_cell(row, col, expected, screen)
+ local pos = t.ffi.new('VTermPos')
+ pos['row'] = row
+ pos['col'] = col
+
+ local cell = t.ffi.new('VTermScreenCell')
+ vterm.vterm_screen_get_cell(screen, pos, cell)
+
+ local actual = '{'
+ for i = 0, vterm.VTERM_MAX_CHARS_PER_CELL - 1 do
+ if cell['chars'][i] ~= 0 then
+ if i > 0 then
+ actual = actual .. ','
+ end
+ actual = string.format('%s%02x', actual, cell['chars'][i])
+ end
+ end
+ actual = string.format('%s} width=%d attrs={', actual, cell['width'])
+ actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '')
+ actual = actual
+ .. (cell['attrs'].underline ~= 0 and string.format('U%d', cell['attrs'].underline) or '')
+ actual = actual .. (cell['attrs'].italic ~= 0 and 'I' or '')
+ actual = actual .. (cell['attrs'].blink ~= 0 and 'K' or '')
+ actual = actual .. (cell['attrs'].reverse ~= 0 and 'R' or '')
+ actual = actual .. (cell['attrs'].font ~= 0 and string.format('F%d', cell['attrs'].font) or '')
+ actual = actual .. (cell['attrs'].small ~= 0 and 'S' or '')
+ if cell['attrs'].baseline ~= 0 then
+ actual = actual .. (cell['attrs'].baseline == vterm.VTERM_BASELINE_RAISE and '^' or '_')
+ end
+ actual = actual .. '} '
+
+ actual = actual .. (cell['attrs'].dwl ~= 0 and 'dwl ' or '')
+ if cell['attrs'].dhl ~= 0 then
+ actual = actual .. string.format('dhl-%s ', cell['attrs'].dhl == 2 and 'bottom' or 'top')
+ end
+
+ actual = string.format('%sfg=', actual)
+ vterm.vterm_screen_convert_color_to_rgb(screen, cell['fg'])
+ vterm.print_color(cell['fg'])
+
+ actual = actual .. read_rm()
+ actual = actual .. ' bg='
+
+ vterm.vterm_screen_convert_color_to_rgb(screen, cell['bg'])
+ vterm.print_color(cell['bg'])
+
+ actual = actual .. read_rm()
+
+ t.eq(expected, actual)
+end
+
+local function screen_eol(row, col, expected, screen)
+ local pos = t.ffi.new('VTermPos')
+ pos['row'] = row
+ pos['col'] = col
+
+ local is_eol = vterm.vterm_screen_is_eol(screen, pos)
+ t.eq(expected, is_eol)
+end
+
+local function screen_attrs_extent(row, col, expected, screen)
+ local pos = t.ffi.new('VTermPos')
+ pos['row'] = row
+ pos['col'] = col
+
+ local rect = t.ffi.new('VTermRect')
+ rect['start_col'] = 0
+ rect['end_col'] = -1
+ vterm.vterm_screen_get_attrs_extent(screen, rect, pos, 1)
+
+ local actual = string.format(
+ '%d,%d-%d,%d',
+ rect['start_row'],
+ rect['start_col'],
+ rect['end_row'],
+ rect['end_col']
+ )
+
+ t.eq(expected, actual)
+end
+
+local function wantencoding()
+ local encoding = t.ffi.new('VTermEncodingInstance')
+ encoding['enc'] = vterm.vterm_lookup_encoding(vterm.ENC_UTF8, string.byte('u'))
+ if encoding.enc.init then
+ encoding.enc.init(encoding.enc, encoding['data'])
+ end
+ return encoding
+end
+
+local function encin(input, encoding)
+ local len = string.len(input)
+
+ local cp = t.ffi.new('uint32_t[?]', len)
+ local cpi = t.ffi.new('int[1]')
+ local pos = t.ffi.new('size_t[1]', 0)
+
+ encoding.enc.decode(encoding.enc, encoding.data, cp, cpi, len, input, pos, len)
+
+ local f = assert(io.open(t.paths.vterm_test_file, 'w'))
+ if tonumber(cpi[0]) > 0 then
+ f:write('encout ')
+ for i = 0, cpi[0] - 1 do
+ if i == 0 then
+ f:write(string.format('%x', cp[i]))
+ else
+ f:write(string.format(',%x', cp[i]))
+ end
+ end
+ f:write('\n')
+ end
+ f:close()
+end
+
+local function strpe_modifiers(input_mod)
+ local mod = t.ffi.new('VTermModifier') ---@type any
+ if input_mod.C then
+ mod = bit.bor(mod, vterm.VTERM_MOD_CTRL)
+ end
+ if input_mod.S then
+ mod = bit.bor(mod, vterm.VTERM_MOD_SHIFT)
+ end
+ if input_mod.A then
+ mod = bit.bor(mod, vterm.VTERM_MOD_ALT)
+ end
+ return mod
+end
+
+local function strp_key(input_key)
+ if input_key == 'up' then
+ return vterm.VTERM_KEY_UP
+ end
+
+ if input_key == 'tab' then
+ return vterm.VTERM_KEY_TAB
+ end
+
+ if input_key == 'enter' then
+ return vterm.VTERM_KEY_ENTER
+ end
+
+ if input_key == 'f1' then
+ return vterm.VTERM_KEY_FUNCTION_0 + 1
+ end
+
+ if input_key ==