From b8c5e8772e646fa21f6afdd2a875a1bd52848bd5 Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 18:53:37 -0300 Subject: A column can be empty, as acme's can; new panes go where acme's makenewwindow puts them Newcol makes an empty column; closing a column's last pane leaves it empty with the keyboard on its tag; Delcol and Joincol alone take a column away; the session's last pane closing quits. The +New stand-in and replaceStillborn / stillborn_joiner are gone. Every new pane goes through exec.placeNew, which follows acme's makenewwindow (active column, empty column whole, blank space, halving the biggest) or, with Placement pardes, the old rules. ColumnTags is gone: column tags are always shown. A grip drag shows acme's box cursor. Co-Authored-By: Claude Opus 5.5 --- docs/tags.md | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 4 deletions(-) (limited to 'docs/tags.md') diff --git a/docs/tags.md b/docs/tags.md index fdea1472..7787d7f1 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -1,8 +1,9 @@ # Editable tags Pardes has three levels of command text: the workspace tag, one tag per -column, and each pane's tag. Column commands run in that column's active -pane (or its first pane when focus comes from another column). This makes +column, and each pane's tag. Column commands act on that column and run in +its active pane (or its first pane when focus comes from another column); a +column with no pane is described under [empty columns](#empty-columns). This makes `New`, `Tty`, `Find`, and `Grep` available beside the work they act on. `New` appears only in the column tag by default; pane tags keep their own save, terminal, close, and collapse commands. `Tty` opens a new embedded terminal. @@ -152,7 +153,7 @@ upgrade to `Mode`; customized command text is preserved. ## Saved workspaces `Dump` and `Restore` preserve customized workspace and column tags, including -intentionally empty tags. New columns start with the standard column tag. +intentionally empty tags, and columns that hold no pane. New columns start with the standard column tag. Closing a column keeps surviving columns' tags; `Joincol` keeps the destination column's tag. Old dumps without these optional fields retain the defaults. The automatic `Restore` shortcut does not overwrite a customized workspace @@ -163,7 +164,66 @@ than three rows it is omitted so a pane still has room. SDL and TTY share the same tag text, editing and layout; SDL additionally uses compact font sizing and subtle pixel separators. -`ColumnTags` toggles the column row (on by default) without deleting its text. +The column row is always shown. An old `ColumnTags` line in an init file is +ignored, with a message saying so. `FocusTint` controls active-column and active-pane emphasis. SDL also honors the shared bold, underline, and strikethrough attributes, including diagnostic underlines and the optional `SyntaxBold` keyword weight. + +## Empty columns + +A column can hold no pane, as acme's can: its tag stands over blank space, the +frame's own fill, where acme paints white (cols.c:186-188). `Newcol` makes an +empty column right of the keyboard's and gives its tag the keyboard. Closing a +column's last pane (`Del`, `Del k`/`Del j`, a shell exiting, a drag to another +column) leaves the column empty where it was, and the keyboard goes to its tag +if it was on that pane. Only `Delcol` and `Joincol` take a column away. + +`Delcol` and `Joincol` from a column's tag act on that column; `Delcol` +written to a pane's ctl closes that pane's column. A pane dragged onto an +empty column fills it. + +## Where new panes go + +Every new pane goes through one placement, chosen by the `Placement` setting: +`acme` (the default) or `pardes`. `Placement pardes` or `Placement acme` sets +it, in an init file, a tag or the root ctl; bare `Placement` flips it; `SPC c +p` is its leader path, and `Config` reports it. + +`Placement acme` is acme's makenewwindow (util.c:449-495). The core keeps +acme's *active column* (activecol, dat.c:37): the column last typed in +(acme.c:487), clicked in with the select button (acme.c:659), dropped into by +a grip (acme.c:640), whose tag was given the keyboard (`Newcol`, an emptied +column, `Ctrl-w k`), or that was given the last new pane (util.c:467). A Look +click moves the keyboard but not the active column, as button 3 does not in +acme. A new pane goes into the column a command's tag belongs to when it came +from a column tag, else the active column, else the keyboard's pane's, and +never into a new column: + +- an empty column it takes whole (util.c:468-469); +- from a tag, or 9P's `pane/new` (acme's `t->w == nil`), it takes the bottom + half of the column's last pane (coladd, cols.c:62-65); +- from a pane's text (a Look, `Tty`, `Alt-n`, a Grep or Find listing), it goes + right under the text of the pane with the most blank rows when that is more + than 15 rows, or more than 3 and more than half the biggest pane + (util.c:482-486); otherwise it halves the biggest pane, or the asking pane + when that is in the column and not much smaller (util.c:487-491); +- `New` goes into its own column, the bottom half of its last pane + (look.c:921-923); +- a command pane or a `+Errors` pane goes to the last column, the bottom + half of its last pane (util.c:94-98). + +`Placement pardes` is what pardes did before: an empty column whose tag asked, +or has the keyboard, is filled; a scratch goes right under the pane that asked; +a shell under it or the nearest pane with room; a document beside the last one +read, or in a column of its own on the left when there is none and the column +is at least 200 cells wide; a command pane at the foot of the last column. + +Down from an empty column's tag stays there, and the pane-to-pane keys pass +over an empty column; Left and Right from a tag walk every column's tag. + +One divergence from acme: acme keeps running when its last window closes, +every column empty. Pardes quits when the session's last pane closes, as +`Delcol` of the last column always has. A key, a prompt or a command in +pardes runs in a pane, so a session without one would have nothing to run +them in. -- cgit v1.3 From 54c14c2e65fe15b097bef11919d6ec8d9fce411a Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 20:11:51 -0300 Subject: An empty column shows its theme's empty_col: white in the acme theme, as acme paints it Co-Authored-By: Claude Opus 5.5 --- docs/config.md | 2 +- docs/tags.md | 5 +++-- docs/themes.md | 1 + src/colors.zig | 4 ++++ src/draw.zig | 1 + src/pardes.zig | 19 +++++++++++++++++++ src/themes/acme.zig | 2 ++ test/gui-goldens.txt | 2 +- 8 files changed, 32 insertions(+), 4 deletions(-) (limited to 'docs/tags.md') diff --git a/docs/config.md b/docs/config.md index 77ed68e7..c2e0b318 100644 --- a/docs/config.md +++ b/docs/config.md @@ -316,7 +316,7 @@ name, thirteen required RGB roles (`sel_bg`, `sel_fg`, `tag_bg`, `tag_fg`, `box`, `box_dim`, `kw`, `str`, `num`, `comment`, `lineno`, `scroll_track`, `scroll_thumb`), nullable `bg`/`fg`, and either a 16-color RGB `palette` or `null`. Optional nullable RGB roles extend this format: -`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `lineno_active`, `search_bg`, `search_fg`, +`tag_active_bg`, `tag_active_fg`, `tag_name_fg`, `tag_active_name_fg`, `border`, `empty_col`, `lineno_active`, `search_bg`, `search_fg`, `diagnostic_error`, `diagnostic_warning`, `diagnostic_info`, and `diagnostic_hint`. Missing new roles use backward-compatible defaults, so previously exported files remain valid. [Theme customization](themes.md) diff --git a/docs/tags.md b/docs/tags.md index 7787d7f1..26f3dcf0 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -172,8 +172,9 @@ underlines and the optional `SyntaxBold` keyword weight. ## Empty columns -A column can hold no pane, as acme's can: its tag stands over blank space, the -frame's own fill, where acme paints white (cols.c:186-188). `Newcol` makes an +A column can hold no pane, as acme's can: its tag stands over blank space in +the theme's `empty_col` colour, white in the acme theme as acme paints it +(cols.c:186-188), and the frame's border fill in themes that do not set it. `Newcol` makes an empty column right of the keyboard's and gives its tag the keyboard. Closing a column's last pane (`Del`, `Del k`/`Del j`, a shell exiting, a drag to another column) leaves the column empty where it was, and the keyboard goes to its tag diff --git a/docs/themes.md b/docs/themes.md index 8cc4ff9b..103a3f5a 100644 --- a/docs/themes.md +++ b/docs/themes.md @@ -108,6 +108,7 @@ so existing exported themes remain valid. | `column_box` | Column grip while held, and its drag rail | `num` | | `column_box_dim` | Column drag grip at rest, in every focus state | Equal mix of `column_box` and `tag_bg` | | `border` | Quiet separators | `scroll_track` | +| `empty_col` | A column with no pane, under its tag (acme: white) | `border` | | `lineno_active` | Restrained current line number foreground | `lineno` | | `search_bg`, `search_fg` | Search matches, independent of selection | `sel_bg`, `sel_fg` | | `diagnostic_error` | Error text | `fg`, or `tag_fg` for an inherited foreground | diff --git a/src/colors.zig b/src/colors.zig index aeff7b00..401b77f1 100644 --- a/src/colors.zig +++ b/src/colors.zig @@ -54,6 +54,8 @@ pub const Theme = struct { column_box: ?[3]u8 = null, column_box_dim: ?[3]u8 = null, border: ?[3]u8 = null, + /// What an empty column shows under its tag; the border fill without it. + empty_col: ?[3]u8 = null, search_bg: ?[3]u8 = null, search_fg: ?[3]u8 = null, diagnostic_error: ?[3]u8 = null, @@ -144,6 +146,7 @@ pub const ChromeTheme = struct { tag_name_fg: [3]u8, tag_active_name_fg: [3]u8, border: [3]u8, + empty_col: [3]u8, search_bg: [3]u8, search_fg: [3]u8, diagnostic_error: [3]u8, @@ -167,6 +170,7 @@ pub const ChromeTheme = struct { .tag_name_fg = th.tag_name_fg orelse th.tag_fg, .tag_active_name_fg = th.tag_active_name_fg orelse (th.tag_name_fg orelse (th.tag_active_fg orelse th.tag_fg)), .border = th.border orelse th.scroll_track, + .empty_col = th.empty_col orelse (th.border orelse th.scroll_track), .search_bg = th.search_bg orelse th.sel_bg, .search_fg = th.search_fg orelse th.sel_fg, .diagnostic_error = th.diagnostic_error orelse (th.fg orelse th.tag_fg), diff --git a/src/draw.zig b/src/draw.zig index b12ab52c..8f2f95f9 100644 --- a/src/draw.zig +++ b/src/draw.zig @@ -189,6 +189,7 @@ pub fn render(p: *Pardes, arena: std.mem.Allocator) !*Surface { // Separators have their own role; a scrollbar need not frame the page. const whole = s.regions[0].rect; s.fill(whole.x, whole.y, whole.w, whole.h, .{ .bg = .{ .rgb = chrome.border } }); + for (0..p.ncol) |c| if (p.col_n[c] == 0) s.fill(p.col_x[c], p.topBarHeight() + p.columnBarHeight(), p.col_w[c], s.rows -| (p.topBarHeight() + p.columnBarHeight()), .{ .bg = .{ .rgb = chrome.empty_col } }); for (&p.panes, 0..) |*slot, id| { const pane = slot.* orelse continue; diff --git a/src/pardes.zig b/src/pardes.zig index bad512a9..e3f4bb60 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1317,6 +1317,25 @@ test "Placement pardes brings back the old rules, and says so in Config" { try std.testing.expectEqual(config.Runtime.Placement.acme, p.settings.placement); } +test "an empty column shows its theme's empty_col under its tag: acme's white, else the border" { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 120, .rows = 20 }); + defer p.deinit(); + p.newColumn(0); + tagline.exitHeader(p); + inline for (.{ "acme", "dark" }) |name| { + try std.testing.expect(p.executeBuiltinLine(0, "Theme " ++ name)); + p.chrome_animation.displayed = colors.ChromeTheme.fromTheme(p.theme()); + p.sync(); + var arena: std.heap.ArenaAllocator = .init(std.testing.allocator); + defer arena.deinit(); + const s = try p.render(arena.allocator()); + const cell = s.at(p.col_x[1] + 3, p.screen_h - 2); + const want = p.chromeTheme().empty_col; + try std.testing.expectEqual(want, cell.style.bg.rgb); + if (std.mem.eql(u8, name, "acme")) try std.testing.expectEqual([3]u8{ 0xff, 0xff, 0xff }, want) else try std.testing.expectEqual(p.chromeTheme().border, want); + } +} + test "dragging a column's only pane to another leaves that column in place, empty" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 32 }); defer p.deinit(); diff --git a/src/themes/acme.zig b/src/themes/acme.zig index d91e626b..693055d6 100644 --- a/src/themes/acme.zig +++ b/src/themes/acme.zig @@ -27,6 +27,8 @@ pub const theme = .{ .lineno = .{ 0x99, 0x99, 0x80 }, .scroll_track = .{ 0x99, 0x99, 0x4c }, .scroll_thumb = .{ 0xff, 0xff, 0xea }, + // acme paints a column with no window white (cols.c:187). + .empty_col = .{ 0xff, 0xff, 0xff }, .palette = .{ .{ 0x00, 0x00, 0x00 }, .{ 0xaa, 0x22, 0x22 }, .{ 0x22, 0x80, 0x22 }, .{ 0x88, 0x66, 0x00 }, .{ 0x22, 0x44, 0xbb }, .{ 0x99, 0x33, 0x99 }, .{ 0x11, 0x88, 0x88 }, .{ 0x55, 0x55, 0x55 }, diff --git a/test/gui-goldens.txt b/test/gui-goldens.txt index 78639bfc..9a8baf9c 100644 --- a/test/gui-goldens.txt +++ b/test/gui-goldens.txt @@ -14,4 +14,4 @@ 15-theme-acme-light cdd8f24e67ed9940c4af3eaa5d54eb71c4fb0efb8fe48ce12368b72bbca90003 16-debug f6b3b93bc6ca178f1762b7f77857586861e105c51eb6202d519f7605fed523d8 17-terminal ea0d877ba228515f0ab7c10097486bf09318762c6e475c481fdbf478939d3a1b -18-mid-transition 6100b892b4654594f9eb09fd105ce9639e99f7d145fee2f9c9986825d936785d +18-mid-transition 9c84c5e27a7ecb063919aaef7a8d8a477e6b5b41909938f233b6cba859f8affc -- cgit v1.3 From 0acd945d85887da88f2863219e706000ca738cdd Mon Sep 17 00:00:00 2001 From: Gabriel Schneider Date: Mon, 28 Sep 2026 20:13:57 -0300 Subject: A document dragged beside the boot shell leaves it; BootShell replace closes it as before Placement and BootShell are one kind of setting now, a word from an enum, set, flipped bare, and reported by one arm. Co-Authored-By: Claude Opus 5.5 --- docs/config.md | 5 +++++ docs/tags.md | 4 ++++ src/CHANGELOG.md | 2 ++ src/builtins.zig | 2 +- src/config.zig | 45 +++++++++++++++++++++++++++++++++------------ src/exec.zig | 6 ++++-- src/mouse.zig | 3 ++- src/ninep/ctl.zig | 4 +++- src/pardes.zig | 18 ++++++++++++++++++ 9 files changed, 72 insertions(+), 17 deletions(-) (limited to 'docs/tags.md') diff --git a/docs/config.md b/docs/config.md index c2e0b318..6905446a 100644 --- a/docs/config.md +++ b/docs/config.md @@ -116,6 +116,11 @@ ignored with a message. `Placement acme` (the default) puts new panes where acme would, in the active column; `Placement pardes` brings back pardes's own rules, which open a first document in a column of its own. See [where new panes go](tags.md#where-new-panes-go). +`BootShell keep` (the default) leaves a shell alone when a document is dragged +into the left column beside it, whatever it holds; `BootShell replace` closes +such a shell there when it is the column's only one and nobody has typed +into it (no scrollback, cursor still on the first prompt line), the boot's +placeholder giving its rows to the document. Bare `BootShell` flips it. `Verbose` toggles the message-row announcement every builtin makes of its own name before it runs; it is on by default, and the builtins that own the message row themselves (`Msg`) never announce. Turning it off leaves the row to the diff --git a/docs/tags.md b/docs/tags.md index 26f3dcf0..b8299f22 100644 --- a/docs/tags.md +++ b/docs/tags.md @@ -220,6 +220,10 @@ a shell under it or the nearest pane with room; a document beside the last one read, or in a column of its own on the left when there is none and the column is at least 200 cells wide; a command pane at the foot of the last column. +`BootShell replace` brings back one more placeholder: a document dragged into +the left column closes the column's lone shell if nobody has typed into it. +`BootShell keep`, the default, never closes a pane for another one. + Down from an empty column's tag stays there, and the pane-to-pane keys pass over an empty column; Left and Right from a tag walk every column's tag. diff --git a/src/CHANGELOG.md b/src/CHANGELOG.md index 874082d8..142287b2 100644 --- a/src/CHANGELOG.md +++ b/src/CHANGELOG.md @@ -13,6 +13,8 @@ the text of a pane with room to spare, else halving the biggest pane; never into a new column. `Placement pardes` brings back the old rules, which open a first document in a column of its own. +- A document dragged into the left column no longer closes the untouched + boot shell there; `BootShell replace` brings that back. - Column tags are always shown: the `ColumnTags` setting is gone, and an init file's `ColumnTags` line is ignored with a message. - Carrying a pane or a column by its grip shows acme's box cursor in the SDL diff --git a/src/builtins.zig b/src/builtins.zig index c2b4d62f..7b34b376 100644 --- a/src/builtins.zig +++ b/src/builtins.zig @@ -169,7 +169,7 @@ pub const registry = struct { inline for (comptime settingList(), manualBuiltinCount()..) |setting, i| if (@intFromEnum(b) == i) return switch (setting.action) { // a switch flips bare, and DumpDir bare is the default - .toggle, .transition, .scene, .dump_dir, .placement => false, + .toggle, .transition, .scene, .dump_dir, .choice => false, .shell, .theme, .font, .tagline_size, .window_opacity, .window_blur, .message_ms, .shader, .shader_animation => true, }; unreachable; diff --git a/src/config.zig b/src/config.zig index 48f49d50..01a5daf8 100644 --- a/src/config.zig +++ b/src/config.zig @@ -74,6 +74,7 @@ pub const leader_path = paths: { .Delcol = "cd", .Joincol = "cj", .Placement = "cp", + .BootShell = null, .Debug = "td", .Msg = null, .Colors = "tc", @@ -666,9 +667,16 @@ pub const Runtime = struct { /// Where a new pane goes (exec.placeNew): `acme`, acme's makenewwindow, /// or `pardes`, the rules pardes had before it. placement: Placement = .acme, + /// A document dragged into the left column where a lone shell nobody + /// has typed into stands: `keep` leaves the shell, `replace` closes it + /// (exec.evictLonePristineTty). + boot_shell: BootShell = .keep, pub const ShaderAnimation = enum { off, on, always }; pub const Placement = enum { acme, pardes }; + pub const BootShell = enum { keep, replace }; + /// A setting that is one of a few words, each an enum field of Runtime. + pub const Choice = enum { placement, boot_shell }; /// Each pass the bundled Crt or a Shadertoy file. pub const Post = struct { @@ -841,7 +849,7 @@ pub const Runtime = struct { /// it of files. shader, shader_animation, - placement, + choice: Choice, }; pub const Setting = struct { @@ -893,7 +901,8 @@ pub const Runtime = struct { .{ .word = "Crt", .action = .{ .scene = .crt }, .availability = .scene_shaders }, .{ .word = "Shader", .action = .shader, .availability = .scene_shaders }, .{ .word = "ShaderAnimation", .action = .shader_animation, .availability = .scene_shaders }, - .{ .word = "Placement", .action = .placement }, + .{ .word = "Placement", .action = .{ .choice = .placement } }, + .{ .word = "BootShell", .action = .{ .choice = .boot_shell } }, }; pub fn find(name: []const u8) ?Setting { @@ -1052,13 +1061,17 @@ pub const Runtime = struct { const text = std.mem.trim(u8, argument orelse return false, " \t\r\n"); state.shader_animation = std.meta.stringToEnum(ShaderAnimation, text) orelse return false; }, - // Bare, it flips between the two, as a tag word should. - .placement => { - const text = std.mem.trim(u8, argument orelse "", " \t\r\n"); - state.placement = if (text.len == 0) - (if (state.placement == .acme) .pardes else .acme) - else - std.meta.stringToEnum(Placement, text) orelse return false; + // Bare, it steps to the next word, as a tag word should. + .choice => |which| switch (which) { + inline else => |field| { + const now = &@field(state, @tagName(field)); + const T = @TypeOf(now.*); + const text = std.mem.trim(u8, argument orelse "", " \t\r\n"); + now.* = if (text.len == 0) + @enumFromInt((@as(usize, @intFromEnum(now.*)) + 1) % std.enums.values(T).len) + else + std.meta.stringToEnum(T, text) orelse return false; + }, }, .theme, .font => return false, } @@ -1229,7 +1242,9 @@ pub const Runtime = struct { try out.print("{s} {s}\n", .{ setting.word, @tagName(state.shader_animation) }) else try out.print("{s} unsupported\n", .{setting.word}), - .placement => try out.print("{s} {s}\n", .{ setting.word, @tagName(state.placement) }), + .choice => |which| switch (which) { + inline else => |field| try out.print("{s} {s}\n", .{ setting.word, @tagName(@field(state, @tagName(field))) }), + }, }; if (context.startup_config_path) |path| @@ -1481,7 +1496,10 @@ pub const Runtime = struct { .dump_dir => "~/dumps", .shader => "~/crt.glsl", .shader_animation => "always", - .placement => "pardes", + .choice => |which| switch (which) { + .placement => "pardes", + .boot_shell => "replace", + }, else => null, }; try std.testing.expect(apply(&state, setting, argument)); @@ -1531,7 +1549,10 @@ pub const Runtime = struct { }, .shader => "Shader ~/crt.glsl\n", .shader_animation => "ShaderAnimation always\n", - .placement => "Placement pardes\n", + .choice => |which| switch (which) { + .placement => "Placement pardes\n", + .boot_shell => "BootShell replace\n", + }, .theme, .font => unreachable, }; try std.testing.expect(std.mem.indexOf(u8, report, expected) != null); diff --git a/src/exec.zig b/src/exec.zig index 117970d9..22aae10a 100644 --- a/src/exec.zig +++ b/src/exec.zig @@ -326,9 +326,11 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane { return nt; } -/// when a doc lands in `col`, a lone pristine shell there is clutter — drop -/// it; absorbVWeight hands its space to the doc +/// With `BootShell replace`, a doc landing in `col` closes a lone pristine +/// shell there, the boot's placeholder, and absorbVWeight hands its space to +/// the doc. With `keep`, the default, the shell stays like any other pane. pub fn evictLonePristineTty(p: *Pardes, col: usize, keep_id: usize) void { + if (p.settings.boot_shell == .keep) return; var n_tty: usize = 0; var tty_id: usize = 0; for (0..p.col_n[col]) |k| { diff --git a/src/mouse.zig b/src/mouse.zig index 5f0a6c4b..ac72481a 100644 --- a/src/mouse.zig +++ b/src/mouse.zig @@ -788,7 +788,8 @@ fn dragRelease(p: *Pardes, button: Mouse.Button) void { .move => |d| { layout.movePane(p, d.id, d.cur_x, d.cur_y); if (layout.findPane(p, d.id)) |f| p.setActiveColumn(f.col); - // a file moved into the left column evicts a lone unused shell + // BootShell replace: a file moved into the left column closes a + // lone unused shell there if (p.panes[d.id]) |mt| if (mt.file != null) { if (layout.findPane(p, d.id)) |f| if (f.col == 0) exec.evictLonePristineTty(p, 0, d.id); diff --git a/src/ninep/ctl.zig b/src/ninep/ctl.zig index b4ae7231..23d966e8 100644 --- a/src/ninep/ctl.zig +++ b/src/ninep/ctl.zig @@ -308,7 +308,9 @@ fn writeSettings(p: *Pardes, w: *std.Io.Writer) !void { .tagline_size => try w.print("{s} {d}\n", .{ word, s.font.tagline_percent }), .window_opacity => try w.print("{s} {d}\n", .{ word, s.window_opacity }), .window_blur => try w.print("{s} {d}\n", .{ word, s.window_blur }), - .placement => try w.print("{s} {s}\n", .{ word, @tagName(s.placement) }), + .choice => |which| switch (which) { + inline else => |field| try w.print("{s} {s}\n", .{ word, @tagName(@field(s, @tagName(field))) }), + }, .message_ms => |which| try w.print("{s} {d}\n", .{ word, @field(s, "message_" ++ @tagName(which) ++ "_ms") }), // Bare, it names the default directory. .dump_dir => if (s.dump_dir.len > 0) try w.print("{s} {s}\n", .{ word, s.dump_dir.get() }) else try w.print("{s}\n", .{word}), diff --git a/src/pardes.zig b/src/pardes.zig index e3f4bb60..23341904 100644 --- a/src/pardes.zig +++ b/src/pardes.zig @@ -1336,6 +1336,24 @@ test "an empty column shows its theme's empty_col under its tag: acme's white, e } } +test "BootShell keep leaves the untouched boot shell a dragged document joins; replace closes it" { + inline for (.{ "keep", "replace" }) |word| { + const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 100, .rows = 30 }); + defer p.deinit(); + const shell = p.active; + try std.testing.expect(p.panes[shell].?.isTerminal()); + try std.testing.expect(p.executeBuiltinLine(shell, "BootShell " ++ word)); + _ = try p.openTutorView(1); + layout.insert(p, 0, 1, 1); + p.sync(); + exec.evictLonePristineTty(p, 0, 1); + p.sync(); + const kept = std.mem.eql(u8, word, "keep"); + try std.testing.expectEqual(kept, p.panes[shell] != null); + try std.testing.expectEqual(@as(usize, if (kept) 2 else 1), p.col_n[0]); + } +} + test "dragging a column's only pane to another leaves that column in place, empty" { const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true, .cols = 160, .rows = 32 }); defer p.deinit(); -- cgit v1.3