summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames McCoy <jamessan@debian.org>2022-09-12 20:28:03 -0400
committerJames McCoy <jamessan@debian.org>2022-09-12 20:28:03 -0400
commit97864b073a8a8cc553a844063242a2b0dec364a3 (patch)
treef34b2af02dca0b8f47445568379515825ca9b444
parent2727851f76a12754bcc7605ba715a05f0f5e5ec0 (diff)
New upstream version 0.3~rc1
-rw-r--r--CONTRIBUTING4
-rw-r--r--Makefile2
-rw-r--r--bin/vterm-ctrl.c2
-rw-r--r--doc/seqs.txt4
-rw-r--r--include/vterm.h45
-rw-r--r--src/pen.c52
-rw-r--r--src/screen.c233
-rw-r--r--src/state.c95
-rw-r--r--src/vterm.c39
-rw-r--r--src/vterm_internal.h4
-rw-r--r--t/10state_putglyph.test6
-rw-r--r--t/13state_edit.test6
-rw-r--r--t/26state_query.test5
-rw-r--r--t/30state_pen.test11
-rw-r--r--t/60screen_ascii.test18
-rw-r--r--t/61screen_unicode.test6
-rw-r--r--t/62screen_damage.test2
-rw-r--r--t/63screen_resize.test34
-rw-r--r--t/64screen_pen.test6
-rw-r--r--t/65screen_protect.test8
-rw-r--r--t/69screen_reflow.test79
-rw-r--r--t/harness.c86
-rwxr-xr-xt/run-test.pl18
23 files changed, 643 insertions, 122 deletions
diff --git a/CONTRIBUTING b/CONTRIBUTING
index 2100d1e..e9a8f0c 100644
--- a/CONTRIBUTING
+++ b/CONTRIBUTING
@@ -6,8 +6,8 @@ The main resources for this library are:
Launchpad
https://launchpad.net/libvterm
- Freenode:
- ##tty or #tickit on irc.freenode.net
+ IRC:
+ ##tty or #tickit on irc.libera.chat
Email:
Paul "LeoNerd" Evans <leonerd@leonerd.org.uk>
diff --git a/Makefile b/Makefile
index e3c1c39..e3aa812 100644
--- a/Makefile
+++ b/Makefile
@@ -37,7 +37,7 @@ INCFILES=$(TBLFILES:.tbl=.inc)
HFILES_INT=$(sort $(wildcard src/*.h)) $(HFILES)
VERSION_MAJOR=0
-VERSION_MINOR=2
+VERSION_MINOR=3
VERSION_CURRENT=0
VERSION_REVISION=0
diff --git a/bin/vterm-ctrl.c b/bin/vterm-ctrl.c
index 8f8be7e..2bfa85b 100644
--- a/bin/vterm-ctrl.c
+++ b/bin/vterm-ctrl.c
@@ -125,7 +125,7 @@ static char *read_dcs()
bool in_esc = false;
int i = 0;
for(; i < sizeof(dcs)-1; ) {
- char c = getchar();
+ unsigned char c = getchar();
if(c == 0x9c) // ST
break;
if(in_esc && c == 0x5c)
diff --git a/doc/seqs.txt b/doc/seqs.txt
index 7f21367..16bfee2 100644
--- a/doc/seqs.txt
+++ b/doc/seqs.txt
@@ -132,6 +132,7 @@ x = xterm
1 or 2 = block
3 or 4 = underline
5 or 6 = I-beam to left
+ x CSI > q = XTVERSION, request version string
23x CSI " q = DECSCA, select character attributes
123x CSI r = DECSTBM
x CSI s = DECSLRM
@@ -197,6 +198,9 @@ x = xterm
x SGR 40-47 = Background ANSI
x SGR 48 = Background alternative palette
x SGR 49 = Background default
+ SGR 73 = Superscript on
+ SGR 74 = Subscript on
+ SGR 75 = Superscript/subscript off
x SGR 90-97 = Foreground ANSI high-intensity
x SGR 100-107 = Background ANSI high-intensity
diff --git a/include/vterm.h b/include/vterm.h
index 327f62c..257b677 100644
--- a/include/vterm.h
+++ b/include/vterm.h
@@ -12,11 +12,16 @@ extern "C" {
#include "vterm_keycodes.h"
#define VTERM_VERSION_MAJOR 0
-#define VTERM_VERSION_MINOR 2
+#define VTERM_VERSION_MINOR 3
#define VTERM_CHECK_VERSION \
vterm_check_version(VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR)
+/* 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
+
typedef struct VTerm VTerm;
typedef struct VTermState VTermState;
typedef struct VTermScreen VTermScreen;
@@ -237,6 +242,8 @@ typedef enum {
VTERM_ATTR_FONT, // number: 10-19
VTERM_ATTR_FOREGROUND, // color: 30-39 90-97
VTERM_ATTR_BACKGROUND, // color: 40-49 100-107
+ VTERM_ATTR_SMALL, // bool: 73, 74, 75
+ VTERM_ATTR_BASELINE, // number: 73, 74, 75
VTERM_N_ATTRS
} VTermAttr;
@@ -301,6 +308,7 @@ typedef struct {
*/
typedef struct {
VTermPos pos; /* current cursor position */
+ VTermLineInfo *lineinfos[2]; /* [1] may be NULL */
} VTermStateFields;
typedef struct {
@@ -312,8 +320,26 @@ typedef struct {
void vterm_check_version(int major, int minor);
+struct VTermBuilder {
+ int ver; /* currently unused but reserved for some sort of ABI version flag */
+
+ int rows, cols;
+
+ const VTermAllocatorFunctions *allocator;
+ void *allocdata;
+
+ /* Override default sizes for various structures */
+ size_t outbuffer_len; /* default: 4096 */
+ size_t tmpbuffer_len; /* default: 4096 */
+};
+
+VTerm *vterm_build(const struct VTermBuilder *builder);
+
+/* A convenient shortcut for default cases */
VTerm *vterm_new(int rows, int cols);
+/* This shortcuts are generally discouraged in favour of just using vterm_build() */
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
+
void vterm_free(VTerm* vt);
void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp);
@@ -403,6 +429,7 @@ typedef struct {
int (*bell)(void *user);
int (*resize)(int rows, int cols, VTermStateFields *fields, void *user);
int (*setlineinfo)(int row, const VTermLineInfo *newinfo, const VTermLineInfo *oldinfo, void *user);
+ int (*sb_clear)(void *user);
} VTermStateCallbacks;
typedef struct {
@@ -473,6 +500,8 @@ typedef struct {
unsigned int font : 4; /* 0 to 9 */
unsigned int dwl : 1; /* On a DECDWL or DECDHL line */
unsigned int dhl : 2; /* On a DECDHL line (1=top 2=bottom) */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
} VTermScreenCellAttrs;
enum {
@@ -482,8 +511,13 @@ enum {
VTERM_UNDERLINE_CURLY,
};
+enum {
+ VTERM_BASELINE_NORMAL,
+ VTERM_BASELINE_RAISE,
+ VTERM_BASELINE_LOWER,
+};
+
typedef struct {
-#define VTERM_MAX_CHARS_PER_CELL 6
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
char width;
VTermScreenCellAttrs attrs;
@@ -499,6 +533,7 @@ typedef struct {
int (*resize)(int rows, int cols, void *user);
int (*sb_pushline)(int cols, const VTermScreenCell *cells, void *user);
int (*sb_popline)(int cols, VTermScreenCell *cells, void *user);
+ int (*sb_clear)(void* user);
} VTermScreenCallbacks;
VTermScreen *vterm_obtain_screen(VTerm *vt);
@@ -509,6 +544,8 @@ void *vterm_screen_get_cbdata(VTermScreen *screen);
void vterm_screen_set_unrecognised_fallbacks(VTermScreen *screen, const VTermStateFallbacks *fallbacks, void *user);
void *vterm_screen_get_unrecognised_fbdata(VTermScreen *screen);
+void vterm_screen_set_reflow(VTermScreen *screen, bool reflow);
+
void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen);
typedef enum {
@@ -540,8 +577,10 @@ typedef enum {
VTERM_ATTR_FOREGROUND_MASK = 1 << 7,
VTERM_ATTR_BACKGROUND_MASK = 1 << 8,
VTERM_ATTR_CONCEAL_MASK = 1 << 9,
+ VTERM_ATTR_SMALL_MASK = 1 << 10,
+ VTERM_ATTR_BASELINE_MASK = 1 << 11,
- VTERM_ALL_ATTRS_MASK = (1 << 10) - 1
+ VTERM_ALL_ATTRS_MASK = (1 << 12) - 1
} VTermAttrMask;
int vterm_screen_get_attrs_extent(const VTermScreen *screen, VTermRect *extent, VTermPos pos, VTermAttrMask attrs);
diff --git a/src/pen.c b/src/pen.c
index 5279592..704e9d8 100644
--- a/src/pen.c
+++ b/src/pen.c
@@ -170,13 +170,15 @@ INTERNAL void vterm_state_newpen(VTermState *state)
INTERNAL void vterm_state_resetpen(VTermState *state)
{
state->pen.bold = 0; setpenattr_bool(state, VTERM_ATTR_BOLD, 0);
- state->pen.underline = 0; setpenattr_int( state, VTERM_ATTR_UNDERLINE, 0);
+ state->pen.underline = 0; setpenattr_int (state, VTERM_ATTR_UNDERLINE, 0);
state->pen.italic = 0; setpenattr_bool(state, VTERM_ATTR_ITALIC, 0);
state->pen.blink = 0; setpenattr_bool(state, VTERM_ATTR_BLINK, 0);
state->pen.reverse = 0; setpenattr_bool(state, VTERM_ATTR_REVERSE, 0);
state->pen.conceal = 0; setpenattr_bool(state, VTERM_ATTR_CONCEAL, 0);
state->pen.strike = 0; setpenattr_bool(state, VTERM_ATTR_STRIKE, 0);
- state->pen.font = 0; setpenattr_int( state, VTERM_ATTR_FONT, 0);
+ state->pen.font = 0; setpenattr_int (state, VTERM_ATTR_FONT, 0);
+ state->pen.small = 0; setpenattr_bool(state, VTERM_ATTR_SMALL, 0);
+ state->pen.baseline = 0; setpenattr_int (state, VTERM_ATTR_BASELINE, 0);
state->pen.fg = state->default_fg; setpenattr_col(state, VTERM_ATTR_FOREGROUND, state->default_fg);
state->pen.bg = state->default_bg; setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->default_bg);
@@ -190,14 +192,17 @@ INTERNAL void vterm_state_savepen(VTermState *state, int save)
else {
state->pen = state->saved.pen;
- setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
- setpenattr_int( state, VTERM_ATTR_UNDERLINE, state->pen.underline);
- setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
- setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
- setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
- setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal);
- setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
- setpenattr_int( state, VTERM_ATTR_FONT, state->pen.font);
+ setpenattr_bool(state, VTERM_ATTR_BOLD, state->pen.bold);
+ setpenattr_int (state, VTERM_ATTR_UNDERLINE, state->pen.underline);
+ setpenattr_bool(state, VTERM_ATTR_ITALIC, state->pen.italic);
+ setpenattr_bool(state, VTERM_ATTR_BLINK, state->pen.blink);
+ setpenattr_bool(state, VTERM_ATTR_REVERSE, state->pen.reverse);
+ setpenattr_bool(state, VTERM_ATTR_CONCEAL, state->pen.conceal);
+ setpenattr_bool(state, VTERM_ATTR_STRIKE, state->pen.strike);
+ setpenattr_int (state, VTERM_ATTR_FONT, state->pen.font);
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+
setpenattr_col( state, VTERM_ATTR_FOREGROUND, state->pen.fg);
setpenattr_col( state, VTERM_ATTR_BACKGROUND, state->pen.bg);
}
@@ -425,6 +430,18 @@ INTERNAL void vterm_state_setpen(VTermState *state, const long args[], int argco
setpenattr_col(state, VTERM_ATTR_BACKGROUND, state->pen.bg);
break;
+ case 73: // Superscript
+ case 74: // Subscript
+ case 75: // Superscript/subscript off
+ state->pen.small = (arg != 75);
+ state->pen.baseline =
+ (arg == 73) ? VTERM_BASELINE_RAISE :
+ (arg == 74) ? VTERM_BASELINE_LOWER :
+ VTERM_BASELINE_NORMAL;
+ setpenattr_bool(state, VTERM_ATTR_SMALL, state->pen.small);
+ setpenattr_int (state, VTERM_ATTR_BASELINE, state->pen.baseline);
+ break;
+
case 90: case 91: case 92: case 93:
case 94: case 95: case 96: case 97: // Foreground colour high-intensity palette
value = CSI_ARG(args[argi]) - 90 + 8;
@@ -519,6 +536,13 @@ INTERNAL int vterm_state_getpen(VTermState *state, long args[], int argcount)
argi = vterm_state_getpen_color(&state->pen.bg, argi, args, false);
+ if(state->pen.small) {
+ if(state->pen.baseline == VTERM_BASELINE_RAISE)
+ args[argi++] = 73;
+ else if(state->pen.baseline == VTERM_BASELINE_LOWER)
+ args[argi++] = 74;
+ }
+
return argi;
}
@@ -565,6 +589,14 @@ int vterm_state_get_penattr(const VTermState *state, VTermAttr attr, VTermValue
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_N_ATTRS:
return 0;
}
diff --git a/src/screen.c b/src/screen.c
index 38b9b52..a86b7b8 100644
--- a/src/screen.c
+++ b/src/screen.c
@@ -9,6 +9,8 @@
#define UNICODE_SPACE 0x20
#define UNICODE_LINEFEED 0x0a
+#undef DEBUG_REFLOW
+
/* State of the pen at some moment in time, also used in a cell */
typedef struct
{
@@ -23,6 +25,8 @@ typedef struct
unsigned int conceal : 1;
unsigned int strike : 1;
unsigned int font : 4; /* 0 to 9 */
+ unsigned int small : 1;
+ unsigned int baseline : 2;
/* Extra state storage that isn't strictly pen-related */
unsigned int protected_cell : 1;
@@ -53,7 +57,9 @@ struct VTermScreen
int rows;
int cols;
- int global_reverse;
+
+ unsigned int global_reverse : 1;
+ unsigned int reflow : 1;
/* Primary and Altscreen. buffers[1] is lazily allocated as needed */
ScreenCell *buffers[2];
@@ -428,6 +434,12 @@ static int setpenattr(VTermAttr attr, VTermValue *val, void *user)
case VTERM_ATTR_BACKGROUND:
screen->pen.bg = val->color;
return 1;
+ case VTERM_ATTR_SMALL:
+ screen->pen.small = val->boolean;
+ return 1;
+ case VTERM_ATTR_BASELINE:
+ screen->pen.baseline = val->number;
+ return 1;
case VTERM_N_ATTRS:
return 0;
@@ -476,35 +488,170 @@ static int bell(void *user)
return 0;
}
+/* How many cells are non-blank
+ * Returns the position of the first blank cell in the trailing blank end */
+static int line_popcount(ScreenCell *buffer, int row, int rows, int cols)
+{
+ int col = cols - 1;
+ while(col >= 0 && buffer[row * cols + col].chars[0] == 0)
+ col--;
+ return col + 1;
+}
+
+#define REFLOW (screen->reflow)
+
static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new_cols, bool active, VTermStateFields *statefields)
{
int old_rows = screen->rows;
int old_cols = screen->cols;
ScreenCell *old_buffer = screen->buffers[bufidx];
+ VTermLineInfo *old_lineinfo = statefields->lineinfos[bufidx];
+
ScreenCell *new_buffer = vterm_allocator_malloc(screen->vt, sizeof(ScreenCell) * new_rows * new_cols);
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
int old_row = old_rows - 1;
int new_row = new_rows - 1;
- while(new_row >= 0 && old_row >= 0) {
- int col;
- for(col = 0; col < old_cols && col < new_cols; col++)
- new_buffer[new_row * new_cols + col] = old_buffer[old_row * old_cols + col];
- for( ; col < new_cols; col++)
- clearcell(screen, &new_buffer[new_row * new_cols + col]);
+ VTermPos old_cursor = statefields->pos;
+ VTermPos new_cursor = { -1, -1 };
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, "Resizing from %dx%d to %dx%d; cursor was at (%d,%d)\n",
+ old_cols, old_rows, new_cols, new_rows, old_cursor.col, old_cursor.row);
+#endif
+
+ /* Keep track of the final row that is knonw to be blank, so we know what
+ * spare space we have for scrolling into
+ */
+ int final_blank_row = new_rows;
+
+ while(old_row >= 0) {
+ int old_row_end = old_row;
+ /* TODO: Stop if dwl or dhl */
+ while(REFLOW && old_lineinfo && old_row >= 0 && old_lineinfo[old_row].continuation)
+ old_row--;
+ int old_row_start = old_row;
+
+ int width = 0;
+ for(int row = old_row_start; row <= old_row_end; row++) {
+ if(REFLOW && row < (old_rows - 1) && old_lineinfo[row + 1].continuation)
+ width += old_cols;
+ else
+ width += line_popcount(old_buffer, row, old_rows, old_cols);
+ }
+
+ if(final_blank_row == (new_row + 1) && width == 0)
+ final_blank_row = new_row;
+
+ int new_height = REFLOW
+ ? width ? (width + new_cols - 1) / new_cols : 1
+ : 1;
+
+ int new_row_end = new_row;
+ int new_row_start = new_row - new_height + 1;
+
+ old_row = old_row_start;
+ int old_col = 0;
+
+ int spare_rows = new_rows - final_blank_row;
+
+ if(new_row_start < 0 && /* we'd fall off the top */
+ spare_rows >= 0 && /* we actually have spare rows */
+ (!active || new_cursor.row == -1 || (new_cursor.row - new_row_start) < new_rows))
+ {
+ /* Attempt to scroll content down into the blank rows at the bottom to
+ * make it fit
+ */
+ int downwards = -new_row_start;
+ if(downwards > spare_rows)
+ downwards = spare_rows;
+ int rowcount = new_rows - downwards;
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " scroll %d rows +%d downwards\n", rowcount, downwards);
+#endif
+
+ memmove(&new_buffer[downwards * new_cols], &new_buffer[0], rowcount * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[downwards], &new_lineinfo[0], rowcount * sizeof(new_lineinfo[0]));
+
+ new_row += downwards;
+ new_row_start += downwards;
+ new_row_end += downwards;
+
+ if(new_cursor.row >= 0)
+ new_cursor.row += downwards;
+
+ final_blank_row += downwards;
+ }
+
+#ifdef DEBUG_REFLOW
+ fprintf(stderr, " rows [%d..%d] <- [%d..%d] width=%d\n",
+ new_row_start, new_row_end, old_row_start, old_row_end, width);
+#endif
+
+ if(new_row_start < 0)
+ break;
+
+ for(new_row = new_row_start, old_row = old_row_start; new_row <= new_row_end; new_row++) {
+ int count = width >= new_cols ? new_cols : width;
+ width -= count;
+
+ int new_col = 0;
+
+ while(count) {
+ /* TODO: This could surely be done a lot faster by memcpy()'ing the entire range */
+ new_buffer[new_row * new_cols + new_col] = old_buffer[old_row * old_cols + old_col];
+
+ if(old_cursor.row == old_row && old_cursor.col == old_col)
+ new_cursor.row = new_row, new_cursor.col = new_col;
+
+ old_col++;
+ if(old_col == old_cols) {
+ old_row++;
+
+ if(!REFLOW) {
+ new_col++;
+ break;
+ }
+ old_col = 0;
+ }
- old_row--;
- new_row--;
+ new_col++;
+ count--;
+ }
- if(new_row < 0 && old_row >= 0 &&
- new_buffer[(new_rows - 1) * new_cols].chars[0] == 0 &&
- (!active || statefields->pos.row < (new_rows - 1))) {
- int moverows = new_rows - 1;
- memmove(&new_buffer[1 * new_cols], &new_buffer[0], moverows * new_cols * sizeof(ScreenCell));
+ if(old_cursor.row == old_row && old_cursor.col >= old_col) {
+ new_cursor.row = new_row, new_cursor.col = (old_cursor.col - old_col + new_col);
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
- new_row++;
+ while(new_col < new_cols) {
+ clearcell(screen, &new_buffer[new_row * new_cols + new_col]);
+ new_col++;
+ }
+
+ new_lineinfo[new_row].continuation = (new_row > new_row_start);
}
+
+ old_row = old_row_start - 1;
+ new_row = new_row_start - 1;
+ }
+
+ if(old_cursor.row <= old_row) {
+ /* cursor would have moved entirely off the top of the screen; lets just
+ * bring it within range */
+ new_cursor.row = 0, new_cursor.col = old_cursor.col;
+ if(new_cursor.col >= new_cols)
+ new_cursor.col = new_cols-1;
+ }
+
+ /* We really expect the cursor position to be set by now */
+ if(active && (new_cursor.row == -1 || new_cursor.col == -1)) {
+ fprintf(stderr, "screen_resize failed to update cursor position\n");
+ abort();
}
if(old_row >= 0 && bufidx == BUFIDX_PRIMARY) {
@@ -540,6 +687,8 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
dst->pen.conceal = src->attrs.conceal;
dst->pen.strike = src->attrs.strike;
dst->pen.font = src->attrs.font;
+ dst->pen.small = src->attrs.small;
+ dst->pen.baseline = src->attrs.baseline;
dst->pen.fg = src->fg;
dst->pen.bg = src->bg;
@@ -559,20 +708,27 @@ static void resize_buffer(VTermScreen *screen, int bufidx, int new_rows, int new
/* Scroll new rows back up to the top and fill in blanks at the bottom */
int moverows = new_rows - new_row - 1;
memmove(&new_buffer[0], &new_buffer[(new_row + 1) * new_cols], moverows * new_cols * sizeof(ScreenCell));
+ memmove(&new_lineinfo[0], &new_lineinfo[new_row + 1], moverows * sizeof(new_lineinfo[0]));
- for(new_row = moverows; new_row < new_rows; new_row++)
+ new_cursor.row -= (new_row + 1);
+
+ for(new_row = moverows; new_row < new_rows; new_row++) {
for(int col = 0; col < new_cols; col++)
clearcell(screen, &new_buffer[new_row * new_cols + col]);
+ new_lineinfo[new_row] = (VTermLineInfo){ 0 };
+ }
}
vterm_allocator_free(screen->vt, old_buffer);
screen->buffers[bufidx] = new_buffer;
- return;
+ vterm_allocator_free(screen->vt, old_lineinfo);
+ statefields->lineinfos[bufidx] = new_lineinfo;
- /* REFLOW TODO:
- * Handle delta. Probably needs to be a full cursorpos that we edit
- */
+ if(active)
+ statefields->pos = new_cursor;
+
+ return;
}
static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *user)
@@ -581,6 +737,7 @@ static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *us
int altscreen_active = (screen->buffers[BUFIDX_ALTSCREEN] && screen->buffer == screen->buffers[BUFIDX_ALTSCREEN]);
+ int old_rows = screen->rows;
int old_cols = screen->cols;
if(new_cols > old_cols) {
@@ -594,6 +751,17 @@ static int resize(int new_rows, int new_cols, VTermStateFields *fields, void *us
resize_buffer(screen, 0, new_rows, new_cols, !altscreen_active, fields);
if(screen->buffers[BUFIDX_ALTSCREEN])
resize_buffer(screen, 1, new_rows, new_cols, altscreen_active, fields);
+ else if(new_rows != old_rows) {
+ /* We don't need a full resize of the altscreen because it isn't enabled
+ * but we should at least keep the lineinfo the right size */
+ vterm_allocator_free(screen->vt, fields->lineinfos[BUFIDX_ALTSCREEN]);
+
+ VTermLineInfo *new_lineinfo = vterm_allocator_malloc(screen->vt, sizeof(new_lineinfo[0]) * new_rows);
+ for(int row = 0; row < new_rows; row++)
+ new_lineinfo[row] = (VTermLineInfo){ 0 };
+
+ fields->lineinfos[BUFIDX_ALTSCREEN] = new_lineinfo;
+ }
screen->buffer = altscreen_active ? screen->buffers[BUFIDX_ALTSCREEN] : screen->buffers[BUFIDX_PRIMARY];
@@ -647,6 +815,16 @@ static int setlineinfo(int row, const VTermLineInfo *newinfo, const VTermLineInf
return 1;
}
+static int sb_clear(void *user) {
+ VTermScreen *screen = user;
+
+ if(screen->callbacks && screen->callbacks->sb_clear)
+ if((*screen->callbacks->sb_clear)(screen->cbdata))
+ return 1;
+
+ return 0;
+}
+
static VTermStateCallbacks state_cbs = {
.putglyph = &putglyph,
.movecursor = &movecursor,
@@ -657,6 +835,7 @@ static VTermStateCallbacks state_cbs = {
.bell = &bell,
.resize = &resize,
.setlineinfo = &setlineinfo,
+ .sb_clear = &sb_clear,
};
static VTermScreen *screen_new(VTerm *vt)
@@ -680,6 +859,9 @@ static VTermScreen *screen_new(VTerm *vt)
screen->rows = rows;
screen->cols = cols;
+ screen->global_reverse = false;
+ screen->reflow = false;
+
screen->callbacks = NULL;
screen->cbdata = NULL;
@@ -794,6 +976,8 @@ int vterm_screen_get_cell(const VTermScreen *screen, VTermPos pos, VTermScreenCe
cell->attrs.conceal = intcell->pen.conceal;
cell->attrs.strike = intcell->pen.strike;
cell->attrs.font = intcell->pen.font;
+ cell->attrs.small = intcell->pen.small;
+ cell->attrs.baseline = intcell->pen.baseline;
cell->attrs.dwl = intcell->pen.dwl;
cell->attrs.dhl = intcell->pen.dhl;
@@ -833,6 +1017,11 @@ VTermScreen *vterm_obtain_screen(VTerm *vt)
return screen;
}
+void vterm_screen_set_reflow(VTermScreen *screen, bool reflow)
+{
+ screen->reflow = reflow;
+}
+
void vterm_screen_enable_altscreen(VTermScreen *screen, int altscreen)
{
if(!screen->buffers[BUFIDX_ALTSCREEN] && altscreen) {
@@ -909,6 +1098,10 @@ static int attrs_differ(VTermAttrMask attrs, ScreenCell *a, ScreenCell *b)
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;
return 0;
}
diff --git a/src/state.c b/src/state.c
index d5f3a30..302ec0f 100644
--- a/src/state.c
+++ b/src/state.c
@@ -275,8 +275,9 @@ static int on_text(const char bytes[], size_t len, void *user)
VTermPos oldpos = state->pos;
- // We'll have at most len codepoints
- uint32_t codepoints[len];
+ uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
+ size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);
+
int npoints = 0;
size_t eaten = 0;
@@ -287,7 +288,7 @@ static int on_text(const char bytes[], size_t len, void *user)
&state->encoding[state->gr_set];
(*encoding->enc->decode)(encoding->enc, encoding->data,
- codepoints, &npoints, state->gsingle_set ? 1 : len,
+ codepoints, &npoints, state->gsingle_set ? 1 : maxpoints,
bytes, &eaten, len);
/* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
@@ -347,13 +348,15 @@ static int on_text(const char bytes[], size_t len, void *user)
// Try to find combining characters following this
int glyph_starts = i;
int glyph_ends;
- for(glyph_ends = i + 1; glyph_ends < npoints; glyph_ends++)
+ for(glyph_ends = i + 1;
+ (glyph_ends < npoints) && (glyph_ends < glyph_starts + VTERM_MAX_CHARS_PER_CELL);
+ glyph_ends++)
if(!vterm_unicode_is_combining(codepoints[glyph_ends]))
break;
int width = 0;
- uint32_t chars[glyph_ends - glyph_starts + 1];
+ uint32_t chars[VTERM_MAX_CHARS_PER_CELL + 1];
for( ; i < glyph_ends; i++) {
chars[i - glyph_starts] = codepoints[i];
@@ -367,6 +370,9 @@ static int on_text(const char bytes[], size_t len, void *user)
width += this_width;
}
+ while(i < npoints && vterm_unicode_is_combining(codepoints[i]))
+ i++;
+
chars[glyph_ends - glyph_starts] = 0;
i--;
@@ -910,6 +916,12 @@ static void request_dec_mode(VTermState *state, int num)
vterm_push_output_sprintf_ctrl(state->vt, C1_CSI, "?%d;%d$y", num, reply ? 1 : 2);
}
+static void request_version_string(VTermState *state)
+{
+ vterm_push_output_sprintf_str(state->vt, C1_DCS, true, ">|libvterm(%d.%d)",
+ VTERM_VERSION_MAJOR, VTERM_VERSION_MINOR);
+}
+
static int on_csi(const char *leader, const long args[], int argcount, const char *intermed, char command, void *user)
{
VTermState *state = user;
@@ -1082,6 +1094,12 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha
set_lineinfo(state, row, FORCE, DWL_OFF, DHL_OFF);
erase(state, rect, selective);
break;
+
+ case 3:
+ if(state->callbacks && state->callbacks->sb_clear)
+ if((*state->callbacks->sb_clear)(state->cbdata))
+ return 1;
+ break;
}
break;
@@ -1353,6 +1371,10 @@ static int on_csi(const char *leader, const long args[], int argcount, const cha
request_dec_mode(state, CSI_ARG(args[0]));
break;
+ case LEADER('>', 0x71): // XTVERSION - xterm query version string
+ request_version_string(state);
+ break;
+
case INTERMED(' ', 0x71): // DECSCUSR - DEC set cursor shape
val = CSI_ARG_OR(args[0], 1);
@@ -1902,32 +1924,6 @@ static int on_resize(int rows, int cols, void *user)
state->tabstops = newtabstops;
}
- if(rows != state->rows) {
- for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
- VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
- if(!oldlineinfo)
- continue;
-
- VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
-
- int row;
- for(row = 0; row < state->rows && row < rows; row++) {
- newlineinfo[row] = oldlineinfo[row];
- }
-
- for( ; row < rows; row++) {
- newlineinfo[row] = (VTermLineInfo){
- .doublewidth = 0,
- };
- }
-
- vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
- state->lineinfos[bufidx] = newlineinfo;
- }
-
- state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
- }
-
state->rows = rows;
state->cols = cols;
@@ -1937,13 +1933,44 @@ static int on_resize(int rows, int cols, void *user)
UBOUND(state->scrollregion_right, state->cols);
VTermStateFields fields = {
- .pos = state->pos,
+ .pos = state->pos,
+ .lineinfos = { [0] = state->lineinfos[0], [1] = state->lineinfos[1] },
};
- if(state->callbacks && state->callbacks->resize)
+ if(state->callbacks && state->callbacks->resize) {
(*state->callbacks->resize)(rows, cols, &fields, state->cbdata);
+ state->pos = fields.pos;
+
+ state->lineinfos[0] = fields.lineinfos[0];
+ state->lineinfos[1] = fields.lineinfos[1];
+ }
+ else {
+ if(rows != state->rows) {
+ for(int bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
+ VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
+ if(!oldlineinfo)
+ continue;
+
+ VTermLineInfo *newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
+
+ int row;
+ for(row = 0; row < state->rows && row < rows; row++) {
+ newlineinfo[row] = oldlineinfo[row];
+ }
+
+ for( ; row < rows; row++) {
+ newlineinfo[row] = (VTermLineInfo){
+ .doublewidth = 0,
+ };
+ }
+
+ vterm_allocator_free(state->vt, state->lineinfos[bufidx]);
+ state->lineinfos[bufidx] = newlineinfo;
+ }
+ }
+ }
- state->pos = fields.pos;
+ state->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
if(state->at_phantom && state->pos.col < cols-1) {
state->at_phantom = 0;
diff --git a/src/vterm.c b/src/vterm.c
index bbc55ae..b2f61d2 100644
--- a/src/vterm.c
+++ b/src/vterm.c
@@ -29,19 +29,37 @@ static VTermAllocatorFunctions default_allocator = {
VTerm *vterm_new(int rows, int cols)
{
- return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
+ return vterm_build(&(const struct VTermBuilder){
+ .rows = rows,
+ .cols = cols,
+ });
}
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
{
+ return vterm_build(&(const struct VTermBuilder){
+ .rows = rows,
+ .cols = cols,
+ .allocator = funcs,
+ .allocdata = allocdata,
+ });
+}
+
+/* A handy macro for defaulting values out of builder fields */
+#define DEFAULT(v, def) ((v) ? (v) : (def))
+
+VTerm *vterm_build(const struct VTermBuilder *builder)
+{
+ const VTermAllocatorFunctions *allocator = DEFAULT(builder->allocator, &default_allocator);
+
/* Need to bootstrap using the allocator function directly */
- VTerm *vt = (*funcs->malloc)(sizeof(VTerm), allocdata);
+ VTerm *vt = (*allocator->malloc)(sizeof(VTerm), builder->allocdata);
- vt->allocator = funcs;
- vt->allocdata = allocdata;
+ vt->allocator = allocator;
+ vt->allocdata = builder->allocdata;
- vt->rows = rows;
- vt->cols = cols;
+ vt->rows = builder->rows;
+ vt->cols = builder->cols;
vt->parser.state = NORMAL;
@@ -51,11 +69,11 @@ VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *fun
vt->outfunc = NULL;
vt->outdata = NULL;
- vt->outbuffer_len = 64;
+ vt->outbuffer_len = DEFAULT(builder->outbuffer_len, 4096);
vt->outbuffer_cur = 0;
vt->outbuffer = vterm_allocator_malloc(vt, vt->outbuffer_len);
- vt->tmpbuffer_len = 64;
+ vt->tmpbuffer_len = DEFAULT(builder->tmpbuffer_len, 4096);
vt->tmpbuffer = vterm_allocator_malloc(vt, vt->tmpbuffer_len);
return vt;
@@ -95,6 +113,9 @@ void vterm_get_size(const VTerm *vt, int *rowsp, int *colsp)
void vterm_set_size(VTerm *vt, int rows, int cols)
{
+ if(rows < 1 || cols < 1)
+ return;
+
vt->rows = rows;
vt->cols = cols;
@@ -253,6 +274,8 @@ VTermValueType vterm_get_attr_type(VTermAttr attr)
case VTERM_ATTR_FONT: return VTERM_VALUETYPE_INT;
case VTERM_ATTR_FOREGROUND: return VTERM_VALUETYPE_COLOR;
case VTERM_ATTR_BACKGROUND: return VTERM_VALUETYPE_COLOR;
+ case VTERM_ATTR_SMALL: return VTERM_VALUETYPE_BOOL;
+ case VTERM_ATTR_BASELINE: return VTERM_VALUETYPE_INT;
case VTERM_N_ATTRS: return 0;
}
diff --git a/src/vterm_internal.h b/src/vterm_internal.h
index 2d77107..6aa9007 100644
--- a/src/vterm_internal.h
+++ b/src/vterm_internal.h
@@ -48,6 +48,8 @@ struct VTermPen
unsigned int conceal:1;
unsigned int strike:1;
unsigned int font:4; /* To store 0-9 */
+ unsigned int small:1;
+ unsigned int baseline:2;
};
struct VTermState
@@ -172,7 +174,7 @@ struct VTermState
struct VTerm
{
- VTermAllocatorFunctions *allocator;
+ const VTermAllocatorFunctions *allocator;
void *allocdata;
int rows;
diff --git a/t/10state_putglyph.test b/t/10state_putglyph.test
index bae0423..c82c525 100644
--- a/t/10state_putglyph.test
+++ b/t/10state_putglyph.test
@@ -52,6 +52,12 @@ PUSH "\xCC\x81Z"
putglyph 0x65,0x301 1 0,0
putglyph 0x5a 1 0,1
+!Spare combining chars get truncated
+RESET
+PUSH "e" . "\xCC\x81" x 10
+ putglyph 0x65,0x301,0x301,0x301,0x301,0x301 1 0,0
+ # and nothing more
+
RESET
PUSH "e"
putglyph 0x65 1 0,0
diff --git a/t/13state_edit.test b/t/13state_edit.test
index b435655..d3f3e9e 100644
--- a/t/13state_edit.test
+++ b/t/13state_edit.test
@@ -1,6 +1,6 @@
INIT
UTF8 1
-WANTSTATE se
+WANTSTATE seb
!ICH
RESET
@@ -242,6 +242,10 @@ PUSH "\e[2J"
erase 0..25,0..80
?cursor = 1,1
+!ED 3
+PUSH "\e[3J"
+ sb_clear
+
!SED
RESET
erase 0..25,0..80
diff --git a/t/26state_query.test b/t/26state_query.test
index 7c85042..5b97c40 100644
--- a/t/26state_query.test
+++ b/t/26state_query.test
@@ -6,6 +6,11 @@ RESET
PUSH "\e[c"
output "\e[?1;2c"
+!XTVERSION
+RESET
+PUSH "\e[>q"
+ output "\eP>|libvterm(0.3)\e\\"
+
!DSR
RESET
PUSH "\e[5n"
diff --git a/t/30state_pen.test b/t/30state_pen.test
index 915baec..92cf01d 100644
--- a/t/30state_pen.test
+++ b/t/30state_pen.test
@@ -112,3 +112,14 @@ PUSH "\e[m\e[1;37m"
PUSH "\e[m\e[37;1m"
?pen bold = on
?pen foreground = idx(15)
+
+!Super/Subscript
+PUSH "\e[73m"
+ ?pen small = on
+ ?pen baseline = raise
+PUSH "\e[74m"
+ ?pen small = on
+ ?pen baseline = lower
+PUSH "\e[75m"
+ ?pen small = off
+ ?pen baseline = normal
diff --git a/t/60screen_ascii.test b/t/60screen_ascii.test
index e679b98..57729c5 100644
--- a/t/60screen_ascii.test
+++ b/t/60screen_ascii.test
@@ -18,11 +18,11 @@ PUSH "ABC"
?screen_eol 0,3 = 1
PUSH "\e[H"
movecursor 0,0
- ?screen_chars 0,0,1,80 = "ABC"
+ ?screen_row 0 = "ABC"
?screen_text 0,0,1,80 = 0x41,0x42,0x43
PUSH "E"
movecursor 0,1
- ?screen_chars 0,0,1,80 = "EBC"
+ ?screen_row 0 = "EBC"
?screen_text 0,0,1,80 = 0x45,0x42,0x43
WANTSCREEN -c
@@ -30,14 +30,14 @@ WANTSCREEN -c
!Erase
RESET
PUSH "ABCDE\e[H\e[K"
- ?screen_chars 0,0,1,80 =
+ ?screen_row 0 = ""
?screen_text 0,0,1,80 =
!Copycell
RESET
PUSH "ABC\e[H\e[@"
PUSH "1"
- ?screen_chars 0,0,1,80 = "1ABC"
+ ?screen_row 0 = "1ABC"
RESET
PUSH "ABC\e[H\e[P"
@@ -48,7 +48,7 @@ PUSH "ABC\e[H\e[P"
!Space padding
RESET
PUSH "Hello\e[CWorld"
- ?screen_chars 0,0,1,80 = "Hello World"
+ ?screen_row 0 = "Hello World"
?screen_text 0,0,1,80 = 0x48,0x65,0x6c,0x6c,0x6f,0x20,0x57,0x6f,0x72,0x6c,0x64
!Linefeed padding
@@ -60,10 +60,10 @@ PUSH "Hello\r\nWorld"
!Altscreen
RESET
PUSH "P"
- ?screen_chars 0,0,1,80 = "P"
+ ?screen_row 0 = "P"
PUSH "\e[?1049h"
- ?screen_chars 0,0,1,80 =
+ ?screen_row 0 = ""
PUSH "\e[2K\e[HA"
- ?screen_chars 0,0,1,80 = "A"
+ ?screen_row 0 = "A"
PUSH "\e[?1049l"
- ?screen_chars 0,0,1,80 = "P"
+ ?screen_row 0 = "P"
diff --git a/t/61screen_unicode.test b/t/61screen_unicode.test
index 79dcb68..68b3381 100644
--- a/t/61screen_unicode.test
+++ b/t/61screen_unicode.test
@@ -7,7 +7,7 @@ WANTSCREEN
# U+00E9 = 0xC3 0xA9 name: LATIN SMALL LETTER E WITH ACUTE
RESET
PUSH "\xC3\x81\xC3\xA9"
- ?screen_chars 0,0,1,80 = 0xc1,0xe9
+ ?screen_row 0 = 0xc1,0xe9
?screen_text 0,0,1,80 = 0xc3,0x81,0xc3,0xa9
?screen_cell 0,0 = {0xc1} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
@@ -16,7 +16,7 @@ PUSH "\xC3\x81\xC3\xA9"
RESET
PUSH "0123\e[H"
PUSH "\xEF\xBC\x90"
- ?screen_chars 0,0,1,80 = 0xff10,0x32,0x33
+ ?screen_row 0 = 0xff10,0x32,0x33
?screen_text 0,0,1,80 = 0xef,0xbc,0x90,0x32,0x33
?screen_cell 0,0 = {0xff10} width=2 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
@@ -25,7 +25,7 @@ PUSH "\xEF\xBC\x90"
RESET
PUSH "0123\e[H"
PUSH "e\xCC\x81"
- ?screen_chars 0,0,1,80 = 0x65,0x301,0x31,0x32,0x33
+ ?screen_row 0 = 0x65,0x301,0x31,0x32,0x33
?screen_text 0,0,1,80 = 0x65,0xcc,0x81,0x31,0x32,0x33
?screen_cell 0,0 = {0x65,0x301} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
diff --git a/t/62screen_damage.test b/t/62screen_damage.test
index 32cac2d..3b1b238 100644
--- a/t/62screen_damage.test
+++ b/t/62screen_damage.test
@@ -152,4 +152,4 @@ PUSH "\e[25H\r\nABCDE\b\b\b\e[2P\r\n"
DAMAGEFLUSH
moverect 1..25,0..80 -> 0..24,0..80
damage 24..25,0..80
- ?screen_chars 23,0,24,5 = "ABE"
+ ?screen_row 23 = "ABE"
diff --git a/t/63screen_resize.test b/t/63screen_resize.test
index 87b88d6..6835222 100644
--- a/t/63screen_resize.test
+++ b/t/63screen_resize.test
@@ -28,20 +28,20 @@ PUSH "E"
RESET
RESIZE 25,80
PUSH "Top\e[10HLine 10"
- ?screen_chars 0,0,1,80 = "Top"
- ?screen_chars 9,0,10,80 = "Line 10"
+ ?screen_row 0 = "Top"
+ ?screen_row 9 = "Line 10"
?cursor = 9,7
RESIZE 20,80
- ?screen_chars 0,0,1,80 = "Top"
- ?screen_chars 9,0,10,80 = "Line 10"
+ ?screen_row 0 = "Top"
+ ?screen_row 9 = "Line 10"
?cursor = 9,7
!Resize shorter with content must scroll
RESET
RESIZE 25,80
PUSH "Top\e[25HLine 25\e[15H"
- ?screen_chars 0,0,1,80 = "Top"
- ?screen_chars 24,0,25,80 = "Line 25"
+ ?screen_row 0 = "Top"
+ ?screen_row 24 = "Line 25"
?cursor = 14,0
WANTSCREEN b
RESIZE 20,80
@@ -50,8 +50,8 @@ RESIZE 20,80
sb_pushline 80 =
sb_pushline 80 =
sb_pushline 80 =
- ?screen_chars 0,0,1,80 =
- ?screen_chars 19,0,20,80 = "Line 25"
+ ?screen_row 0 = ""
+ ?screen_row 19 = "Line 25"
?cursor = 9,0
!Resize shorter does not lose line with cursor
@@ -62,11 +62,11 @@ RESIZE 25,80
WANTSCREEN b
PUSH "\e[24HLine 24\r\nLine 25\r\n"
sb_pushline 80 =
- ?screen_chars 23,0,24,10 = "Line 25"
+ ?screen_row 23 = "Line 25"
?cursor = 24,0
RESIZE 24,80
sb_pushline 80 =
- ?screen_chars 22,0,23,10 = "Line 25"
+ ?screen_row 22 = "Line 25"
?cursor = 23,0
!Resize shorter does not send the cursor to a negative row
@@ -90,8 +90,8 @@ RESET
WANTSCREEN -b
RESIZE 25,80
PUSH "Line 1\e[25HBottom\e[15H"
- ?screen_chars 0,0,1,80 = "Line 1"
- ?screen_chars 24,0,25,80 = "Bottom"
+ ?screen_row 0 = "Line 1"
+ ?screen_row 24 = "Bottom"
?cursor = 14,0
WANTSCREEN b
RESIZE 30,80
@@ -100,9 +100,9 @@ RESIZE 30,80
sb_popline 80
sb_popline 80
sb_popline 80
- ?screen_chars 0,0,1,80 = "ABCDE"
- ?screen_chars 5,0,6,80 = "Line 1"
- ?screen_chars 29,0,30,80 = "Bottom"
+ ?screen_row 0 = "ABCDE"
+ ?screen_row 5 = "Line 1"
+ ?screen_row 29 = "Bottom"
?cursor = 19,0
WANTSCREEN -b
@@ -112,6 +112,6 @@ WANTSCREEN a
RESIZE 25,80
PUSH "Main screen\e[?1049h\e[HAlt screen"
RESIZE 30,80
- ?screen_chars 0,0,1,3 = "Alt"
+ ?screen_row 0 = "Alt screen"
PUSH "\e[?1049l"
- ?screen_chars 0,0,1,3 = "Mai"
+ ?screen_row 0 = "Main screen"
diff --git a/t/64screen_pen.test b/t/64screen_pen.test
index f1ee639..b21593b 100644
--- a/t/64screen_pen.test
+++ b/t/64screen_pen.test
@@ -35,6 +35,12 @@ PUSH "\e[31mG\e[m"
PUSH "\e[42mH\e[m"
?screen_cell 0,7 = {0x48} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,224,0)
+!Super/subscript
+PUSH "x\e[74m0\e[73m2\e[m"
+ ?screen_cell 0,8 = {0x78} width=1 attrs={} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,9 = {0x30} width=1 attrs={S_} fg=rgb(240,240,240) bg=rgb(0,0,0)
+ ?screen_cell 0,10 = {0x32} width=1 attrs={S^} fg=rgb(240,240,240) bg=rgb(0,0,0)
+
!EL sets reverse and colours to end of line
PUSH "\e[H\e[7;33;44m\e[K"
?screen_cell 0,0 = {} width=1 attrs={R} fg=rgb(224,224,0) bg=rgb(0,0,224)
diff --git a/t/65screen_protect.test b/t/65screen_protect.test
index 718f853..ec412a5 100644
--- a/t/65screen_protect.test
+++ b/t/65screen_protect.test
@@ -4,13 +4,13 @@ WANTSCREEN
!Selective erase
RESET
PUSH "A\e[1\"qB\e[\"qC"
- ?screen_chars 0,0,1,3 = 0x41,0x42,0x43
+ ?screen_row 0 = "ABC"
PUSH "\e[G\e[?J"
- ?screen_chars 0,0,1,3 = 0x20,0x42
+ ?screen_row 0 = " B"
!Non-selective erase
RESET
PUSH "A\e[1\"qB\e[\"qC"
- ?screen_chars 0,0,1,3 = 0x41,0x42,0x43
+ ?screen_row 0 = "ABC"
PUSH "\e[G\e[J"
- ?screen_chars 0,0,1,3 =
+ ?screen_row 0 = ""
diff --git a/t/69screen_reflow.test b/t/69screen_reflow.test
new file mode 100644
index 0000000..278cc5b
--- /dev/null
+++ b/t/69screen_reflow.test
@@ -0,0 +1,79 @@
+INIT
+# Run these tests on a much smaller default screen, so debug output is
+# nowhere near as noisy
+RESIZE 5,10
+WANTSTATE
+WANTSCREEN r
+RESET
+
+!Resize wider reflows wide lines
+RESET
+PUSH "A"x12
+ ?screen_row 0 = "AAAAAAAAAA"
+ ?screen_row 1 = "AA"
+ ?lineinfo 1 = cont
+ ?cursor = 1,2
+RESIZE 5,15
+ ?screen_row 0 = "AAAAAAAAAAAA"
+ ?screen_row 1 =
+ ?lineinfo 1 =
+ ?cursor = 0,12
+RESIZE 5,20
+ ?screen_row 0 = "AAAAAAAAAAAA"
+ ?screen_row 1 =
+ ?lineinfo 1 =
+ ?cursor = 0,12
+
+!Resize narrower can create continuation lines
+RESET
+RESIZE 5,10
+PUSH "ABCDEFGHI"
+ ?screen_row 0 = "ABCDEFGHI"
+ ?screen_row 1 = ""
+ ?lineinfo 1 =
+ ?cursor = 0,9
+RESIZE 5,8
+ ?screen_row 0 = "ABCDEFGH"
+ ?screen_row 1 = "I"
+ ?lineinfo 1 = cont
+ ?cursor = 1,1
+RESIZE 5,6
+ ?screen_row 0 = "ABCDEF"
+ ?screen_row 1 = "GHI"
+ ?lineinfo 1 = cont
+ ?cursor = 1,3
+
+!Shell wrapped prompt behaviour
+RESET
+RESIZE 5,10
+PUSH "PROMPT GOES HERE\r\n> \r\n\r\nPROMPT GOES HERE\r\n> "
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOE"
+ ?screen_row 3 = "S HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,11
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES"
+ ?screen_row 3 = " HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,12
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES "
+ ?screen_row 3 = "HERE"
+ ?lineinfo 3 = cont
+ ?screen_row 4 = "> "
+ ?cursor = 4,2
+RESIZE 5,16
+ ?screen_row 0 = "> "
+ ?screen_row 1 = ""
+ ?screen_row 2 = "PROMPT GOES HERE"
+ ?lineinfo 3 =
+ ?screen_row 3 = "> "
+ ?cursor = 3,2
diff --git a/t/harness.c b/t/harness.c
index 1cda50b..d541cb2 100644
--- a/t/harness.c
+++ b/t/harness.c
@@ -1,6 +1,7 @@
#include "vterm.h"
#include "../src/vterm_internal.h" // We pull in some internal bits too
+#include <assert.h>
#include <stdio.h>
#include <string.h>
@@ -399,6 +400,8 @@ static struct {
int conceal;
int strike;
int font;
+ int small;
+ int baseline;
VTermColor foreground;
VTermColor background;
} state_pen;
@@ -429,6 +432,12 @@ static int state_setpenattr(VTermAttr attr, VTermValue *val, void *user)
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;
@@ -448,6 +457,15 @@ static int state_setlineinfo(int row, const VTermLineInfo *newinfo, const VTermL
return 1;
}
+static int want_state_scrollback = 0;
+static int state_sb_clear(void *user) {
+ if(!want_state_scrollback)
+ return 1;
+
+ printf("sb_clear\n");
+ return 0;
+}
+
VTermStateCallbacks state_cbs = {
.putglyph = state_putglyph,
.movecursor = movecursor,
@@ -457,6 +475,7 @@ VTermStateCallbacks state_cbs = {
.setpenattr = state_setpenattr,
.settermprop = settermprop,
.setlineinfo = state_setlineinfo,
+ .sb_clear = state_sb_clear,
};
static int selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
@@ -566,6 +585,15 @@ static int screen_sb_popline(int cols, VTermScreenCell *cells, void *user)
return 1;
}
+static int screen_sb_clear(void *user)
+{
+ if(!want_screen_scrollback)
+ return 1;
+
+ printf("sb_clear\n");
+ return 0;
+}
+
VTermScreenCallbacks screen_cbs = {
.damage = screen_damage,
.moverect = moverect,
@@ -573,6 +601,7 @@ VTermScreenCallbacks screen_cbs = {
.settermprop = settermprop,
.sb_pushline = screen_sb_pushline,
.sb_popline = screen_sb_popline,
+ .sb_clear = screen_sb_clear,
};
int main(int argc, char **argv)
@@ -599,10 +628,12 @@ int main(int argc, char **argv)
}
else if(streq(line, "WANTPARSER")) {
+ assert(vt);
vterm_parser_set_callbacks(vt, &parser_cbs, NULL);
}
else if(strstartswith(line, "WANTSTATE") && (line[9] == '\0' || line[9] == ' ')) {
+ assert(vt);
if(!state) {
state = vterm_obtain_state(vt);
vterm_state_set_callbacks(state, &state_cbs, NULL);
@@ -641,12 +672,16 @@ int main(int argc, char **argv)
case 'f':
vterm_state_set_unrecognised_fallbacks(state, sense ? &fallbacks : NULL, NULL);
break;
+ case 'b':
+ want_state_scrollback = sense;
+ break;
default:
fprintf(stderr, "Unrecognised WANTSTATE flag '%c'\n", line[i]);
}
}
else if(strstartswith(line, "WANTSCREEN") && (line[10] == '\0' || line[10] == ' ')) {
+ assert(vt);
if(!screen)
screen = vterm_obtain_screen(vt);
vterm_screen_set_callbacks(screen, &screen_cbs, NULL);
@@ -682,6 +717,9 @@ int main(int argc, char **argv)
case 'b':
want_screen_scrollback = sense;
break;
+ case 'r':
+ vterm_screen_set_reflow(screen, sense);
+ break;
default:
fprintf(stderr, "Unrecognised WANTSCREEN flag '%c'\n", line[i]);
}
@@ -713,6 +751,8 @@ int main(int argc, char **argv)
else if(strstartswith(line, "PUSH ")) {
char *bytes = line + 5;
size_t len = inplace_hex2bytes(bytes);
+ assert(len);
+
size_t written = vterm_input_write(vt, bytes, len);
if(written < len)
fprintf(stderr, "! short write\n");
@@ -730,6 +770,7 @@ int main(int argc, char **argv)
else if(strstartswith(line, "ENCIN ")) {
char *bytes = line + 6;
size_t len = inplace_hex2bytes(bytes);
+ assert(len);
uint32_t cp[len];
int cpi = 0;
@@ -781,6 +822,7 @@ int main(int argc, char **argv)
}
else if(strstartswith(line, "FOCUS ")) {
+ assert(state);
char *linep = line + 6;
if(streq(linep, "IN"))
vterm_state_focus_in(state);
@@ -818,6 +860,7 @@ int main(int argc, char **argv)
}
else if(strstartswith(line, "SELECTION ")) {
+ assert(state);
char *linep = line + 10;
unsigned int mask;
int len;
@@ -834,6 +877,8 @@ int main(int argc, char **argv)
}
frag.len = inplace_hex2bytes(linep);
frag.str = linep;
+ assert(frag.len);
+
linep += frag.len * 2;
while(linep[0] == ' ')
linep++;
@@ -844,6 +889,7 @@ int main(int argc, char **argv)
}
else if(strstartswith(line, "DAMAGEMERGE ")) {
+ assert(screen);
char *linep = line + 12;
while(linep[0] == ' ')
linep++;
@@ -858,11 +904,13 @@ int main(int argc, char **argv)
}
else if(strstartswith(line, "DAMAGEFLUSH")) {
+ assert(screen);
vterm_screen_flush_damage(screen);
}
else if(line[0] == '?') {
if(streq(line, "?cursor")) {
+ assert(state);
VTermPos pos;
vterm_state_get_cursorpos(state, &pos);
if(pos.row != state_pos.row)
@@ -875,6 +923,7 @@ int main(int argc, char **argv)
printf("%d,%d\n", state_pos.row, state_pos.col);
}
else if(strstartswith(line, "?pen ")) {
+ assert(state);
char *linep = line + 5;
while(linep[0] == ' ')
linep++;
@@ -930,6 +979,24 @@ int main(int argc, char **argv)
else
printf("%d\n", state_pen.font);
}
+ else if(streq(linep, "small")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_SMALL, &val);
+ if(val.boolean != state_pen.small)
+ printf("! pen small mismatch; state=%s, event=%s\n",
+ BOOLSTR(val.boolean), BOOLSTR(state_pen.small));
+ else
+ printf("%s\n", BOOLSTR(state_pen.small));
+ }
+ else if(streq(linep, "baseline")) {
+ vterm_state_get_penattr(state, VTERM_ATTR_BASELINE, &val);
+ if(val.number != state_pen.baseline)
+ printf("! pen baseline mismatch: state=%d, event=%d\n",
+ val.number, state_pen.baseline);
+ else
+ printf("%s\n", state_pen.baseline == VTERM_BASELINE_RAISE ? "raise"
+ : state_pen.baseline == VTERM_BASELINE_LOWER ? "lower"
+ : "normal");
+ }
else if(streq(linep, "foreground")) {
print_color(&state_pen.foreground);
printf("\n");
@@ -942,6 +1009,7 @@ int main(int argc, char **argv)
printf("?\n");
}
else if(strstartswith(line, "?lineinfo ")) {
+ assert(state);
char *linep = line + 10;
int row;
const VTermLineInfo *info;
@@ -961,12 +1029,20 @@ int main(int argc, char **argv)
printf("\n");
}
else if(strstartswith(line, "?screen_chars ")) {
+ assert(screen);
char *linep = line + 13;
VTermRect rect;
size_t len;
while(linep[0] == ' ')
linep++;
- if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) < 4) {
+ if(sscanf(linep, "%d,%d,%d,%d", &rect.start_row, &rect.start_col, &rect.end_row, &rect.end_col) == 4)
+ ; // fine
+ else if(sscanf(linep, "%d", &rect.start_row) == 1) {
+ rect.end_row = rect.start_row + 1;
+ rect.start_col = 0;
+ vterm_get_size(vt, NULL, &rect.end_col);
+ }
+ else {
printf("! screen_chars unrecognised input\n");
goto abort_line;
}
@@ -985,6 +1061,7 @@ int main(int argc, char **argv)
}
}
else if(strstartswith(line, "?screen_text ")) {
+ assert(screen);
char *linep = line + 12;
VTermRect rect;
size_t len;
@@ -1021,6 +1098,7 @@ int main(int argc, char **argv)
}
}
else if(strstartswith(line, "?screen_cell ")) {
+ assert(screen);
char *linep = line + 12;
VTermPos pos;
while(linep[0] == ' ')
@@ -1043,6 +1121,10 @@ int main(int argc, char **argv)
if(cell.attrs.blink) printf("K");
if(cell.attrs.reverse) printf("R");
if(cell.attrs.font) printf("F%d", cell.attrs.font);
+ if(cell.attrs.small) printf("S");
+ if(cell.attrs.baseline) printf(
+ cell.attrs.baseline == VTERM_BASELINE_RAISE ? "^" :
+ "_");
printf("} ");
if(cell.attrs.dwl) printf("dwl ");
if(cell.attrs.dhl) printf("dhl-%s ", cell.attrs.dhl == 2 ? "bottom" : "top");
@@ -1055,6 +1137,7 @@ int main(int argc, char **argv)
printf("\n");
}
else if(strstartswith(line, "?screen_eol ")) {
+ assert(screen);
char *linep = line + 12;
while(linep[0] == ' ')
linep++;
@@ -1066,6 +1149,7 @@ int main(int argc, char **argv)
printf("%d\n", vterm_screen_is_eol(screen, pos));
}
else if(strstartswith(line, "?screen_attrs_extent ")) {
+ assert(screen);
char *linep = line + 21;
while(linep[0] == ' ')
linep++;
diff --git a/t/run-test.pl b/t/run-test.pl
index 730eef3..99c36df 100755
--- a/t/run-test.pl
+++ b/t/run-test.pl
@@ -125,7 +125,7 @@ sub do_line
elsif( $line =~ m/^putglyph (\S+) (.*)$/ ) {
$line = "putglyph " . join( ",", map sprintf("%x", $_), eval($1) ) . " $2";
}
- elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|settermprop|setmousefunc|selection-query) / ) {
+ elsif( $line =~ m/^(?:movecursor|scrollrect|moverect|erase|damage|sb_pushline|sb_popline|sb_clear|settermprop|setmousefunc|selection-query) ?/ ) {
# no conversion
}
elsif( $line =~ m/^(selection-set) (.*?) (\[?)(.*?)(\]?)$/ ) {
@@ -140,17 +140,23 @@ sub do_line
# ?screen_row assertion is emulated here
elsif( $line =~ s/^\?screen_row\s+(\d+)\s*=\s*// ) {
my $row = $1;
- my $row1 = $row + 1;
- my $want = eval($line);
+ my $want;
+
+ if( $line =~ m/^"/ ) {
+ $want = eval($line);
+ }
+ else {
+ # Turn 0xDD,0xDD,... directly into bytes
+ $want = pack "C*", map { hex } split m/,/, $line;
+ }
do_onetest if defined $command;
- # TODO: may not be 80
- $hin->print( "\?screen_chars $row,0,$row1,80\n" );
+ $hin->print( "\?screen_chars $row\n" );
my $response = <$hout>;
chomp $response;
- $response = pack "C*", map hex, split m/,/, $response;
+ $response = pack "C*", map { hex } split m/,/, $response;
if( $response ne $want ) {
print "# line $linenum: Assert ?screen_row $row failed:\n" .
"# Expected: $want\n" .