//! A pager wraps output to an external pager program (like `less`) when //! stdout is a TTY. The pager command is resolved as: //! //! `$GHOSTTY_PAGER` > `$PAGER` > `less` //! //! Setting either env var to an empty string disables paging. //! If stdout is not a TTY, writes go directly to stdout. const Pager = @This(); const builtin = @import("builtin"); const std = @import("std"); const Allocator = std.mem.Allocator; const internal_os = @import("../os/main.zig"); const global = @import("../global.zig"); /// The pager child process, if one was spawned. child: ?std.process.Child = null, /// The buffered file writer used for both the pager pipe and direct /// stdout paths. file_writer: std.Io.File.Writer = undefined, /// Initialize the pager. If stdout is a TTY, this spawns the pager /// process. Otherwise, output goes directly to stdout. pub fn init() Pager { return .{ .child = initPager() }; } /// Writes to the pager process if available; otherwise, stdout. pub fn writer(self: *Pager, buffer: []u8) *std.Io.Writer { if (self.child) |child| { self.file_writer = child.stdin.?.writer(global.io(), buffer); } else { self.file_writer = std.Io.File.stdout().writer(global.io(), buffer); } return &self.file_writer.interface; } /// Deinitialize the pager. Waits for the spawned process to exit. pub fn deinit(self: *Pager) void { if (self.child) |*child| { // Flush any remaining buffered data, close the pipe so the // pager sees EOF, then wait for it to exit. self.file_writer.interface.flush() catch {}; if (child.stdin) |stdin| { stdin.close(global.io()); child.stdin = null; } _ = child.wait(global.io()) catch {}; } self.* = undefined; } fn initPager() ?std.process.Child { const stdout_file: std.Io.File = .stdout(); if (!(stdout_file.isTty(global.io()) catch return null)) return null; var env = global.environMap() catch return null; defer env.deinit(); // Resolve the pager command: $GHOSTTY_PAGER > $PAGER > `less`. // An empty value for either env var disables paging. const ghostty_var = env.get("GHOSTTY_PAGER"); const pager_var = env.get("PAGER"); const cmd: ?[]const u8 = cmd: { if (ghostty_var) |v| break :cmd if (v.len > 0) v else null; if (pager_var) |v| break :cmd if (v.len > 0) v else null; break :cmd "less"; }; if (cmd == null) return null; return std.process.spawn(global.io(), .{ .argv = &.{cmd.?}, .stdin = .pipe, .stdout = .inherit, .stderr = .inherit, }) catch null; } test "pager: non-tty" { var pager: Pager = .init(); defer pager.deinit(); try std.testing.expect(pager.child == null); } test "pager: default writer" { var pager: Pager = .{}; defer pager.deinit(); try std.testing.expect(pager.child == null); var buf: [4096]u8 = undefined; const w = pager.writer(&buf); try w.writeAll("hello"); }