Commit: e605378fbffdeb8ac0d9bfed5baa0dd67e4db78e
Parent: d6121f4c8f5fc54a4bba186c04dfef217286a2a0
Author: Randy Palamar
Date: Fri, 27 Sep 2024 03:35:48 -0600
support HTS: horizontal tab stop
Diffstat:
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);