vtgl

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

Commit: e605378fbffdeb8ac0d9bfed5baa0dd67e4db78e
Parent: d6121f4c8f5fc54a4bba186c04dfef217286a2a0
Author: Randy Palamar
Date:   Fri, 27 Sep 2024 03:35:48 -0600

support HTS: horizontal tab stop

Diffstat:
Mterminal.c | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
Mtest.c | 6++++--
Mutil.c | 9+++++++++
Mutil.h | 6++++++
Mvtgl.c | 5+++++
5 files changed, 84 insertions(+), 8 deletions(-)

diff --git a/terminal.c b/terminal.c @@ -288,6 +288,34 @@ cursor_step_raw(Term *t, i32 step, i32 rows, i32 cols) cursor_move_to(t, t->cursor.pos.y + rows, t->cursor.pos.x + cols); } +static i32 +next_tab_position(Term *t, b32 backwards) +{ + u32 col = t->cursor.pos.x; + u32 idx = col / ARRAY_COUNT(t->tabs); + u32 bit = col % ARRAY_COUNT(t->tabs); + u32 mask = safe_left_shift(1, bit - backwards) - 1; + + i32 result = 32 * idx; + if (backwards) result += 32 - _lzcnt_u32(t->tabs[idx] & mask); + else result += _tzcnt_u32(t->tabs[idx] & ~mask) + 1; + ASSERT(ABS(result) < t->size.w); + + return result; +} + +static void +term_tab_col(Term *t, u32 col, b32 set) +{ + ASSERT(col < t->size.w); + u32 idx = (col - 1) / ARRAY_COUNT(t->tabs); + u32 bit = (col - 1) % ARRAY_COUNT(t->tabs); + u32 mask = 1u; + if (bit) mask = 1 << bit; + if (set) t->tabs[idx] |= mask; + else t->tabs[idx] &= ~mask; +} + static void term_reset(Term *t) { @@ -299,6 +327,11 @@ term_reset(Term *t) swap_screen(t); fb_clear_region(t, 0, t->size.h, 0, t->size.w); } + for (u32 i = 0; i < ARRAY_COUNT(t->tabs); i++) + t->tabs[i] = 0; + for (u32 i = g_tabstop; i < t->size.w; i += g_tabstop) + term_tab_col(t, i, 1); + t->top = 0; t->bot = t->size.h - 1; /* TODO: why is term_reset() being called when we are in the altscreen */ @@ -401,6 +434,24 @@ erase_characters(Term *t, i32 count) fb_clear_region(t, cpos.y, cpos.y, cpos.x, cpos.x + count - 1); } +/* TBC: Tabulation Clear */ +static void +clear_term_tab(Term *t, i32 arg) +{ + /* TODO: case 1, 2? */ + switch(arg) { + case 0: + term_tab_col(t, t->cursor.pos.x, 0); + break; + case 3: + for (u32 i = 0; i < ARRAY_COUNT(t->tabs); i++) + t->tabs[i] = 0; + break; + default: + fprintf(stderr, "clear_term_tab: unhandled arg: %d\n", arg); + } +} + /* SM/DECSET: Set Mode & RM/DECRST Reset Mode */ static void set_mode(Term *t, CSI *csi, b32 set) @@ -657,10 +708,9 @@ push_newline(Term *t, b32 move_to_first_col) static void push_tab(Term *t, i32 n) { - i32 n_abs = ABS(n); - i32 n_sgn = SGN(n); - i32 advance = n_abs * g_tabstop - (t->cursor.pos.x % g_tabstop); - cursor_step_column(t, n_sgn * advance); + u32 end = ABS(n); + for (u32 i = 0; i < end; i++) + cursor_move_to(t, t->cursor.pos.y, next_tab_position(t, n < 0)); } static i32 @@ -724,6 +774,7 @@ handle_csi(Term *t, CSI *csi) case 'd': cursor_move_abs_to(t, csi->argv[0] - 1, p.x); break; case 'e': cursor_step_raw(t, ORONE(csi->argv[0]), 1, 0); break; case 'f': cursor_move_abs_to(t, csi->argv[0] - 1, csi->argv[1] - 1); break; + case 'g': clear_term_tab(t, csi->argv[0]); break; case 'h': set_mode(t, csi, 1); break; case 'l': set_mode(t, csi, 0); break; case 'm': set_colours(t, csi); break; @@ -870,6 +921,9 @@ handle_escape(Term *t, s8 *raw, Arena a) case 'E': /* NEL -- Next Line */ push_newline(t, 1); break; + case 'H': /* HTS -- Horizontal Tab Stop */ + term_tab_col(t, t->cursor.pos.x, 1); + break; case 'M': /* RI -- Reverse Index */ if (t->cursor.pos.y == t->top) { fb_scroll_down(t, t->top, 1); @@ -902,8 +956,8 @@ push_control(Term *t, s8 *line, u32 cp, Arena a) cursor_move_to(t, t->cursor.pos.y, t->cursor.pos.x - 1); break; } - if (cp != 0x1B && t->escape & EM_CSI) { - t->csi.raw.len++; + if (cp != 0x1B) { + if (t->escape & EM_CSI) t->csi.raw.len++; } } diff --git a/test.c b/test.c @@ -176,8 +176,10 @@ static TEST_FN(cursor_tabs) result.status = term->cursor.pos.x == (g_tabstop); - /* NOTE: now test negative tabstop movement */ - launder_static_string(term, s8("\t1234\t")); + /* NOTE: now test negative tabstop movement and tabstop setting */ + launder_static_string(term, s8("12")); + 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); diff --git a/util.c b/util.c @@ -1,4 +1,13 @@ /* See LICENSE for copyright details */ + +/* NOTE: avoids braindead standards committee UB when n is negative or shift is > 31 */ +static u32 +safe_left_shift(u32 n, u32 shift) +{ + u64 result = (u64)n << (shift & 63); + return result & 0xFFFFFFFF; +} + static b32 equal_iv2(iv2 a, iv2 b) { diff --git a/util.h b/util.h @@ -268,6 +268,8 @@ struct conversion_result { union { i32 i; f32 f; Colour colour;}; }; +#include <immintrin.h> + #include "util.c" #define STB_TRUETYPE_IMPLEMENTATION @@ -419,6 +421,10 @@ typedef struct { enum escape_mode escape; enum terminal_mode mode; + /* NOTE: this means we limit ourselves to 32 * 32 cells (1024). Even on a 4k sceen + * this would mean that cells are ~4px wide which is unreadable */ + u32 tabs[32]; + char saved_title[1024]; #ifdef _DEBUG diff --git a/vtgl.c b/vtgl.c @@ -122,6 +122,11 @@ resize(Term *t) t->size.w = (u32)(ws.w / cs.w); t->size.h = (u32)(ws.h / cs.h); + if (t->size.w > ARRAY_COUNT(t->tabs) * 32) { + t->size.w = ARRAY_COUNT(t->tabs) * 32u; + fprintf(stderr, "resize: max terminal width is %u; clamping\n", t->size.w); + } + if (!equal_uv2(old_size, t->size)) { os_alloc_framebuffer(&t->views[0].fb, t->size.h, t->size.w); os_alloc_framebuffer(&t->views[1].fb, t->size.h, t->size.w);