aboutsummaryrefslogtreecommitdiff
path: root/test/unit
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
committerJosh Rahm <joshuarahm@gmail.com>2025-02-05 23:09:29 +0000
commitd5f194ce780c95821a855aca3c19426576d28ae0 (patch)
treed45f461b19f9118ad2bb1f440a7a08973ad18832 /test/unit
parentc5d770d311841ea5230426cc4c868e8db27300a8 (diff)
parent44740e561fc93afe3ebecfd3618bda2d2abeafb0 (diff)
downloadrneovim-rahm.tar.gz
rneovim-rahm.tar.bz2
rneovim-rahm.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309HEADrahm
Diffstat (limited to 'test/unit')
-rw-r--r--test/unit/fixtures/multiqueue.c5
-rw-r--r--test/unit/fixtures/multiqueue.h4
-rw-r--r--test/unit/fixtures/posix.h6
-rw-r--r--test/unit/fixtures/vterm_test.c789
-rw-r--r--test/unit/fixtures/vterm_test.h45
-rw-r--r--test/unit/mbyte_spec.lua6
-rw-r--r--test/unit/optionstr_spec.lua4
-rw-r--r--test/unit/strings_spec.lua26
-rw-r--r--test/unit/vterm_spec.lua392
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)