diff options
Diffstat (limited to 'src/host_io.zig')
| -rw-r--r-- | src/host_io.zig | 95 |
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 |
