summaryrefslogtreecommitdiff
path: root/src/pardes.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-15 17:06:27 -0300
committerGabriel Schneider <[email protected]>2026-09-15 17:24:42 -0300
commit6560da36cf8111575cd3fec443a42de6eff82f5f (patch)
tree5c977f487c705b4e801de840b9a7720ad8b7b18f /src/pardes.zig
parent9ce11bfeff73b96704e521b41cffe4625b02f103 (diff)
downloadpardes-6560da36cf8111575cd3fec443a42de6eff82f5f.tar.gz
pardes-6560da36cf8111575cd3fec443a42de6eff82f5f.zip
Cycle pane input modes with Mode and retain terminal tag access
Diffstat (limited to 'src/pardes.zig')
-rw-r--r--src/pardes.zig119
1 files changed, 80 insertions, 39 deletions
diff --git a/src/pardes.zig b/src/pardes.zig
index 90658589..91605d38 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -291,6 +291,7 @@ const pre_collapse_file_pane_tail = " Save Tty Del";
const pre_collapse_pdf_pane_tail = " Tty Del PdfSections PdfTint";
const pre_collapse_terminal_pane_tail = " Save Tty Del Togglettymode Filter";
const pre_tty_first_terminal_pane_tail = " Save Tty Del Togglettymode Filter Collapse";
+const pre_mode_terminal_pane_tail = " Tty Save Del Togglettymode Filter Collapse";
const Builtin = builtins.registry.Builtin();
@@ -1003,7 +1004,7 @@ test "pane-tag Exec prefers Tty and argument builtins before shell fallback" {
p.acknowledgeShell(0, "/bin/sh", false);
const pane = p.panes[0].?;
- try std.testing.expectEqualStrings(" Save Tty Del Togglettymode Filter Collapse", Pardes.curTail(pane));
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(pane));
const tag_x = p.rects[0].x + config.GUTTER;
const tag_y = p.rects[0].y;
@@ -1201,7 +1202,9 @@ test "Togglettymode builtin and keyboard chord share mode transitions including
const p = try Pardes.init(std.testing.allocator, .{ .cols = 120, .rows = 20, .tty_only = true });
defer p.deinit();
const pane = p.panes[0].?;
- try std.testing.expect(std.mem.indexOf(u8, Pardes.curTail(pane), " Togglettymode ") != null);
+ try std.testing.expect(std.mem.indexOf(u8, Pardes.curTail(pane), " Mode ") != null);
+ p.seedTail(pane);
+ try std.testing.expect(pane.appendTag(" Togglettymode"));
try std.testing.expectEqual(Pane.Mode.tty, pane.mode);
try std.testing.expect(p.executeBuiltinLine(0, "Togglettymode"));
try std.testing.expectEqual(Pane.Mode.normal, pane.mode);
@@ -1288,7 +1291,7 @@ test "compact tag commands stay beside their own prefix across layout changes" {
defer gpa.free(above_tag);
const below_tag = try p.tagText(p.scratch.allocator(), below);
try std.testing.expect(below_tag.len > above_tag.len);
- const tail = " Save Tty Del Togglettymode Filter Collapse";
+ const tail = terminal_pane_tail;
try std.testing.expectEqualStrings(tail, above_tag[above_tag.len - tail.len ..]);
try std.testing.expectEqual(above.cwdSlice().len + tail.len, above_tag.len);
@@ -1324,14 +1327,14 @@ test "legacy default tag tails upgrade while custom tails remain owned" {
});
p.restoreTail(terminal, terminal_old);
try std.testing.expect(!terminal.tag_init);
- try std.testing.expectEqualStrings(" Tty Save Del Togglettymode Filter Collapse", Pardes.curTail(terminal));
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(terminal));
const terminal_previous = try std.fmt.allocPrint(p.scratch.allocator(), "{s} New Del", .{
try p.tagPrefix(terminal),
});
p.restoreTail(terminal, terminal_previous);
try std.testing.expect(!terminal.tag_init);
- try std.testing.expectEqualStrings(" Tty Save Del Togglettymode Filter Collapse", Pardes.curTail(terminal));
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(terminal));
const terminal_prefilter = try std.fmt.allocPrint(p.scratch.allocator(), "{s}{s}", .{
try p.tagPrefix(terminal),
@@ -1339,8 +1342,21 @@ test "legacy default tag tails upgrade while custom tails remain owned" {
});
p.restoreTail(terminal, terminal_prefilter);
try std.testing.expect(!terminal.tag_init);
- try std.testing.expectEqualStrings(" Tty Save Del Togglettymode Filter Collapse", Pardes.curTail(terminal));
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(terminal));
+
+ for ([_][]const u8{ pre_mode_terminal_pane_tail, pre_tty_first_terminal_pane_tail, pre_collapse_terminal_pane_tail }) |old_tail| {
+ const old = try std.fmt.allocPrint(p.scratch.allocator(), "{s}{s}", .{ try p.tagPrefix(terminal), old_tail });
+ p.restoreTail(terminal, old);
+ try std.testing.expect(!terminal.tag_init);
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(terminal));
+ }
+ const custom_mode = try std.fmt.allocPrint(p.scratch.allocator(), "{s}{s} Keep", .{ try p.tagPrefix(terminal), pre_mode_terminal_pane_tail });
+ p.restoreTail(terminal, custom_mode);
+ try std.testing.expect(terminal.tag_init);
+ try std.testing.expectEqualStrings(pre_mode_terminal_pane_tail ++ " Keep", Pardes.curTail(terminal));
+ terminal.tag_tail_len = 0;
+ terminal.tag_init = false;
const terminal_custom = try std.fmt.allocPrint(p.scratch.allocator(), "{s} Keep Del", .{
try p.tagPrefix(terminal),
});
@@ -1368,7 +1384,7 @@ test "legacy default tag tails upgrade while custom tails remain owned" {
.terminal = .{ .cwd = "/historical/cwd" },
});
try std.testing.expect(!terminal.tag_init);
- try std.testing.expectEqualStrings(" Tty Save Del Togglettymode Filter Collapse", Pardes.curTail(terminal));
+ try std.testing.expectEqualStrings(terminal_pane_tail, Pardes.curTail(terminal));
// This happens to be the historical FILE default, but on a terminal it is
// user-owned text and must not be swallowed by migration.
@@ -2211,7 +2227,7 @@ test "Save on an output buffer writes its rows out and leaves the buffer alone"
defer gpa.free(rows);
// Save leads its tagline now, and the path is REQUIRED: a bare Save asks
- try std.testing.expectEqualStrings(" Save Tty Del Collapse", Pardes.curTail(out));
+ try std.testing.expectEqualStrings(file_pane_tail, Pardes.curTail(out));
try std.testing.expect(p.executeBuiltinLine(id, "Save"));
try std.testing.expect(out.prompt == .save);
try std.testing.expect(out.appendTag("help.txt"));
@@ -2399,7 +2415,7 @@ test "Save reaches every tagline with text behind it and no other" {
const out = try p.setTestFile("build.zig:1:1 pub fn main\n");
out.file.?.output = .{ .from = .search };
- try std.testing.expectEqualStrings(" Save Tty Del Collapse", Pardes.curTail(out));
+ try std.testing.expectEqualStrings(locations_pane_tail, Pardes.curTail(out));
// A dump written before Save reached output buffers carries the generic
// tail. That is a DEFAULT and not something typed, so it upgrades.
@@ -2409,7 +2425,7 @@ test "Save reaches every tagline with text behind it and no other" {
});
p.restoreTail(out, dumped);
try std.testing.expect(!out.tag_init);
- try std.testing.expectEqualStrings(" Save Tty Del Collapse", Pardes.curTail(out));
+ try std.testing.expectEqualStrings(locations_pane_tail, Pardes.curTail(out));
// A scratch is an output buffer that has worn the FILE tails all along,
// so its own older default upgrades from that family too.
@@ -7557,7 +7573,9 @@ pub const Pardes = struct {
const cmd = if (hit(key, config.look_key)) config.look_cmd else config.exec_cmd;
const txt = p.tagChordText(pane) orelse return;
const id = p.active;
+ const body_mode = pane.tag_mode;
exitTagEdit(pane); // leave the tag FIRST: Del frees the pane
+ pane.mode = body_mode;
p.runBuiltin(cmd, id, "", txt);
return;
}
@@ -8135,6 +8153,56 @@ pub const Pardes = struct {
return std.fmt.allocPrint(p.scratch.allocator(), "{s} {s}", .{ t, a }) catch txt;
}
+ /// Cycle only the body modes supported by this pane.
+ pub fn cycleMode(p: *Pardes, id: usize) void {
+ const pane = p.panes[id] orelse return;
+ if (pane.file == null and !pane.isTerminal()) return;
+ const previous = if (pane.tag_edit) pane.tag_mode else pane.mode;
+ p.active = id;
+ p.exitHeader();
+ if (pane.tag_edit) exitTagEdit(pane);
+ pane.mode = previous;
+ switch (previous) {
+ .tty => {
+ pane.mode = .normal;
+ pane.normal.clear();
+ },
+ .normal => p.enterInsert(pane, .at, 1),
+ .insert => {
+ p.exitInsert(pane);
+ if (pane.isTerminal()) panes.Terminal.enterTty(p, id);
+ },
+ }
+ }
+
+ fn exitInsert(p: *Pardes, pane: *Pane) void {
+ pane.mode = .normal;
+ pane.msel.active = false;
+ pane.normal.clear();
+ if (pane.append_at) |aa| {
+ pane.append_at = null;
+ const text = p.flatSurface(pane) catch return;
+ const gap = modal.offsetAt(text, .{ .row = @intCast(@max(0, pane.cur_row)), .col = @intCast(@max(0, pane.cur_col)) });
+ const a_off = modal.offsetAt(text, .{ .row = @intCast(@max(0, aa.row)), .col = @intCast(@max(0, aa.col)) });
+ const back = @max(a_off, modal.prevGrapheme(text, gap));
+ const bc = modal.positionAt(text, back);
+ pane.cur_row = @intCast(bc.row);
+ pane.cur_col = @intCast(bc.col);
+ pane.vsel = .{ .active = a_off != back, .row = aa.row, .col = aa.col, .explicit = false };
+ pane.cur_pinned = true;
+ pane.ensureCursorVisible();
+ for (pane.sels[0..pane.nsel]) |*s| {
+ const sgap = modal.offsetAt(text, .{ .row = @intCast(@max(0, s.row)), .col = @intCast(@max(0, s.col)) });
+ const b2 = if (back == gap) sgap else modal.prevGrapheme(text, sgap);
+ const bp = modal.positionAt(text, b2);
+ s.row = @intCast(bp.row);
+ s.col = @intCast(bp.col);
+ s.arow = s.row;
+ s.acol = s.col;
+ }
+ }
+ }
+
/// One mode transition for the terminal tag command and the Ctrl-B chord.
pub fn toggleTty(p: *Pardes, id: usize) void {
const pane = p.panes[id] orelse return;
@@ -8258,34 +8326,7 @@ pub const Pardes = struct {
p.handleNormal(pane, key);
},
.insert => {
- if (hit(key, config.escape)) {
- pane.mode = .normal;
- pane.msel.active = false;
- pane.normal.clear();
- if (pane.append_at) |aa| {
- pane.append_at = null;
- const text = p.flatSurface(pane) catch return;
- const gap = modal.offsetAt(text, .{ .row = @intCast(@max(0, pane.cur_row)), .col = @intCast(@max(0, pane.cur_col)) });
- const a_off = modal.offsetAt(text, .{ .row = @intCast(@max(0, aa.row)), .col = @intCast(@max(0, aa.col)) });
- const back = @max(a_off, modal.prevGrapheme(text, gap));
- const bc = modal.positionAt(text, back);
- pane.cur_row = @intCast(bc.row);
- pane.cur_col = @intCast(bc.col);
- pane.vsel = .{ .active = a_off != back, .row = aa.row, .col = aa.col, .explicit = false };
- pane.cur_pinned = true;
- pane.ensureCursorVisible();
- for (pane.sels[0..pane.nsel]) |*s| {
- const sgap = modal.offsetAt(text, .{ .row = @intCast(@max(0, s.row)), .col = @intCast(@max(0, s.col)) });
- const b2 = if (back == gap) sgap else modal.prevGrapheme(text, sgap);
- const bp = modal.positionAt(text, b2);
- s.row = @intCast(bp.row);
- s.col = @intCast(bp.col);
- s.arow = s.row;
- s.acol = s.col;
- }
- }
- return;
- }
+ if (hit(key, config.escape)) return p.exitInsert(pane);
p.handleInsert(pane, key);
},
.tty => panes.Terminal.forwardKey(p, p.active, key),
@@ -12813,7 +12854,7 @@ pub const Pardes = struct {
pre_collapse_file_pane_tail,
pre_collapse_pane_tail,
},
- .terminal => &.{ terminal_pane_tail, pre_tty_first_terminal_pane_tail, pre_collapse_terminal_pane_tail, pre_column_terminal_pane_tail, pre_tty_terminal_pane_tail, prev_terminal_pane_tail, legacy_terminal_pane_tail, pane_tail, pre_collapse_pane_tail, pre_column_pane_tail, prev_pane_tail, legacy_pane_tail },
+ .terminal => &.{ terminal_pane_tail, pre_mode_terminal_pane_tail, pre_tty_first_terminal_pane_tail, pre_collapse_terminal_pane_tail, pre_column_terminal_pane_tail, pre_tty_terminal_pane_tail, prev_terminal_pane_tail, legacy_terminal_pane_tail, pane_tail, pre_collapse_pane_tail, pre_column_pane_tail, prev_pane_tail, legacy_pane_tail },
};
for (defaults) |default|
if (std.mem.eql(u8, said, std.mem.trimStart(u8, default, " "))) return;