From bed20eb36453c61c6aff76bdfda0c15235b8e513 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Tue, 18 Aug 2026 23:07:21 +0200 Subject: [PATCH] terminal: clear tabstop bit on unset instead of toggling Tabstops.unset used XOR, so unsetting a column without a tabstop set one instead. This made TBC (CSI 0 g) and CTC (CSI 2 W) create a tabstop at the cursor column when none existed. Signed-off-by: Fredrik Fornwall --- src/terminal/Tabstops.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/terminal/Tabstops.zig b/src/terminal/Tabstops.zig index 7e74bc961..fb1d7ae61 100644 --- a/src/terminal/Tabstops.zig +++ b/src/terminal/Tabstops.zig @@ -93,13 +93,13 @@ pub fn unset(self: *Tabstops, col: usize) void { const i = entry(col); const idx = index(col); if (i < prealloc_count) { - self.prealloc_stops[i] ^= masks[idx]; + self.prealloc_stops[i] &= ~masks[idx]; return; } const dynamic_i = i - prealloc_count; assert(dynamic_i < self.dynamic_stops.len); - self.dynamic_stops[dynamic_i] ^= masks[idx]; + self.dynamic_stops[dynamic_i] &= ~masks[idx]; } /// Get the value of a tabstop at a specific column. The columns are 0-indexed. @@ -198,6 +198,10 @@ test "Tabstops: basic" { try testing.expect(t.get(4)); t.unset(4); try testing.expect(!t.get(4)); + + // Unsetting a column with no tabstop should be a no-op, not a toggle. + t.unset(4); + try testing.expect(!t.get(4)); } test "Tabstops: dynamic allocations" { @@ -214,6 +218,10 @@ test "Tabstops: dynamic allocations" { try testing.expect(t.get(cap + 4)); try testing.expect(!t.get(cap + 3)); + // Unsetting a column with no tabstop should be a no-op, not a toggle. + t.unset(cap + 3); + try testing.expect(!t.get(cap + 3)); + // Growing again preserves existing stops and clears the new unit. try t.resize(testing.allocator, cap + unit_bits + 1); try testing.expect(t.get(cap + 4));