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 <fredrik@fornwall.net>
This commit is contained in:
Fredrik Fornwall
2026-08-18 23:07:21 +02:00
parent 7f62fe70a2
commit bed20eb364

View File

@@ -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));