summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-09-30 23:58:54 -0300
committerGabriel Schneider <[email protected]>2026-10-01 00:12:18 -0300
commit3815a0bfad5818e2caf85a00c0c2d3aecc13bd1c (patch)
treedfb1920c13ac0e113d9294c96f86d36ecf95bbc9 /src/host_io.zig
parent03ace1893d1a4b509c209e4e8ca023f7261e5cb9 (diff)
downloadpardes-3815a0bfad5818e2caf85a00c0c2d3aecc13bd1c.tar.gz
pardes-3815a0bfad5818e2caf85a00c0c2d3aecc13bd1c.zip
pardes - reads stdin into a +Pager pane, and is a terminal's PAGER and GIT_PAGER, Pager pardes|off to opt out; command panes page through cat
A paging command in a terminal, or run through its pty/run, sat in less, and a pty/run never answered. `pardes -` now reads stdin to its end, strips terminal escapes (colour, OSC, overstrike), and shows it: inside a session as a clean <cwd>/+Pager pane made through pane/new and back at once, outside one as a new editor whose first pane it is, as `vim -` does. A terminal's shell gets PAGER and GIT_PAGER set to this pardes's own path and `-` where the user's environment sets neither; `Pager off`, a setting in /ctl, init and DumpConfig, leaves terminals started after it to the environment. A command pane's one-shot shell keeps cat, its output being a pane already. Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig66
1 files changed, 58 insertions, 8 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index 8ddb0c90..4ebac9ee 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -1255,7 +1255,12 @@ pub const ChildEnv = struct {
return line[0 .. std.mem.indexOfScalar(u8, line, '=') orelse line.len];
}
- pub fn build(slots: *Slots) ?[*:null]const ?[*:0]const u8 {
+ /// Which pager a child is given where the user's environment names none:
+ /// a command pane's one-shot shell `cat` (its output is a pane already),
+ /// a terminal's shell `pardes -` (a +Pager pane) unless `Pager off`.
+ pub const Paging = enum { command, terminal, terminal_off };
+
+ pub fn build(slots: *Slots, paging: Paging) ?[*:null]const ?[*:0]const u8 {
var n: usize = 0;
var i: usize = 0;
while (libc.environ[i]) |entry| : (i += 1) {
@@ -1268,9 +1273,15 @@ pub const ChildEnv = struct {
slots[n] = entry;
n += 1;
}
- // No pager to wait in: a pty/run or a command pane's output is read
- // whole, and less would hold it at its first screen for ever. Only
- // where the inherited environment chose none.
+ // No pager to wait in (less holds a run at its first screen for
+ // ever): a command pane's output is read whole, through cat; a
+ // terminal's goes to a +Pager pane. Only where the inherited
+ // environment chose none.
+ const defaults: []const [*:0]const u8 = switch (paging) {
+ .command => &cat_pagers,
+ .terminal => pardesPagers(),
+ .terminal_off => &.{},
+ };
for (defaults) |default| {
const set = for (libc.environ[0..i]) |entry| {
if (std.mem.eql(u8, name(entry.?), name(default))) break true;
@@ -1288,8 +1299,27 @@ pub const ChildEnv = struct {
return @ptrCast(slots);
}
- /// Set for a pane's child only when the user's environment has none.
- const defaults = [_][*:0]const u8{ "PAGER=cat", "GIT_PAGER=cat" };
+ const cat_pagers = [_][*:0]const u8{ "PAGER=cat", "GIT_PAGER=cat" };
+
+ /// `PAGER='<this pardes> -'`, its own path so no PATH lookup can find
+ /// another; plain `pardes -` where the path cannot be read or quoted.
+ fn pardesPagers() []const [*:0]const u8 {
+ const S = struct {
+ var pager: [std.fs.max_path_bytes + 16:0]u8 = undefined;
+ var git_pager: [std.fs.max_path_bytes + 16:0]u8 = undefined;
+ var entries: [2][*:0]const u8 = undefined;
+ var ready = false;
+ };
+ if (!S.ready) {
+ var exe_buf: [std.fs.max_path_bytes]u8 = undefined;
+ const n = if (comptime builtin.os.tag == .linux) libc.readlink("/proc/self/exe", &exe_buf, exe_buf.len) else -1;
+ const exe: []const u8 = if (n > 0 and @as(usize, @intCast(n)) < exe_buf.len and std.mem.indexOfScalar(u8, exe_buf[0..@intCast(n)], '\'') == null) exe_buf[0..@intCast(n)] else "pardes";
+ S.entries[0] = if (std.fmt.bufPrintSentinel(&S.pager, "PAGER='{s}' -", .{exe}, 0)) |t| t.ptr else |_| "PAGER=pardes -";
+ S.entries[1] = if (std.fmt.bufPrintSentinel(&S.git_pager, "GIT_PAGER='{s}' -", .{exe}, 0)) |t| t.ptr else |_| "GIT_PAGER=pardes -";
+ S.ready = true;
+ }
+ return &S.entries;
+ }
};
test "the child environment replaces the launcher's terminal identity exactly once" {
@@ -1314,7 +1344,24 @@ test "the child environment replaces the launcher's terminal identity exactly on
defer _ = unsetenv("PARDES_CHILD_ENV_PROBE");
var slots: ChildEnv.Slots = undefined;
- const envp = ChildEnv.build(&slots) orelse return error.EnvironmentTooLarge;
+ // Where the user named no pager: a terminal's shell pages into a
+ // +Pager pane (`pardes -`), or, Pager off, as the environment says; a
+ // command pane's through cat. A pager the user named is kept.
+ for ([_]ChildEnv.Paging{ .terminal, .terminal_off }) |paging| {
+ var shell_slots: ChildEnv.Slots = undefined;
+ const shell_envp = ChildEnv.build(&shell_slots, paging) orelse return error.EnvironmentTooLarge;
+ var said: usize = 0;
+ var j: usize = 0;
+ while (shell_envp[j]) |entry| : (j += 1) {
+ const line = std.mem.span(entry);
+ if (std.mem.startsWith(u8, line, "PAGER=") and std.c.getenv("PAGER") == null) {
+ try std.testing.expect(paging == .terminal and std.mem.endsWith(u8, line, " -"));
+ said += 1;
+ }
+ }
+ if (std.c.getenv("PAGER") == null) try std.testing.expectEqual(@as(usize, if (paging == .terminal) 1 else 0), said);
+ }
+ const envp = ChildEnv.build(&slots, .command) orelse return error.EnvironmentTooLarge;
var terms: usize = 0;
var colorterms: usize = 0;
var probes: usize = 0;
@@ -1442,7 +1489,10 @@ pub fn forkShell(
// not call setenv, whose malloc can deadlock against a pty reader thread
// that held the heap when the fork took its snapshot.
var env_slots: ChildEnv.Slots = undefined;
- const envp = ChildEnv.build(&env_slots);
+ const paging: ChildEnv.Paging = if (one_line != null)
+ .command
+ else if (core) |c| (if (c.settings.pager == .off) .terminal_off else .terminal) else .terminal;
+ const envp = ChildEnv.build(&env_slots, paging);
const ws = posix.winsize{ .row = rows, .col = cols, .xpixel = 0, .ypixel = 0 };
// The child says here why it could not become the shell (its chdir or
// its exec failed, as for a script whose interpreter is not there): the