diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 09:30:26 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-07-24 09:30:26 +0200 |
| commit | 9321fd3490b1739e4c84fd229a2cfef244ffdc29 (patch) | |
| tree | dc747f19d74f06e8bc4741864d007e0cd593e5eb /internal/cli/pager.go | |
| parent | b93de95dd24ff2a1d3126e6d7d00cd77530a03bf (diff) | |
| download | lectio-9321fd3490b1739e4c84fd229a2cfef244ffdc29.tar.gz lectio-9321fd3490b1739e4c84fd229a2cfef244ffdc29.zip | |
cli: pager for reading output (-P/--pager, --no-pager, config pager, $PAGER fallback); v0.3.0
Diffstat (limited to 'internal/cli/pager.go')
| -rw-r--r-- | internal/cli/pager.go | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/internal/cli/pager.go b/internal/cli/pager.go new file mode 100644 index 0000000..2c7365a --- /dev/null +++ b/internal/cli/pager.go @@ -0,0 +1,83 @@ +package cli + +import ( + "io" + "os" + "os/exec" + "strings" + + "github.com/lukaszkasprzak/lectio/internal/config" +) + +// pagerRequested decides whether the reading-output tail should be paged, +// given -P/--pager, --no-pager, and cfg.Pager. --no-pager always wins (an +// explicit "never page" beats everything); otherwise -P forces paging on; +// otherwise a non-empty cfg.Pager turns paging on by default. Pure and +// side-effect free -- the actual TTY gate lives in isTerminalWriter. +func pagerRequested(pagerFlag, noPager bool, cfg config.Config) bool { + if noPager { + return false + } + if pagerFlag { + return true + } + return cfg.Pager != "" +} + +// pagerCommand resolves the pager argv to run: cfg.Pager wins, then $PAGER, +// then the "less -R" built-in default. The resolved command string is split +// on whitespace via strings.Fields into an argv slice (so "less -R" or a +// $PAGER value with flags works without a shell). +func pagerCommand(cfg config.Config) []string { + cmd := cfg.Pager + if cmd == "" { + cmd = os.Getenv("PAGER") + } + if cmd == "" { + cmd = "less -R" + } + return strings.Fields(cmd) +} + +// isTerminalWriter reports whether w is a real terminal -- only then is +// paging worth doing. Reuses ttyWidth (termwidth_unix.go/termwidth_other.go) +// via the *os.File it already knows how to probe; anything else (notably +// the bytes.Buffer every test uses for stdout) is never a terminal, so +// paging never triggers in tests, and `lectio | cat` (stdout redirected to +// a pipe, not a *os.File terminal) writes directly too. +func isTerminalWriter(w io.Writer) bool { + if f, ok := w.(*os.File); ok { + return ttyWidth(f) > 0 + } + return false +} + +// startPager launches argv[0] as a pager subprocess wired to the real +// terminal (stdout/stderr passed through), returning a WriteCloser the +// caller renders reading output into. ok=false (with out, finish both nil) +// on any setup failure -- an empty argv, a StdinPipe error, or the pager +// binary not being found/startable -- so the caller can fall back to +// writing directly to stdout without crashing. +func startPager(argv []string, stdout, stderr io.Writer) (out io.WriteCloser, finish func(), ok bool) { + if len(argv) == 0 { + return nil, nil, false + } + + cmd := exec.Command(argv[0], argv[1:]...) + cmd.Stdout = stdout + cmd.Stderr = stderr + + w, err := cmd.StdinPipe() + if err != nil { + return nil, nil, false + } + if err := cmd.Start(); err != nil { + return nil, nil, false + } + + finish = func() { + w.Close() + _ = cmd.Wait() + } + return w, finish, true +} |
