summaryrefslogtreecommitdiff
path: root/internal/cli/pager.go
blob: 2c7365a4f61b77244df6264ee6db4942c2b79c27 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
}