summaryrefslogtreecommitdiff
path: root/src/host_io.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/host_io.zig')
-rw-r--r--src/host_io.zig95
1 files changed, 92 insertions, 3 deletions
diff --git a/src/host_io.zig b/src/host_io.zig
index ebe910aa..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,6 +1273,24 @@ pub const ChildEnv = struct {
slots[n] = entry;
n += 1;
}
+ // 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;
+ } else false;
+ if (set) continue;
+ if (n + own.len + 2 > slots.len) return null;
+ slots[n] = default;
+ n += 1;
+ }
for (own) |mine| {
slots[n] = mine;
n += 1;
@@ -1275,6 +1298,28 @@ pub const ChildEnv = struct {
slots[n] = null;
return @ptrCast(slots);
}
+
+ 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" {
@@ -1299,11 +1344,32 @@ 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;
var dumb = false;
+ var pagers: usize = 0;
+ var git_pagers: usize = 0;
+ var pager_cat = false;
+ var git_pager_cat = false;
var i: usize = 0;
while (envp[i]) |entry| : (i += 1) {
const line = std.mem.span(entry);
@@ -1311,7 +1377,16 @@ test "the child environment replaces the launcher's terminal identity exactly on
if (std.mem.startsWith(u8, line, "COLORTERM=")) colorterms += 1;
if (std.mem.eql(u8, line, "TERM=dumb")) dumb = true;
if (std.mem.eql(u8, line, "PARDES_CHILD_ENV_PROBE=1")) probes += 1;
+ if (std.mem.startsWith(u8, line, "PAGER=")) pagers += 1;
+ if (std.mem.startsWith(u8, line, "GIT_PAGER=")) git_pagers += 1;
+ if (std.mem.eql(u8, line, "PAGER=cat")) pager_cat = true;
+ if (std.mem.eql(u8, line, "GIT_PAGER=cat")) git_pager_cat = true;
}
+ // A pager only where the user named none: cat, so nothing waits in less.
+ try std.testing.expectEqual(@as(usize, 1), pagers);
+ try std.testing.expectEqual(@as(usize, 1), git_pagers);
+ if (std.c.getenv("PAGER") == null) try std.testing.expect(pager_cat);
+ if (std.c.getenv("GIT_PAGER") == null) try std.testing.expect(git_pager_cat);
try std.testing.expectEqual(@as(usize, 1), terms);
try std.testing.expectEqual(@as(usize, 1), colorterms);
try std.testing.expectEqual(@as(usize, 1), probes);
@@ -1323,6 +1398,14 @@ test "the child environment replaces the launcher's terminal identity exactly on
extern "c" fn unsetenv(name: [*:0]const u8) c_int;
+fn exportWinid(serial: u32) void {
+ var buf: [16]u8 = undefined;
+ if (serial != 0) if (std.fmt.bufPrintSentinel(&buf, "{d}", .{serial}, 0)) |text| {
+ if (Shell.setenv("winid", text, 1) == 0) return;
+ } else |_| {};
+ _ = unsetenv("winid");
+}
+
/// The longest line a command pane runs (exec.zig refuses a longer one).
pub const exec_command_max = pardes.exec.command_max;
@@ -1390,6 +1473,9 @@ pub fn forkShell(
if (core) |c| (if (c.panes[pane]) |pn| pn.serial else 0) else 0,
if (core) |c| !c.opts.nested else false,
);
+ // acme's `$winid`: the pane a command was run from, so a script it runs
+ // can address that pane; unset for a shell and a header tag's command.
+ exportWinid(if (core) |c| (if (c.panes[pane]) |pn| (if (pn.command != null) pn.command_from else 0) else 0) else 0);
if (helper) |path| {
const c = core.?;
const pn = c.panes[pane].?;
@@ -1403,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