diff options
author | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
---|---|---|
committer | Josh Rahm <joshuarahm@gmail.com> | 2025-02-05 23:09:29 +0000 |
commit | d5f194ce780c95821a855aca3c19426576d28ae0 (patch) | |
tree | d45f461b19f9118ad2bb1f440a7a08973ad18832 /test/unit | |
parent | c5d770d311841ea5230426cc4c868e8db27300a8 (diff) | |
parent | 44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff) | |
download | rneovim-rahm.tar.gz rneovim-rahm.tar.bz2 rneovim-rahm.zip |
Diffstat (limited to 'test/unit')
-rw-r--r-- | test/unit/fixtures/multiqueue.c | 5 | ||||
-rw-r--r-- | test/unit/fixtures/multiqueue.h | 4 | ||||
-rw-r--r-- | test/unit/fixtures/posix.h | 6 | ||||
-rw-r--r-- | test/unit/fixtures/vterm_test.c | 789 | ||||
-rw-r--r-- | test/unit/fixtures/vterm_test.h | 45 | ||||
-rw-r--r-- | test/unit/mbyte_spec.lua | 6 | ||||
-rw-r--r-- | test/unit/optionstr_spec.lua | 4 | ||||
-rw-r--r-- | test/unit/strings_spec.lua | 26 | ||||
-rw-r--r-- | test/unit/vterm_spec.lua | 392 |
9 files changed, 1174 insertions, 103 deletions
diff --git a/test/unit/fixtures/multiqueue.c b/test/unit/fixtures/multiqueue.c index 149daca893..2003bc7a5a 100644 --- a/test/unit/fixtures/multiqueue.c +++ b/test/unit/fixtures/multiqueue.c @@ -1,8 +1,9 @@ -#include <string.h> #include <stdlib.h> -#include "nvim/event/multiqueue.h" +#include <string.h> + #include "multiqueue.h" +#include "nvim/event/multiqueue.h" void ut_multiqueue_put(MultiQueue *self, const char *str) { diff --git a/test/unit/fixtures/multiqueue.h b/test/unit/fixtures/multiqueue.h index 78a3a89063..37da1d4db9 100644 --- a/test/unit/fixtures/multiqueue.h +++ b/test/unit/fixtures/multiqueue.h @@ -1,4 +1,4 @@ #include "nvim/event/multiqueue.h" -void ut_multiqueue_put(MultiQueue *queue, const char *str); -const char *ut_multiqueue_get(MultiQueue *queue); +void ut_multiqueue_put(MultiQueue *self, const char *str); +const char *ut_multiqueue_get(MultiQueue *self); diff --git a/test/unit/fixtures/posix.h b/test/unit/fixtures/posix.h index f6f24cd9dc..0d16f8aac9 100644 --- a/test/unit/fixtures/posix.h +++ b/test/unit/fixtures/posix.h @@ -1,8 +1,8 @@ -#include <unistd.h> -#include <string.h> #include <errno.h> -#include <sys/wait.h> #include <stdlib.h> +#include <string.h> +#include <sys/wait.h> +#include <unistd.h> enum { kPOSIXErrnoEINTR = EINTR, diff --git a/test/unit/fixtures/vterm_test.c b/test/unit/fixtures/vterm_test.c new file mode 100644 index 0000000000..6744305960 --- /dev/null +++ b/test/unit/fixtures/vterm_test.c @@ -0,0 +1,789 @@ +#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) +{ + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "text "); + size_t i; + for (i = 0; i < len; i++) { + unsigned char b = (unsigned char)bytes[i]; + if (b < 0x20 || b == 0x7f || (b >= 0x80 && b < 0xa0)) { + break; + } + fprintf(f, i ? ",%x" : "%x", b); + } + fprintf(f, "\n"); + fclose(f); + + return (int)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 (size_t 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; +} + +static void print_schar(FILE *f, schar_T schar) +{ + char buf[MAX_SCHAR_SIZE]; + schar_get(buf, schar); + StrCharInfo ci = utf_ptr2StrCharInfo(buf); + bool did = false; + while (*ci.ptr != 0) { + if (did) { + fprintf(f, ","); + } + + if (ci.chr.len == 1 && ci.chr.value >= 0x80) { + fprintf(f, "??%x", ci.chr.value); + } else { + fprintf(f, "%x", ci.chr.value); + } + did = true; + ci = utf_ptr2StrCharInfo(ci.ptr + ci.chr.len); + } +} + +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 "); + print_schar(f, info->schar); + 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); +} + +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_PROP_THEMEUPDATES: + 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) +{ + 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].schar) { + eol--; + } + + FILE *f = fopen(VTERM_TEST_FILE, "a"); + fprintf(f, "sb_pushline %d =", cols); + for (int c = 0; c < eol; c++) { + fprintf(f, " "); + print_schar(f, cells[c].schar); + } + 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].schar = schar_from_ascii((uint32_t)('A' + col)); + } else { + cells[col].schar = 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 (size_t i = 0; i < len; i++) { + fprintf(f, "%x%s", (unsigned char)s[i], i < len - 1 ? "," : "\n"); + } + 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 new file mode 100644 index 0000000000..ef6463af6d --- /dev/null +++ b/test/unit/fixtures/vterm_test.h @@ -0,0 +1,45 @@ +#include <stdbool.h> +#include <stdint.h> + +#include "nvim/macros_defs.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); +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); +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/mbyte_spec.lua b/test/unit/mbyte_spec.lua index bdc111de2c..2c52aa9217 100644 --- a/test/unit/mbyte_spec.lua +++ b/test/unit/mbyte_spec.lua @@ -58,11 +58,11 @@ describe('mbyte', function() lib.schar_get(buf, lib.utfc_ptr2schar(to_string(seq), firstc)) local str = ffi.string(buf) if 1 > 2 then -- for debugging - local tabel = {} + local tbl = {} for i = 1, #str do - table.insert(tabel, string.format('0x%02x', string.byte(str, i))) + table.insert(tbl, string.format('0x%02x', string.byte(str, i))) end - print('{ ' .. table.concat(tabel, ', ') .. ' }') + print('{ ' .. table.concat(tbl, ', ') .. ' }') io.stdout:flush() end return { str, firstc[0] } diff --git a/test/unit/optionstr_spec.lua b/test/unit/optionstr_spec.lua index b9c9ceaa85..1f5b42485f 100644 --- a/test/unit/optionstr_spec.lua +++ b/test/unit/optionstr_spec.lua @@ -11,8 +11,8 @@ local check_ff_value = function(ff) end describe('check_ff_value', function() - itp('views empty string as valid', function() - eq(1, check_ff_value('')) + itp('views empty string as invalid', function() + eq(0, check_ff_value('')) end) itp('views "unix", "dos" and "mac" as valid', function() diff --git a/test/unit/strings_spec.lua b/test/unit/strings_spec.lua index 25cdc27b28..2b7a4d6261 100644 --- a/test/unit/strings_spec.lua +++ b/test/unit/strings_spec.lua @@ -1,6 +1,7 @@ local t = require('test.unit.testutil') local itp = t.gen_itp(it) +local child_call_once = t.child_call_once local cimport = t.cimport local eq = t.eq local ffi = t.ffi @@ -8,6 +9,12 @@ local to_cstr = t.to_cstr local strings = cimport('stdlib.h', './src/nvim/strings.h', './src/nvim/memory.h') +local UVARNUM_TYPE + +child_call_once(function() + UVARNUM_TYPE = ffi.typeof('uvarnumber_T') +end) + describe('vim_strsave_escaped()', function() local vim_strsave_escaped = function(s, chars) local res = strings.vim_strsave_escaped(to_cstr(s), to_cstr(chars)) @@ -140,13 +147,22 @@ end) describe('vim_snprintf()', function() local function a(expected, buf, bsize, fmt, ...) - eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...)) + local args = { ... } + local ctx = string.format('snprintf(buf, %d, "%s"', bsize, fmt) + for _, x in ipairs(args) do + ctx = ctx .. ', ' .. tostring(x) + end + ctx = ctx .. string.format(') = %s', expected) + eq(#expected, strings.vim_snprintf(buf, bsize, fmt, ...), ctx) if bsize > 0 then local actual = ffi.string(buf, math.min(#expected + 1, bsize)) eq(expected:sub(1, bsize - 1) .. '\0', actual) end end + local function uv(n) + return ffi.cast(UVARNUM_TYPE, n) + end local function i(n) return ffi.cast('int', n) end @@ -181,7 +197,7 @@ describe('vim_snprintf()', function() a(' 1234567', buf, bsize, '%9ld', l(1234567)) a('1234567 ', buf, bsize, '%-9ld', l(1234567)) a('deadbeef', buf, bsize, '%x', u(0xdeadbeef)) - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('one two', buf, bsize, '%s %s', 'one', 'two') a('1.234000', buf, bsize, '%f', 1.234) a('1.234000e+00', buf, bsize, '%e', 1.234) @@ -223,10 +239,10 @@ describe('vim_snprintf()', function() a('three one two', buf, bsize, '%3$s %1$s %2$s', 'one', 'two', 'three') a('1234567', buf, bsize, '%1$d', i(1234567)) a('deadbeef', buf, bsize, '%1$x', u(0xdeadbeef)) - a('001100', buf, bsize, '%2$0*1$b', i(6), u(12)) - a('001100', buf, bsize, '%1$0.*2$b', u(12), i(6)) + a('001100', buf, bsize, '%2$0*1$b', i(6), uv(12)) + a('001100', buf, bsize, '%1$0.*2$b', uv(12), i(6)) a('one two', buf, bsize, '%1$s %2$s', 'one', 'two') - a('001100', buf, bsize, '%06b', u(12)) + a('001100', buf, bsize, '%06b', uv(12)) a('two one', buf, bsize, '%2$s %1$s', 'one', 'two') a('1.234000', buf, bsize, '%1$f', 1.234) a('1.234000e+00', buf, bsize, '%1$e', 1.234) diff --git a/test/unit/vterm_spec.lua b/test/unit/vterm_spec.lua index 4ea5d9c29a..c5293a21cb 100644 --- a/test/unit/vterm_spec.lua +++ b/test/unit/vterm_spec.lua @@ -17,7 +17,9 @@ local bit = require('bit') --- @field VTERM_KEY_NONE integer --- @field VTERM_KEY_TAB integer --- @field VTERM_KEY_UP integer ---- @field VTERM_MAX_CHARS_PER_CELL integer +--- @field VTERM_KEY_BACKSPACE integer +--- @field VTERM_KEY_ESCAPE integer +--- @field VTERM_KEY_DEL integer --- @field VTERM_MOD_ALT integer --- @field VTERM_MOD_CTRL integer --- @field VTERM_MOD_SHIFT integer @@ -29,6 +31,7 @@ local bit = require('bit') --- @field parser_sos function --- @field parser_text function --- @field print_color function +--- @field schar_get fun(any, any):integer --- @field screen_sb_clear function --- @field screen_sb_popline function --- @field screen_sb_pushline function @@ -44,6 +47,8 @@ local bit = require('bit') --- @field state_setpenattr function --- @field state_settermprop function --- @field term_output function +--- @field utf_ptr2char fun(any):integer +--- @field utf_ptr2len fun(any):integer --- @field vterm_input_write function --- @field vterm_keyboard_end_paste function --- @field vterm_keyboard_key function @@ -62,7 +67,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,7 +83,20 @@ local bit = require('bit') --- @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') +local vterm = t.cimport( + './src/nvim/grid.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' +) --- @return string local function read_rm() @@ -298,16 +315,12 @@ local function screen_chars(start_row, start_col, end_row, end_col, expected, sc 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 len = vterm.vterm_screen_get_text(screen, nil, 0, rect) - local actual = '' - for i = 0, tonumber(len) - 1 do - actual = actual .. string.char(chars[i]) - end + local text = t.ffi.new('unsigned char[?]', len) + vterm.vterm_screen_get_text(screen, text, len, rect) + local actual = t.ffi.string(text, len) t.eq(expected, actual) end @@ -345,7 +358,7 @@ local function screen_row(row, expected, screen, end_col) local text = t.ffi.new('unsigned char[?]', len) vterm.vterm_screen_get_text(screen, text, len, rect) - t.eq(expected, t.ffi.string(text)) + t.eq(expected, t.ffi.string(text, len)) end local function screen_cell(row, col, expected, screen) @@ -353,17 +366,23 @@ local function screen_cell(row, col, expected, screen) pos['row'] = row pos['col'] = col - local cell = t.ffi.new('VTermScreenCell') + local cell = t.ffi.new('VTermScreenCell') ---@type any vterm.vterm_screen_get_cell(screen, pos, cell) + local buf = t.ffi.new('unsigned char[32]') + vterm.schar_get(buf, cell.schar) + 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]) + local i = 0 + while buf[i] > 0 do + local char = vterm.utf_ptr2char(buf + i) + local charlen = vterm.utf_ptr2len(buf + i) + if i > 0 then + actual = actual .. ',' end + local invalid = char >= 128 and charlen == 1 + actual = string.format('%s%s%02x', actual, invalid and '?' or '', char) + i = i + charlen end actual = string.format('%s} width=%d attrs={', actual, cell['width']) actual = actual .. (cell['attrs'].bold ~= 0 and 'B' or '') @@ -489,6 +508,18 @@ local function strp_key(input_key) return vterm.VTERM_KEY_ENTER end + if input_key == 'bs' then + return vterm.VTERM_KEY_BACKSPACE + end + + if input_key == 'del' then + return vterm.VTERM_KEY_DEL + end + + if input_key == 'esc' then + return vterm.VTERM_KEY_ESCAPE + end + if input_key == 'f1' then return vterm.VTERM_KEY_FUNCTION_0 + 1 end @@ -958,8 +989,8 @@ describe('vterm', function() -- Spare combining chars get truncated reset(state, nil) - push('e' .. string.rep('\xCC\x81', 10), vt) - expect('putglyph 65,301,301,301,301,301 1 0,0') -- and nothing more + push('e' .. string.rep('\xCC\x81', 20), vt) + expect('putglyph 65,301,301,301,301,301,301,301,301,301,301,301,301,301,301 1 0,0') -- and nothing more reset(state, nil) push('e', vt) @@ -969,6 +1000,34 @@ describe('vterm', function() push('\xCC\x82', vt) expect('putglyph 65,301,302 1 0,0') + -- emoji with ZWJ and variant selectors, as one chunk + reset(state, nil) + push('🏳️🌈🏳️⚧️🏴☠️', vt) + expect([[putglyph 1f3f3,fe0f,200d,1f308 2 0,0 +putglyph 1f3f3,fe0f,200d,26a7,fe0f 2 0,2 +putglyph 1f3f4,200d,2620,fe0f 2 0,4]]) + + -- emoji, one code point at a time + reset(state, nil) + push('🏳', vt) + expect('putglyph 1f3f3 2 0,0') + push('\xef\xb8\x8f', vt) + expect('putglyph 1f3f3,fe0f 2 0,0') + push('\xe2\x80\x8d', vt) + expect('putglyph 1f3f3,fe0f,200d 2 0,0') + push('🌈', vt) + expect('putglyph 1f3f3,fe0f,200d,1f308 2 0,0') + + -- modifier can change width + push('❤', vt) + expect('putglyph 2764 1 0,2') + push('\xef\xb8\x8f', vt) + expect('putglyph 2764,fe0f 2 0,2') + + -- also works batched + push('❤️', vt) + expect('putglyph 2764,fe0f 2 0,4') + -- DECSCA protected reset(state, nil) push('A\x1b[1"qB\x1b[2"qC', vt) @@ -1090,7 +1149,7 @@ describe('vterm', function() push('\x1b[0F', vt) cursor(0, 0, state) - -- Cursor Horizonal Absolute + -- Cursor Horizontal Absolute push('\n', vt) cursor(1, 0, state) push('\x1b[20G', vt) @@ -1664,12 +1723,6 @@ describe('vterm', function() push('#', vt) expect('putglyph 23 1 0,0') - -- Designate G0=UK - reset(state, nil) - push('\x1b(A', vt) - push('#', vt) - expect('putglyph a3 1 0,0') - -- Designate G0=DEC drawing reset(state, nil) push('\x1b(0', vt) @@ -2026,6 +2079,18 @@ describe('vterm', function() mousebtn('u', 1, vt) expect_output('\x1b[<0;301;301m') + -- Button 8 on SGR extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[<128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[<128;301;301m') + + -- Button 9 on SGR extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[<129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[<129;301;301m') + -- DECRQM on SGR extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') @@ -2041,6 +2106,18 @@ describe('vterm', function() mousebtn('u', 1, vt) expect_output('\x1b[3;301;301M') + -- Button 8 on rxvt extended encoding mode + mousebtn('d', 8, vt) + expect_output('\x1b[128;301;301M') + mousebtn('u', 8, vt) + expect_output('\x1b[3;301;301M') + + -- Button 9 on rxvt extended encoding mode + mousebtn('d', 9, vt) + expect_output('\x1b[129;301;301M') + mousebtn('u', 9, vt) + expect_output('\x1b[3;301;301M') + -- DECRQM on rxvt extended encoding mode push('\x1b[?1005$p', vt) expect_output('\x1b[?1005;2$y') @@ -2286,65 +2363,83 @@ describe('vterm', function() local vt = init() local state = wantstate(vt) + -- Disambiguate escape codes enabled + push('\x1b[>1u', vt) + -- Unmodified ASCII - inchar(41, vt) - expect('output 29') - inchar(61, vt) - expect('output 3d') + inchar(0x41, vt) + expect_output('A') + inchar(0x61, vt) + expect_output('a') -- Ctrl modifier on ASCII letters - inchar(41, vt, { C = true }) - expect('output 1b,5b,34,31,3b,35,75') - inchar(61, vt, { C = true }) - expect('output 1b,5b,36,31,3b,35,75') + inchar(0x41, vt, { C = true }) + expect_output('\x1b[97;6u') + inchar(0x61, vt, { C = true }) + expect_output('\x1b[97;5u') -- Alt modifier on ASCII letters - inchar(41, vt, { A = true }) - expect('output 1b,29') - inchar(61, vt, { A = true }) - expect('output 1b,3d') + inchar(0x41, vt, { A = true }) + expect_output('\x1b[97;4u') + inchar(0x61, vt, { A = true }) + expect_output('\x1b[97;3u') -- Ctrl-Alt modifier on ASCII letters - inchar(41, vt, { C = true, A = true }) - expect('output 1b,5b,34,31,3b,37,75') - inchar(61, vt, { C = true, A = true }) - expect('output 1b,5b,36,31,3b,37,75') - - -- Special handling of Ctrl-I - inchar(49, vt) - expect('output 31') - inchar(69, vt) - expect('output 45') - inchar(49, vt, { C = true }) - expect('output 1b,5b,34,39,3b,35,75') - inchar(69, vt, { C = true }) - expect('output 1b,5b,36,39,3b,35,75') - inchar(49, vt, { A = true }) - expect('output 1b,31') - inchar(69, vt, { A = true }) - expect('output 1b,45') - inchar(49, vt, { A = true, C = true }) - expect('output 1b,5b,34,39,3b,37,75') - inchar(69, vt, { A = true, C = true }) - expect('output 1b,5b,36,39,3b,37,75') + inchar(0x41, vt, { C = true, A = true }) + expect_output('\x1b[97;8u') + inchar(0x61, vt, { C = true, A = true }) + expect_output('\x1b[97;7u') + + -- Ctrl-I is disambiguated + inchar(0x49, vt) + expect_output('I') + inchar(0x69, vt) + expect_output('i') + inchar(0x49, vt, { C = true }) + expect_output('\x1b[105;6u') + inchar(0x69, vt, { C = true }) + expect_output('\x1b[105;5u') + inchar(0x49, vt, { A = true }) + expect_output('\x1b[105;4u') + inchar(0x69, vt, { A = true }) + expect_output('\x1b[105;3u') + inchar(0x49, vt, { A = true, C = true }) + expect_output('\x1b[105;8u') + inchar(0x69, vt, { A = true, C = true }) + expect_output('\x1b[105;7u') + + -- Ctrl+Digits + for i = 0, 9 do + local c = 0x30 + i + inchar(c, vt) + expect_output(tostring(i)) + inchar(c, vt, { C = true }) + expect_output(string.format('\x1b[%d;5u', c)) + inchar(c, vt, { C = true, S = true }) + expect_output(string.format('\x1b[%d;6u', c)) + inchar(c, vt, { C = true, A = true }) + expect_output(string.format('\x1b[%d;7u', c)) + inchar(c, vt, { C = true, A = true, S = true }) + expect_output(string.format('\x1b[%d;8u', c)) + end -- Special handling of Space - inchar(20, vt) - expect('output 14') - inchar(20, vt, { S = true }) - expect('output 14') - inchar(20, vt, { C = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { C = true, S = true }) - expect('output 1b,5b,32,30,3b,35,75') - inchar(20, vt, { A = true }) - expect('output 1b,14') - inchar(20, vt, { S = true, A = true }) - expect('output 1b,14') - inchar(20, vt, { C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') - inchar(20, vt, { S = true, C = true, A = true }) - expect('output 1b,5b,32,30,3b,37,75') + inchar(0x20, vt) + expect_output(' ') + inchar(0x20, vt, { S = true }) + expect_output('\x1b[32;2u') + inchar(0x20, vt, { C = true }) + expect_output('\x1b[32;5u') + inchar(0x20, vt, { C = true, S = true }) + expect_output('\x1b[32;6u') + inchar(0x20, vt, { A = true }) + expect_output('\x1b[32;3u') + inchar(0x20, vt, { S = true, A = true }) + expect_output('\x1b[32;4u') + inchar(0x20, vt, { C = true, A = true }) + expect_output('\x1b[32;7u') + inchar(0x20, vt, { S = true, C = true, A = true }) + expect_output('\x1b[32;8u') -- Cursor keys in reset (cursor) mode inkey('up', vt) @@ -2375,21 +2470,65 @@ describe('vterm', function() inkey('up', vt, { C = true }) expect_output('\x1b[1;5A') - -- Shift-Tab should be different + -- Tab inkey('tab', vt) expect_output('\x09') inkey('tab', vt, { S = true }) - expect_output('\x1b[Z') + expect_output('\x1b[9;2u') inkey('tab', vt, { C = true }) expect_output('\x1b[9;5u') inkey('tab', vt, { A = true }) - expect_output('\x1b\x09') + expect_output('\x1b[9;3u') inkey('tab', vt, { C = true, A = true }) expect_output('\x1b[9;7u') + -- Backspace + inkey('bs', vt) + expect_output('\x7f') + inkey('bs', vt, { S = true }) + expect_output('\x1b[127;2u') + inkey('bs', vt, { C = true }) + expect_output('\x1b[127;5u') + inkey('bs', vt, { A = true }) + expect_output('\x1b[127;3u') + inkey('bs', vt, { C = true, A = true }) + expect_output('\x1b[127;7u') + + -- DEL + inkey('del', vt) + expect_output('\x1b[3~') + inkey('del', vt, { S = true }) + expect_output('\x1b[3;2~') + inkey('del', vt, { C = true }) + expect_output('\x1b[3;5~') + inkey('del', vt, { A = true }) + expect_output('\x1b[3;3~') + inkey('del', vt, { C = true, A = true }) + expect_output('\x1b[3;7~') + + -- ESC + inkey('esc', vt) + expect_output('\x1b[27;1u') + inkey('esc', vt, { S = true }) + expect_output('\x1b[27;2u') + inkey('esc', vt, { C = true }) + expect_output('\x1b[27;5u') + inkey('esc', vt, { A = true }) + expect_output('\x1b[27;3u') + inkey('esc', vt, { C = true, A = true }) + expect_output('\x1b[27;7u') + -- Enter in linefeed mode inkey('enter', vt) expect_output('\x0d') + inkey('enter', vt, { S = true }) + expect_output('\x1b[13;2u') + inkey('enter', vt, { C = true }) + expect_output('\x1b[13;5u') + inkey('enter', vt, { A = true }) + expect_output('\x1b[13;3u') + inkey('enter', vt, { C = true, A = true }) + expect_output('\x1b[13;7u') -- Enter in newline mode push('\x1b[20h', vt) @@ -2410,7 +2549,7 @@ describe('vterm', function() -- Keypad in DECKPNM inkey('kp0', vt) - expect_output('0') + expect_output('\x1b[57399;1u') -- Keypad in DECKPAM push('\x1b=', vt) @@ -2440,6 +2579,77 @@ describe('vterm', function() expect_output('\x1b[I') vterm.vterm_state_focus_out(state) expect_output('\x1b[O') + + -- Disambiguate escape codes disabled + push('\x1b[<u', vt) + + -- Unmodified ASCII + inchar(0x41, vt) + expect_output('A') + inchar(0x61, vt) + expect_output('a') + + -- Ctrl modifier on ASCII letters + inchar(0x41, vt, { C = true }) + expect_output('\x01') + inchar(0x61, vt, { C = true }) + expect_output('\x01') + + -- Alt modifier on ASCII letters + inchar(0x41, vt, { A = true }) + expect_output('\x1bA') + inchar(0x61, vt, { A = true }) + expect_output('\x1ba') + + -- Ctrl-Alt modifier on ASCII letters + inchar(0x41, vt, { C = true, A = true }) + expect_output('\x1b\x01') + inchar(0x61, vt, { C = true, A = true }) + expect_output('\x1b\x01') + + -- Ctrl-I is ambiguous + inchar(0x49, vt) + expect_output('I') + inchar(0x69, vt) + expect_output('i') + inchar(0x49, vt, { C = true }) + expect_output('\x09') + inchar(0x69, vt, { C = true }) + expect_output('\x09') + inchar(0x49, vt, { A = true }) + expect_output('\x1bI') + inchar(0x69, vt, { A = true }) + expect_output('\x1bi') + inchar(0x49, vt, { A = true, C = true }) + expect_output('\x1b\x09') + inchar(0x69, vt, { A = true, C = true }) + expect_output('\x1b\x09') + + -- Ctrl+Digits + inchar(0x30, vt, { C = true }) + expect_output('0') + inchar(0x31, vt, { C = true }) + expect_output('1') + inchar(0x32, vt, { C = true }) + expect_output('\x00') + inchar(0x33, vt, { C = true }) + expect_output('\x1b') + inchar(0x34, vt, { C = true }) + expect_output('\x1c') + inchar(0x35, vt, { C = true }) + expect_output('\x1d') + inchar(0x36, vt, { C = true }) + expect_output('\x1e') + inchar(0x37, vt, { C = true }) + expect_output('\x1f') + inchar(0x38, vt, { C = true }) + expect_output('\x7f') + inchar(0x39, vt, { C = true }) + expect_output('9') + + -- Ctrl+/ + inchar(0x2F, vt, { C = true }) + expect_output('\x1f') end) itp('26state_query', function() @@ -3042,7 +3252,7 @@ describe('vterm', function() screen_cell( 0, 0, - '{65,301,302,303,304,305} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', + '{65,301,302,303,304,305,306,307,308,309,30a} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen ) @@ -3059,15 +3269,25 @@ describe('vterm', function() screen_cell( 0, 0, - '{65,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', + '{65,301,301,301,301,301,301,301,301,301,301,301,301,301,301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen ) - -- Outputing CJK doublewidth in 80th column should wraparound to next line and not crash" + -- Outputting CJK doublewidth in 80th column should wraparound to next line and not crash" reset(nil, screen) push('\x1b[80G\xEF\xBC\x90', vt) screen_cell(0, 79, '{} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) screen_cell(1, 0, '{ff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + + -- Outputting emoji with ZWJ and variant selectors + reset(nil, screen) + push('🏳️🌈🏳️⚧️🏴☠️', vt) + + -- stylua: ignore start + screen_cell(0, 0, '{1f3f3,fe0f,200d,1f308} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + screen_cell(0, 2, '{1f3f3,fe0f,200d,26a7,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + screen_cell(0, 4, '{1f3f4,200d,2620,fe0f} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)', screen) + -- stylua: ignore end end) pending('62screen_damage', function() end) @@ -3121,7 +3341,7 @@ describe('vterm', function() screen = wantscreen(vt, { b = true }) resize(20, 80, vt) expect( - 'sb_pushline 80 = 54 6F 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =' + 'sb_pushline 80 = 54 6f 70\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =\nsb_pushline 80 =' ) -- TODO(dundargoc): fix or remove -- screen_row( 0 , "",screen) |