Open
Description
Of course, you can implement it from scratch, or you can call less
or more
combined with pipe to work around this.
package terminal
import (
"bufio"
"fmt"
"io"
"os"
"os/exec"
"strings"
"syscall"
"unsafe"
"github.com/mattn/go-isatty"
)
// pagingWriter writes to w. If PageMaybe is called, after a large amount of
// text has been written to w it will pipe the output to a pager instead.
type pagingWriter struct {
mode pagingWriterMode
w io.Writer
buf []byte
cmd *exec.Cmd
cmdStdin io.WriteCloser
pager string
lastnl bool
cancel func()
lines, columns int
}
type pagingWriterMode uint8
const (
pagingWriterNormal pagingWriterMode = iota
pagingWriterMaybe
pagingWriterPaging
)
func (w *pagingWriter) Write(p []byte) (nn int, err error) {
switch w.mode {
default:
fallthrough
case pagingWriterNormal:
return w.w.Write(p)
case pagingWriterMaybe:
w.buf = append(w.buf, p...)
if w.largeOutput() {
w.cmd = exec.Command(w.pager)
w.cmd.Stdout = os.Stdout
w.cmd.Stderr = os.Stderr
var err1, err2 error
w.cmdStdin, err1 = w.cmd.StdinPipe()
err2 = w.cmd.Start()
if err1 != nil || err2 != nil {
w.cmd = nil
w.mode = pagingWriterNormal
return w.w.Write(p)
}
if !w.lastnl {
w.w.Write([]byte("\n"))
}
w.w.Write([]byte("Sending output to pager...\n"))
w.cmdStdin.Write(w.buf)
w.buf = nil
w.mode = pagingWriterPaging
return len(p), nil
} else {
if len(p) > 0 {
w.lastnl = p[len(p)-1] == '\n'
}
return w.w.Write(p)
}
case pagingWriterPaging:
n, err := w.cmdStdin.Write(p)
if err != nil && w.cancel != nil {
w.cancel()
w.cancel = nil
}
return n, err
}
}
// Reset returns the pagingWriter to its normal mode.
func (w *pagingWriter) Reset() {
if w.mode == pagingWriterNormal {
return
}
w.mode = pagingWriterNormal
w.buf = nil
if w.cmd != nil {
w.cmdStdin.Close()
w.cmd.Wait()
w.cmd = nil
w.cmdStdin = nil
}
}
// PageMaybe configures pagingWriter to cache the output, after a large
// amount of text has been written to w it will automatically switch to
// piping output to a pager.
// The cancel function is called the first time a write to the pager errors.
func (w *pagingWriter) PageMaybe(cancel func()) {
if w.mode != pagingWriterNormal {
return
}
dlvpager := os.Getenv("DELVE_PAGER")
if dlvpager == "" {
if stdout, _ := w.w.(*os.File); stdout != nil {
if !isatty.IsTerminal(stdout.Fd()) {
return
}
}
if strings.ToLower(os.Getenv("TERM")) == "dumb" {
return
}
}
w.mode = pagingWriterMaybe
w.pager = dlvpager
if w.pager == "" {
w.pager = os.Getenv("PAGER")
if w.pager == "" {
w.pager = "more"
}
}
w.lastnl = true
w.cancel = cancel
w.getWindowSize()
}
func (w *pagingWriter) largeOutput() bool {
lines := 0
lineStart := 0
for i := range w.buf {
if i-lineStart > w.columns || w.buf[i] == '\n' {
lineStart = i
lines++
if lines > w.lines {
return true
}
}
}
return false
}
type winSize struct {
row, col uint16
xpixel, ypixel uint16
}
func (w *pagingWriter) getWindowSize() {
var ws winSize
ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws)))
if int(ok) < 0 {
w.mode = pagingWriterNormal
return
}
w.lines = int(ws.row)
w.columns = int(ws.col)
}
// Print prints to out the text read from reader, between lines startLine and endLine.
func Print(out io.Writer, reader io.Reader, startLine, endLine, arrowLine int) error {
scanner := bufio.NewScanner(reader)
lineno := 0
for scanner.Scan() {
lineno++
if lineno < startLine {
continue
}
if lineno >= endLine {
break
}
// Print line number and arrow
if lineno == arrowLine {
fmt.Fprintf(out, "=>")
} else {
fmt.Fprintf(out, " ")
}
fmt.Fprintf(out, "%4d:\t%s\n", lineno, scanner.Text())
}
return scanner.Err()
}
Metadata
Metadata
Assignees
Labels
No labels