diff options
| author | Gabriel Schneider <[email protected]> | 2026-09-28 20:13:57 -0300 |
|---|---|---|
| committer | Gabriel Schneider <[email protected]> | 2026-10-01 00:12:15 -0300 |
| commit | 0acd945d85887da88f2863219e706000ca738cdd (patch) | |
| tree | db3c4a4b33ce67fc7f7e9e1f8dd96d74edb9fd93 /src | |
| parent | 54c14c2e65fe15b097bef11919d6ec8d9fce411a (diff) | |
| download | pardes-0acd945d85887da88f2863219e706000ca738cdd.tar.gz pardes-0acd945d85887da88f2863219e706000ca738cdd.zip | |
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 <[email protected]>
Diffstat (limited to 'src')
| -rw-r--r-- | src/CHANGELOG.md | 2 | ||||
| -rw-r--r-- | src/builtins.zig | 2 | ||||
| -rw-r--r-- | src/config.zig | 45 | ||||
| -rw-r--r-- | src/exec.zig | 6 | ||||
| -rw-r--r-- | src/mouse.zig | 3 | ||||
| -rw-r--r-- | src/ninep/ctl.zig | 4 | ||||
| -rw-r--r-- | src/pardes.zig | 18 |
7 files changed, 63 insertions, 17 deletions
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(); |
