Something went wrong. Try again.
Something went wrong. Try again.
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394//! 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 std = @import("std");const Allocator = std.mem.Allocator;const internal_os = @import("../os/main.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.fs.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(alloc: Allocator) Pager { return .{ .child = initPager(alloc) };}
/// 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(buffer); } else { self.file_writer = std.fs.File.stdout().writer(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(); child.stdin = null; } _ = child.wait() catch {}; }
self.* = undefined;}
fn initPager(alloc: Allocator) ?std.process.Child { const stdout_file: std.fs.File = .stdout(); if (!stdout_file.isTty()) return null;
// Resolve the pager command: $GHOSTTY_PAGER > $PAGER > `less`. // An empty value for either env var disables paging. const ghostty_var = internal_os.getenv(alloc, "GHOSTTY_PAGER") catch null; defer if (ghostty_var) |v| v.deinit(alloc); const pager_var = internal_os.getenv(alloc, "PAGER") catch null; defer if (pager_var) |v| v.deinit(alloc);
const cmd: ?[]const u8 = cmd: { if (ghostty_var) |v| break :cmd if (v.value.len > 0) v.value else null; if (pager_var) |v| break :cmd if (v.value.len > 0) v.value else null; break :cmd "less"; };
if (cmd == null) return null;
var child: std.process.Child = .init(&.{cmd.?}, alloc); child.stdin_behavior = .Pipe; child.stdout_behavior = .Inherit; child.stderr_behavior = .Inherit;
child.spawn() catch return null; return child;}
test "pager: non-tty" { var pager: Pager = .init(std.testing.allocator); 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");}