Skip to content

Instantly share code, notes, and snippets.

@cagedmantis
Forked from davidbalbert/pager.go
Created April 23, 2023 17:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cagedmantis/09c0b965451520bcdda86d5e3f840ba3 to your computer and use it in GitHub Desktop.
Save cagedmantis/09c0b965451520bcdda86d5e3f840ba3 to your computer and use it in GitHub Desktop.
package main
import (
"bufio"
"bytes"
"io"
"strings"
"golang.org/x/term"
)
// A simple pager like more(1). Implements io.Writer. If r is a terminal,
// it must be put in raw mode with term.MakeRaw(). If r is not a terminal,
// Pager is a no-op and all output is directly written to w.
type Pager struct {
fd int
w io.Writer
r *bufio.Reader
buf bytes.Buffer
shouldPage bool
line int
stopped bool
}
var _ io.Writer = &Pager{}
func NewPager(r io.Reader, w io.Writer) *Pager {
shouldPage := false
fd := -1
f, ok := r.(interface{ Fd() uintptr }) // usually *os.File
if ok {
fd = int(f.Fd())
shouldPage = term.IsTerminal(fd)
}
return &Pager{
fd: fd,
w: w,
r: bufio.NewReader(r),
buf: bytes.Buffer{},
shouldPage: shouldPage,
}
}
func (p *Pager) Write(b []byte) (n int, err error) {
if !p.shouldPage {
return p.w.Write(b)
}
if p.stopped {
return 0, io.EOF
}
_, height, err := term.GetSize(p.fd)
if err != nil {
return p.w.Write(b)
}
p.buf.Write(b)
written := 0
for written < len(b) {
if p.line >= height-1 {
// We're at the bottom of the screen, so wait for user input
// before continuing. It's height-1 rather than height to
// leave room for "--More--".
err := p.paginate()
if err != nil {
return written, err
}
// We just pressed 'G' to go to the end. Pass through the rest
// of the buffer.
if !p.shouldPage {
n, err := p.w.Write(p.buf.Bytes())
written += n
return written, err
}
}
line, err := p.buf.ReadBytes('\n')
if err != nil && err != io.EOF {
return written, err
}
n, err := p.w.Write(line)
written += n
if err != nil {
return written, err
}
p.line++
}
return written, nil
}
var (
more = []byte("--More--")
clear = []byte("\r" + strings.Repeat(" ", len(more)) + "\r")
)
func (p *Pager) paginate() error {
for {
_, err := p.w.Write(more)
if err != nil {
return err
}
b, err := p.r.ReadByte()
if err != nil {
return err
}
_, err = p.w.Write(clear)
if err != nil {
return err
}
switch b {
case 'q':
p.stopped = true
return io.EOF
case ' ':
p.line = 0
return nil
case '\r', 'j':
p.line--
return nil
case 'G':
p.shouldPage = false
return nil
case '\x1b': // escape sequence, read next two bytes
var b [2]byte
_, err := p.r.Read(b[:])
if err != nil {
return err
}
if b[0] == '[' && b[1] == 'B' {
// down arrow
p.line--
return nil
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment