221 lines
4.7 KiB
Go
221 lines
4.7 KiB
Go
![]() |
package terminal
|
||
|
|
||
|
import (
|
||
|
"bufio"
|
||
|
"io"
|
||
|
"os"
|
||
|
"os/exec"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/go-delve/delve/pkg/terminal/colorize"
|
||
|
"github.com/mattn/go-isatty"
|
||
|
)
|
||
|
|
||
|
// transcriptWriter writes to a pagingWriter and also, optionally, to a
|
||
|
// buffered file.
|
||
|
type transcriptWriter struct {
|
||
|
fileOnly bool
|
||
|
pw *pagingWriter
|
||
|
file *bufio.Writer
|
||
|
fh io.Closer
|
||
|
colorEscapes map[colorize.Style]string
|
||
|
}
|
||
|
|
||
|
func (w *transcriptWriter) Write(p []byte) (nn int, err error) {
|
||
|
if !w.fileOnly {
|
||
|
nn, err = w.pw.Write(p)
|
||
|
}
|
||
|
if err == nil {
|
||
|
if w.file != nil {
|
||
|
return w.file.Write(p)
|
||
|
}
|
||
|
}
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// ColorizePrint prints to out a syntax highlighted version of the text read from
|
||
|
// reader, between lines startLine and endLine.
|
||
|
func (w *transcriptWriter) ColorizePrint(path string, reader io.ReadSeeker, startLine, endLine, arrowLine int) error {
|
||
|
var err error
|
||
|
if !w.fileOnly {
|
||
|
err = colorize.Print(w.pw.w, path, reader, startLine, endLine, arrowLine, w.colorEscapes)
|
||
|
}
|
||
|
if err == nil {
|
||
|
if w.file != nil {
|
||
|
reader.Seek(0, io.SeekStart)
|
||
|
return colorize.Print(w.file, path, reader, startLine, endLine, arrowLine, nil)
|
||
|
}
|
||
|
}
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// Echo outputs str only to the optional transcript file.
|
||
|
func (w *transcriptWriter) Echo(str string) {
|
||
|
if w.file != nil {
|
||
|
w.file.WriteString(str)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// Flush flushes the optional transcript file.
|
||
|
func (w *transcriptWriter) Flush() {
|
||
|
if w.file != nil {
|
||
|
w.file.Flush()
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// CloseTranscript closes the optional transcript file.
|
||
|
func (w *transcriptWriter) CloseTranscript() error {
|
||
|
if w.file == nil {
|
||
|
return nil
|
||
|
}
|
||
|
w.file.Flush()
|
||
|
w.fileOnly = false
|
||
|
err := w.fh.Close()
|
||
|
w.file = nil
|
||
|
w.fh = nil
|
||
|
return err
|
||
|
}
|
||
|
|
||
|
// TranscribeTo starts transcribing the output to the specified file. If
|
||
|
// fileOnly is true the output will only go to the file, output to the
|
||
|
// io.Writer will be suppressed.
|
||
|
func (w *transcriptWriter) TranscribeTo(fh io.WriteCloser, fileOnly bool) {
|
||
|
if w.file == nil {
|
||
|
w.CloseTranscript()
|
||
|
}
|
||
|
w.fh = fh
|
||
|
w.file = bufio.NewWriter(fh)
|
||
|
w.fileOnly = fileOnly
|
||
|
}
|
||
|
|
||
|
// 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()
|
||
|
}
|
||
|
|
||
|
type pagingWriterMode uint8
|
||
|
|
||
|
const (
|
||
|
pagingWriterNormal pagingWriterMode = iota
|
||
|
pagingWriterMaybe
|
||
|
pagingWriterPaging
|
||
|
|
||
|
pagingWriterMaxLines = 30
|
||
|
pagingWriterColsPerLine = 100
|
||
|
)
|
||
|
|
||
|
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 w.cmdStdin.Write(p)
|
||
|
} 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
|
||
|
}
|
||
|
|
||
|
func (w *pagingWriter) largeOutput() bool {
|
||
|
if len(w.buf) > pagingWriterMaxLines*pagingWriterColsPerLine {
|
||
|
return true
|
||
|
}
|
||
|
nl := 0
|
||
|
for i := range w.buf {
|
||
|
if w.buf[i] == '\n' {
|
||
|
nl++
|
||
|
if nl > pagingWriterMaxLines {
|
||
|
return true
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
return false
|
||
|
}
|