Problem: The libvterm code is outdated.
Solution: Include libvterm changes from revision 790 to 801.
Modifications:
- Add a .gitignore file.
-- Convert from C99 to C90.
+- Convert some code from C99 to C90.
- Other changes to support embedding in Vim.
To get the latest version of libvterm you need the "bzr" command and do:
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
#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;
*/
typedef struct {
VTermPos pos; /* current cursor position */
+ VTermLineInfo *lineinfos[2]; /* [1] may be NULL */
} VTermStateFields;
typedef struct {
void (*free)(void *ptr, void *allocdata);
} VTermAllocatorFunctions;
+/* A convenient shortcut for default cases */
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 */
// Allocate and initialize a new terminal with default allocators.
VTerm *vterm_new(int rows, int cols);
+/* These shortcuts are generally discouraged in favour of just using vterm_build() */
+
// Allocate and initialize a new terminal with specified allocators.
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata);
};
typedef struct {
-#define VTERM_MAX_CHARS_PER_CELL 6
uint32_t chars[VTERM_MAX_CHARS_PER_CELL];
char width;
VTermScreenCellAttrs attrs;
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);
// Find the final row of old buffer content
int old_row = old_rows - 1;
for( ; col < new_cols; col++)
clearcell(screen, &new_buffer[new_row * new_cols + col]);
+ new_lineinfo[new_row] = old_lineinfo[old_row];
+
old_row--;
new_row--;
/* 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++)
+ for(new_row = moverows; new_row < new_rows; new_row++) {
for(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;
+ vterm_allocator_free(screen->vt, old_lineinfo);
+ statefields->lineinfos[bufidx] = new_lineinfo;
+
return;
/* REFLOW TODO:
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) {
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];
static int on_text(const char bytes[], size_t len, void *user)
{
VTermState *state = user;
- uint32_t *codepoints;
int npoints = 0;
size_t eaten = 0;
VTermEncodingInstance *encoding;
// We'll have at most len codepoints, plus one from a previous incomplete
// sequence.
- codepoints = vterm_allocator_malloc(state->vt, (len + 1) * sizeof(uint32_t));
- if (codepoints == NULL)
- return 0;
+ uint32_t *codepoints = (uint32_t *)(state->vt->tmpbuffer);
+ size_t maxpoints = (state->vt->tmpbuffer_len) / sizeof(uint32_t);
encoding =
state->gsingle_set ? &state->encoding[state->gsingle_set] :
&state->encoding[state->gr_set];
(*encoding->enc->decode)(encoding->enc, encoding->data,
- codepoints, &npoints, state->gsingle_set ? 1 : (int)len,
+ codepoints, &npoints, state->gsingle_set ? 1 : (int)maxpoints,
bytes, &eaten, len);
/* There's a chance an encoding (e.g. UTF-8) hasn't found enough bytes yet
*/
if(!npoints)
{
- vterm_allocator_free(state->vt, codepoints);
return (int)eaten;
}
int glyph_starts = i;
int glyph_ends;
int width = 0;
- uint32_t *chars;
- 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;
- chars = vterm_allocator_malloc(state->vt, (glyph_ends - glyph_starts + 1) * sizeof(uint32_t));
+ uint32_t *chars = vterm_allocator_malloc(state->vt, (VTERM_MAX_CHARS_PER_CELL + 1) * sizeof(uint32_t));
if (chars == NULL)
break;
}
#endif
- vterm_allocator_free(state->vt, codepoints);
return (int)eaten;
}
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;
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);
state->tabstops = newtabstops;
}
- if(rows != state->rows) {
- int bufidx;
- for(bufidx = BUFIDX_PRIMARY; bufidx <= BUFIDX_ALTSCREEN; bufidx++) {
- int row;
- VTermLineInfo *oldlineinfo = state->lineinfos[bufidx];
- VTermLineInfo *newlineinfo;
- if(!oldlineinfo)
- continue;
-
- newlineinfo = vterm_allocator_malloc(state->vt, rows * sizeof(VTermLineInfo));
-
- for(row = 0; row < state->rows && row < rows; row++) {
- newlineinfo[row] = oldlineinfo[row];
- }
-
- for( ; row < rows; row++) {
- VTermLineInfo lineInfo = {0x0};
- newlineinfo[row] = lineInfo;
- }
-
- 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;
UBOUND(state->scrollregion_right, state->cols);
fields.pos = state->pos;
+ fields.lineinfos[0] = state->lineinfos[0];
+ fields.lineinfos[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->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->lineinfo = state->lineinfos[state->mode.alt_screen ? BUFIDX_ALTSCREEN : BUFIDX_PRIMARY];
if(state->at_phantom && state->pos.col < cols-1) {
state->at_phantom = 0;
VTerm *vterm_new(int rows, int cols)
{
- return vterm_new_with_allocator(rows, cols, &default_allocator, NULL);
+ struct VTermBuilder builder;
+ memset(&builder, 0, sizeof(builder));
+ builder.rows = rows;
+ builder.cols = cols;
+ return vterm_build(&builder);
}
VTerm *vterm_new_with_allocator(int rows, int cols, VTermAllocatorFunctions *funcs, void *allocdata)
{
+ struct VTermBuilder builder;
+ memset(&builder, 0, sizeof(builder));
+ builder.rows = rows;
+ builder.cols = cols;
+ builder.allocator = funcs;
+ builder.allocdata = allocdata;
+ return vterm_build(&builder);
+}
+
+/* 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);
- if (vt == NULL)
- return NULL;
- 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;
vt->outfunc = NULL;
vt->outdata = NULL;
- vt->outbuffer_len = 200;
+ 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);
if (vt->tmpbuffer == NULL
struct VTerm
{
- VTermAllocatorFunctions *allocator;
+ const VTermAllocatorFunctions *allocator;
void *allocdata;
int rows;
PUSH "\e[c"
output "\e[?1;2c"
+!XTVERSION
+RESET
+PUSH "\e[>q"
+ output "\eP>|libvterm(0.2)\e\\"
+
!DSR
RESET
PUSH "\e[5n"
output "\x{9b}0n"
PUSH "\e F"
-!Truncation on attempted buffer overflow
-PUSH "\e[6n" x 30
- output "\e[10;10R" x 25
+#!Truncation on attempted buffer overflow
+#PUSH "\e[6n" x 30
+# output "\e[10;10R" x 25
?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
!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"
!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
!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"
# 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)
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)
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)
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"
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
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
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
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
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
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"
!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 = ""
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;
}
# ?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" .
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 772,
/**/
771,
/**/