From 2868516c215a96d9e1e933ded7ac5744ec16c368 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Thu, 1 Oct 2026 19:20:19 -0300 Subject: Tab N sets a tab's width in cells, 1 through 16, in every pane, drawn and counted (wrapping, clicks, the cursor); bare Tab says it; the ctl read and a Dump keep it A session setting, not acme's per window: the width is read by File's pure width functions (~130 callers that are given no pane), so it is one value, config.tab_now, that Runtime.apply sets and a new session resets. A file:L:C column counts runes, as compilers do, and never depended on it. Co-Authored-By: Claude Opus 5.5 --- src/pardes.zig | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) (limited to 'src/pardes.zig') diff --git a/src/pardes.zig b/src/pardes.zig index 36a37293..af2b80a2 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -3149,6 +3149,47 @@ test "Unicode display cells map back to body and tag byte cursors" { try std.testing.expectEqualStrings(before, tagline.curTail(pane)); } +test "Tab N draws every pane's tab N cells wide and clicks count past it; bare Tab says the width; the ctl read and a Dump keep it" { + const gpa = std.testing.allocator; + const p = try Pardes.init(gpa, .{ .cols = 60, .rows = 12 }); + defer p.deinit(); + // The width is the process's: the next test starts from the default. + defer config.tab_now = config.tab_width; + const pane = try p.setTestFile("\tx = 1\n"); + try std.testing.expect(p.executeBuiltinLine(0, "Tab 8")); + try std.testing.expectEqual(@as(u16, 8), config.tab_now); + var frame = std.heap.ArenaAllocator.init(gpa); + defer frame.deinit(); + const surface = try p.render(frame.allocator()); + const rect = p.rects[0]; + const text_x = rect.x + config.GUTTER + config.PREFIX_W; + const body_y = rect.y + BOX_H; + try std.testing.expectEqualStrings("x", surface.at(text_x + 8, body_y).grapheme()); + // A click on the x is on its byte, past the tab. + p.update(.{ .mouse = .{ .button = config.select_button, .kind = .press, .col = text_x + 8, .row = body_y } }); + p.update(.{ .mouse = .{ .button = config.select_button, .kind = .release, .col = text_x + 8, .row = body_y } }); + try std.testing.expectEqual(@as(i32, 1), pane.body.cur_col); + // Bare, it says the width. + try std.testing.expect(p.executeBuiltinLine(0, "Tab")); + try std.testing.expectEqualStrings("Tab 8", pane.msg[0..pane.msg_len]); + // Out of range: refused, the width kept. + try std.testing.expect(p.executeBuiltinLine(0, "Tab 0")); + try std.testing.expectEqual(@as(u16, 8), config.tab_now); + // The ctl read's tab column, and a Dump's settings. + const ctl_text = pardes_ctlfs_ctlText(p, pane); + try std.testing.expect(std.mem.indexOf(u8, ctl_text, " 8 ") != null); + try dump.dumpState(p); + const restored = try dump.restore(p, p.dump_out.?, "/tmp/pardes.dump.zon"); + defer restored.deinit(); + try std.testing.expectEqual(@as(u16, 8), restored.settings.tab); + try std.testing.expectEqual(@as(u16, 8), config.tab_now); +} + +fn pardes_ctlfs_ctlText(p: *Pardes, pane: *Pane) []const u8 { + const th = @import("ninep/testing.zig"); + return th.rd(p, ctlfs.Node.of(pane.serial, .ctl), 0, 4096).bytes; +} + test "tabbed file aligns syntax cursor and mouse at the nearest insertion gap" { const gpa = std.testing.allocator; const p = try Pardes.init(gpa, .{ .cols = 60, .rows = 12 }); @@ -4618,6 +4659,8 @@ pub const Pardes = struct { } pub fn init(gpa: std.mem.Allocator, opts: Options) !*Pardes { + // A new session draws a tab at the default until `Tab` says otherwise. + config.tab_now = config.tab_width; const p = try create(gpa, opts); errdefer p.deinit(); p.boot = Boot.of(opts); -- cgit v1.3