vtgl

terminal emulator implemented in OpenGL
git clone anongit@rnpnr.xyz:vtgl.git
Log | Files | Refs | Feed | LICENSE

Commit: 9d496646ed60c7474aa532fbe0b15d65f557a6fe
Parent: 0fb0a5581dfe26f48d6959528c31d09042fbaf56
Author: Randy Palamar
Date:   Sun, 27 Oct 2024 22:13:05 -0600

disable split_raw_input_to_lines for now

the secondary path has quite a few issues due to the need to
maintain consitent state. the input can still be split into lines
in the primary path for use in scrollback. This fixes a number of
bugs related to displaying things incorrectly (especially in the
alt screen).

Diffstat:
Mterminal.c | 190++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mtest.c | 30++++++++++++------------------
Mutil.c | 2+-
Mvtgl.c | 7+++++++
4 files changed, 165 insertions(+), 64 deletions(-)

diff --git a/terminal.c b/terminal.c @@ -240,7 +240,6 @@ swap_screen(Term *t) { t->mode ^= TM_ALTSCREEN; t->view_idx = !!(t->mode & TM_ALTSCREEN); - t->gl.flags |= NEEDS_FULL_REFILL; } static void @@ -1108,6 +1107,8 @@ end: return result; } +#define SPLIT_LONG 4096L +#if 0 static size split_raw_input_to_lines(Term *t, s8 raw) { @@ -1210,6 +1211,59 @@ end: return parsed_lines; } +#endif + +static void +push_normal_cp(Term *t, TermView *tv, u32 cp) +{ + BEGIN_TIMED_BLOCK(); + + if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) + push_newline(t, 1); + + u32 width = 1; + if (cp > 0x7F) { + /* TODO: this is obviously complete crap but wcwidth from libc doesn't + * actually work so we must check from the rendered glyph */ + CachedGlyph *cg; + get_gpu_glyph_index(t->arena_for_frame, &t->gl, &t->fa, cp, 0, 0, &cg); + width = cg->tile_count; + } + + /* NOTE: make this '>=' for fun in vis */ + if (t->cursor.pos.x + width > t->size.w) { + /* NOTE: make space for character if mode enabled else + * clobber whatever was on the end of the line */ + if (t->mode & TM_AUTO_WRAP) + push_newline(t, 1); + else + cursor_move_to(t, t->cursor.pos.y, t->size.w - width); + } + + Cell *c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x]; + /* TODO: pack cell into ssbo */ + c->cp = cp; + c->style = t->cursor.style; + + for (u32 w = width - 1; w; w--) { + c->style.attr |= ATTR_WIDE; + if (t->cursor.pos.x + w < t->size.w) { + Cell *nc = c + w; + nc->style.attr |= ATTR_WDUMMY; + } + } + + if (t->cursor.pos.x + width < t->size.w) + cursor_step_column(t, width); + else + t->cursor.state |= CURSOR_WRAP_NEXT; + + /* TODO: region detection */ + if (is_selected(&t->selection, t->cursor.pos.x, t->cursor.pos.y)) + selection_clear(&t->selection); + + END_TIMED_BLOCK(); +} static void push_line(Term *t, Line *line, Arena a) @@ -1220,7 +1274,6 @@ push_line(Term *t, Line *line, Arena a) s8 l = line_to_s8(line, &tv->log); t->cursor.style = line->cursor_state; - Cell *c; while (l.len) { u32 cp; if (line->has_unicode) cp = get_utf8(&l); @@ -1241,49 +1294,7 @@ push_line(Term *t, Line *line, Arena a) continue; } - if (t->mode & TM_AUTO_WRAP && t->cursor.state & CURSOR_WRAP_NEXT) - push_newline(t, 1); - - u32 width; - if (line->has_unicode) { - /* TODO: this is obviously complete crap but wcwidth from libc doesn't - * actually work so we must check from the rendered glyph */ - CachedGlyph *cg; - get_gpu_glyph_index(t->arena_for_frame, &t->gl, &t->fa, cp, 0, 0, &cg); - width = cg->tile_count; - } else { - width = 1; - } - - /* NOTE: make this '>=' for fun in vis */ - if (t->cursor.pos.x + width > t->size.w) { - /* NOTE: make space for character if mode enabled else - * clobber whatever was on the end of the line */ - if (t->mode & TM_AUTO_WRAP) - push_newline(t, 1); - else - cursor_move_to(t, t->cursor.pos.y, t->size.w - width); - } - - c = &tv->fb.rows[t->cursor.pos.y][t->cursor.pos.x]; - c->cp = cp; - c->style = t->cursor.style; - - if (width == 2) { - c->style.attr |= ATTR_WIDE; - if (t->cursor.pos.x + 1 < t->size.w) { - Cell *nc = c + 1; - nc->style.attr |= ATTR_WDUMMY; - } - } - - if (t->cursor.pos.x + width < t->size.w) - cursor_step_column(t, width); - else - t->cursor.state |= CURSOR_WRAP_NEXT; - - if (is_selected(&t->selection, t->cursor.pos.x, t->cursor.pos.y)) - selection_clear(&t->selection); + push_normal_cp(t, tv, cp); } END_TIMED_BLOCK(); } @@ -1314,10 +1325,12 @@ blit_lines(Term *t, Arena a, size line_count) CLAMP(line_count, 0, tv->lines.filled); for (size idx = -line_count; idx <= 0; idx++) { size line_idx = get_line_idx(&tv->lines, idx - off); + #if 0 if (line_idx == tv->last_line_idx) t->cursor.pos = tv->last_cursor_pos; tv->last_cursor_pos = t->cursor.pos; tv->last_line_idx = line_idx; + #endif push_line(t, tv->lines.buf + line_idx, a); /* TODO: can we avoid this? */ ASSERT(t->escape == 0); @@ -1327,3 +1340,90 @@ blit_lines(Term *t, Arena a, size line_count) END_TIMED_BLOCK(); } + +static void +handle_input(Term *t, Arena a, s8 raw) +{ + BEGIN_TIMED_BLOCK(); + + TermView *tv = t->views + t->view_idx; + + /* TODO: SIMD look ahead */ + while (raw.len) { + size start_len = raw.len; + u32 cp = peek(raw, 0); + if (cp & 0x80) { + cp = get_utf8(&raw); + tv->lines.buf[tv->lines.widx].has_unicode = 1; + if (cp == (u32)-1) { + /* NOTE: Need More Bytes! */ + raw.len = start_len; + goto end; + } + } else { + cp = get_ascii(&raw); + } + + /* TODO: cleanup */ + if (cp == '\n') + feed_line(&tv->lines, raw.data, t->cursor.style); + + ASSERT(cp != (u32)-1); + + if (ISCONTROL(cp)) { + if (cp == 0x1B && raw.len == 0) { + raw.len = start_len; + goto end; + } else { + iv2 old_curs = t->cursor.pos; + push_control(t, &raw, cp, a); + if (!equal_iv2(t->cursor.pos, old_curs)) { + if (line_length(tv->lines.buf + tv->lines.widx)) + feed_line(&tv->lines, raw.data, t->cursor.style); + } + } + continue; + } else if (t->escape & EM_CSI) { + t->csi.raw.len++; + if (BETWEEN(cp, '@', '~')) { + iv2 old_curs = t->cursor.pos; + i32 mode = t->mode & TM_ALTSCREEN; + handle_csi(t, &t->csi); + t->escape &= ~EM_CSI; + if ((t->mode & TM_ALTSCREEN) != mode) { + u8 *old = raw.data - t->csi.raw.len - 2; + ASSERT(*old == 0x1B); + feed_line(&tv->lines, old, t->cursor.style); + TermView *nv = t->views + t->view_idx; + size nstart = nv->log.widx; + mem_copy(raw, (s8){nv->log.cap, nv->log.buf + nstart}); + commit_to_rb(tv, -raw.len); + commit_to_rb(nv, raw.len); + raw.data = nv->log.buf + nstart; + init_line(nv->lines.buf + nv->lines.widx, raw.data, + t->cursor.style); + tv = nv; + } else if (!equal_iv2(t->cursor.pos, old_curs)) { + if (line_length(tv->lines.buf + tv->lines.widx)) + feed_line(&tv->lines, raw.data, t->cursor.style); + } + } + continue; + } + + push_normal_cp(t, tv, cp); + + } +end: + tv->lines.buf[tv->lines.widx].end = raw.data; + + /* TODO: this shouldn't be needed */ + if (tv->lines.buf[tv->lines.widx].end < tv->lines.buf[tv->lines.widx].start) + tv->lines.buf[tv->lines.widx].start -= tv->log.cap; + + if (!t->escape && line_length(tv->lines.buf + tv->lines.widx) > SPLIT_LONG) + feed_line(&tv->lines, raw.data, t->cursor.style); + + t->unprocessed_bytes = raw.len; + END_TIMED_BLOCK(); +} diff --git a/test.c b/test.c @@ -138,8 +138,7 @@ static TEST_FN(csi_embedded_control) launder_static_string(term, s8("1")); launder_static_string(term, CSI(48;2;7\b5;63;4\n2m)); s8 raw = launder_static_string(term, s8(" 2")); - size parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + handle_input(term, arena, raw); #if 0 dump_csi(&term->csi); @@ -174,8 +173,7 @@ static TEST_FN(colour_setting) launder_static_string(term, CSI(48;2;75;63;42m)); s8 raw = launder_static_string(term, s8("A")); - size parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + handle_input(term, arena, raw); Cell c = { .cp = 'A', .style = { .bg = (Colour){.r = 75, .g = 63, .b = 42, .a = 0xFF}, @@ -192,12 +190,10 @@ static TEST_FN(cursor_movement) struct test_result result = {.info = __FUNCTION__}; s8 raw = launder_static_string(term, CSI(17;2H)); - size parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + handle_input(term, arena, raw); raw = launder_static_string(term, CSI(7G)); - parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + handle_input(term, arena, raw); result.status = term->cursor.pos.y == 16 && term->cursor.pos.x == 6; @@ -209,9 +205,8 @@ static TEST_FN(cursor_tabs) struct test_result result = {.info = __FUNCTION__}; /* NOTE: first test advancing to a tabstop */ - s8 raw = launder_static_string(term, s8("123\t")); - size parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + s8 raw = launder_static_string(term, s8("123\t")); + handle_input(term, arena, raw); result.status = term->cursor.pos.x == (g_tabstop); @@ -220,8 +215,7 @@ static TEST_FN(cursor_tabs) launder_static_string(term, ESC(H)); launder_static_string(term, s8("34\t")); raw = launder_static_string(term, CSI(2Z)); - parsed_lines = split_raw_input_to_lines(term, raw); - blit_lines(term, arena, parsed_lines); + handle_input(term, arena, raw); result.status &= term->cursor.pos.x == (g_tabstop); @@ -252,7 +246,7 @@ static TEST_FN(cursor_at_line_boundary) long_line.data[SPLIT_LONG + 1] = red_dragon.data[2]; long_line.data[SPLIT_LONG + 2] = red_dragon.data[3]; - /* NOTE: shove a newline at the end so that finish parsing the second line */ + /* NOTE: shove a newline at the end so that the line completes */ long_line.data[long_line.len - 1] = '\n'; #if 0 @@ -264,10 +258,10 @@ static TEST_FN(cursor_at_line_boundary) s8 raw = launder_static_string(term, long_line); LineBuf *lb = &term->views[term->view_idx].lines; - /* NOTE: check if line actually split and that it was split after red dragon */ - size parsed_lines = split_raw_input_to_lines(term, raw); - result.status = parsed_lines == 2; - result.status &= line_length(lb->buf) > SPLIT_LONG; + size line_count = lb->filled; + /* NOTE: ensure line didn't split on red dragon */ + handle_input(term, arena, raw); + result.status = line_length(lb->buf) > SPLIT_LONG; /* NOTE: check that cursor state was transferred */ Cursor line_end = simulate_line(term, lb->buf); diff --git a/util.c b/util.c @@ -98,7 +98,7 @@ make_arena(Arena *a, size size) static void commit_to_rb(TermView *tv, size len) { - ASSERT(len <= tv->log.cap); + ASSERT(ABS(len) <= tv->log.cap); tv->log.widx += len; tv->log.filled += len; diff --git a/vtgl.c b/vtgl.c @@ -820,6 +820,8 @@ do_terminal(Term *t, f32 dt) resize(t); if (t->gl.flags & INIT_DEBUG) { + /* TODO: cleanup this needs a better place */ + term_reset(t); debug_init(t, t->arena_for_frame); t->gl.flags &= ~INIT_DEBUG; } @@ -843,8 +845,13 @@ do_terminal(Term *t, f32 dt) .len = t->unprocessed_bytes, .data = rb->buf + (rb->widx - t->unprocessed_bytes) }; + #if 1 + handle_input(t, t->arena_for_frame, raw); + t->gl.flags |= UPDATE_RENDER_BUFFER; + #else parsed_lines = split_raw_input_to_lines(t, raw); t->gl.flags |= NEEDS_REFILL; + #endif } if (t->gl.flags & (NEEDS_REFILL|NEEDS_FULL_REFILL)) {