-
-
Save cagedmantis/09c0b965451520bcdda86d5e3f840ba3 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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