diff options
author | James McCoy <jamessan@debian.org> | 2022-09-12 20:28:03 -0400 |
---|---|---|
committer | James McCoy <jamessan@debian.org> | 2022-09-12 20:28:03 -0400 |
commit | 97864b073a8a8cc553a844063242a2b0dec364a3 (patch) | |
tree | f34b2af02dca0b8f47445568379515825ca9b444 | |
parent | 2727851f76a12754bcc7605ba715a05f0f5e5ec0 (diff) |
New upstream version 0.3~rc1
-rw-r--r-- | CONTRIBUTING | 4 | ||||
-rw-r--r-- | Makefile | 2 | ||||
-rw-r--r-- | bin/vterm-ctrl.c | 2 | ||||
-rw-r--r-- | doc/seqs.txt | 4 | ||||
-rw-r--r-- | include/vterm.h | 45 | ||||
-rw-r--r-- | src/pen.c | 52 | ||||
-rw-r--r-- | src/screen.c | 233 | ||||
-rw-r--r-- | src/state.c | 95 | ||||
-rw-r--r-- | src/vterm.c | 39 | ||||
-rw-r--r-- | src/vterm_internal.h | 4 | ||||
-rw-r--r-- | t/10state_putglyph.test | 6 | ||||
-rw-r--r-- | t/13state_edit.test | 6 | ||||
-rw-r--r-- | t/26state_query.test | 5 | ||||
-rw-r--r-- | t/30state_pen.test | 11 | ||||
-rw-r--r-- | t/60screen_ascii.test | 18 | ||||
-rw-r--r-- | t/61screen_unicode.test | 6 | ||||
-rw-r--r-- | t/62screen_damage.test | 2 | ||||
-rw-r--r-- | t/63screen_resize.test | 34 | ||||
-rw-r--r-- | t/64screen_pen.test | 6 | ||||
-rw-r--r-- | t/65screen_protect.test | 8 | ||||
-rw-r--r-- | t/69screen_reflow.test | 79 | ||||
-rw-r--r-- | t/harness.c | 86 | ||||
-rwxr-xr-x | t/run-test.pl | 18 |
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> @@ -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); @@ -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" . |