github.com/cnboonhan/delve@v0.0.0-20230908061759-363f2388c2fb/pkg/terminal/out.go (about)

     1  package terminal
     2  
     3  import (
     4  	"bufio"
     5  	"io"
     6  	"os"
     7  	"os/exec"
     8  	"strings"
     9  
    10  	"github.com/go-delve/delve/pkg/terminal/colorize"
    11  	"github.com/mattn/go-isatty"
    12  )
    13  
    14  // transcriptWriter writes to a pagingWriter and also, optionally, to a
    15  // buffered file.
    16  type transcriptWriter struct {
    17  	fileOnly     bool
    18  	pw           *pagingWriter
    19  	file         *bufio.Writer
    20  	fh           io.Closer
    21  	colorEscapes map[colorize.Style]string
    22  	altTabString string
    23  }
    24  
    25  func (w *transcriptWriter) Write(p []byte) (nn int, err error) {
    26  	if !w.fileOnly {
    27  		nn, err = w.pw.Write(p)
    28  	}
    29  	if err == nil {
    30  		if w.file != nil {
    31  			return w.file.Write(p)
    32  		}
    33  	}
    34  	return
    35  }
    36  
    37  // ColorizePrint prints to out a syntax highlighted version of the text read from
    38  // reader, between lines startLine and endLine.
    39  func (w *transcriptWriter) ColorizePrint(path string, reader io.ReadSeeker, startLine, endLine, arrowLine int) error {
    40  	var err error
    41  	if !w.fileOnly {
    42  		err = colorize.Print(w.pw.w, path, reader, startLine, endLine, arrowLine, w.colorEscapes, w.altTabString)
    43  	}
    44  	if err == nil {
    45  		if w.file != nil {
    46  			reader.Seek(0, io.SeekStart)
    47  			return colorize.Print(w.file, path, reader, startLine, endLine, arrowLine, nil, w.altTabString)
    48  		}
    49  	}
    50  	return err
    51  }
    52  
    53  // Echo outputs str only to the optional transcript file.
    54  func (w *transcriptWriter) Echo(str string) {
    55  	if w.file != nil {
    56  		w.file.WriteString(str)
    57  	}
    58  }
    59  
    60  // Flush flushes the optional transcript file.
    61  func (w *transcriptWriter) Flush() {
    62  	if w.file != nil {
    63  		w.file.Flush()
    64  	}
    65  }
    66  
    67  // CloseTranscript closes the optional transcript file.
    68  func (w *transcriptWriter) CloseTranscript() error {
    69  	if w.file == nil {
    70  		return nil
    71  	}
    72  	w.file.Flush()
    73  	w.fileOnly = false
    74  	err := w.fh.Close()
    75  	w.file = nil
    76  	w.fh = nil
    77  	return err
    78  }
    79  
    80  // TranscribeTo starts transcribing the output to the specified file. If
    81  // fileOnly is true the output will only go to the file, output to the
    82  // io.Writer will be suppressed.
    83  func (w *transcriptWriter) TranscribeTo(fh io.WriteCloser, fileOnly bool) {
    84  	if w.file == nil {
    85  		w.CloseTranscript()
    86  	}
    87  	w.fh = fh
    88  	w.file = bufio.NewWriter(fh)
    89  	w.fileOnly = fileOnly
    90  }
    91  
    92  // pagingWriter writes to w. If PageMaybe is called, after a large amount of
    93  // text has been written to w it will pipe the output to a pager instead.
    94  type pagingWriter struct {
    95  	mode     pagingWriterMode
    96  	w        io.Writer
    97  	buf      []byte
    98  	cmd      *exec.Cmd
    99  	cmdStdin io.WriteCloser
   100  	pager    string
   101  	lastnl   bool
   102  	cancel   func()
   103  
   104  	lines, columns int
   105  }
   106  
   107  type pagingWriterMode uint8
   108  
   109  const (
   110  	pagingWriterNormal pagingWriterMode = iota
   111  	pagingWriterMaybe
   112  	pagingWriterPaging
   113  )
   114  
   115  func (w *pagingWriter) Write(p []byte) (nn int, err error) {
   116  	switch w.mode {
   117  	default:
   118  		fallthrough
   119  	case pagingWriterNormal:
   120  		return w.w.Write(p)
   121  	case pagingWriterMaybe:
   122  		w.buf = append(w.buf, p...)
   123  		if w.largeOutput() {
   124  			w.cmd = exec.Command(w.pager)
   125  			w.cmd.Stdout = os.Stdout
   126  			w.cmd.Stderr = os.Stderr
   127  
   128  			var err1, err2 error
   129  			w.cmdStdin, err1 = w.cmd.StdinPipe()
   130  			err2 = w.cmd.Start()
   131  			if err1 != nil || err2 != nil {
   132  				w.cmd = nil
   133  				w.mode = pagingWriterNormal
   134  				return w.w.Write(p)
   135  			}
   136  			if !w.lastnl {
   137  				w.w.Write([]byte("\n"))
   138  			}
   139  			w.w.Write([]byte("Sending output to pager...\n"))
   140  			w.cmdStdin.Write(w.buf)
   141  			w.buf = nil
   142  			w.mode = pagingWriterPaging
   143  			return len(p), nil
   144  		} else {
   145  			if len(p) > 0 {
   146  				w.lastnl = p[len(p)-1] == '\n'
   147  			}
   148  			return w.w.Write(p)
   149  		}
   150  	case pagingWriterPaging:
   151  		n, err := w.cmdStdin.Write(p)
   152  		if err != nil && w.cancel != nil {
   153  			w.cancel()
   154  			w.cancel = nil
   155  		}
   156  		return n, err
   157  	}
   158  }
   159  
   160  // Reset returns the pagingWriter to its normal mode.
   161  func (w *pagingWriter) Reset() {
   162  	if w.mode == pagingWriterNormal {
   163  		return
   164  	}
   165  	w.mode = pagingWriterNormal
   166  	w.buf = nil
   167  	if w.cmd != nil {
   168  		w.cmdStdin.Close()
   169  		w.cmd.Wait()
   170  		w.cmd = nil
   171  		w.cmdStdin = nil
   172  	}
   173  }
   174  
   175  // PageMaybe configures pagingWriter to cache the output, after a large
   176  // amount of text has been written to w it will automatically switch to
   177  // piping output to a pager.
   178  // The cancel function is called the first time a write to the pager errors.
   179  func (w *pagingWriter) PageMaybe(cancel func()) {
   180  	if w.mode != pagingWriterNormal {
   181  		return
   182  	}
   183  	dlvpager := os.Getenv("DELVE_PAGER")
   184  	if dlvpager == "" {
   185  		if stdout, _ := w.w.(*os.File); stdout != nil {
   186  			if !isatty.IsTerminal(stdout.Fd()) {
   187  				return
   188  			}
   189  		}
   190  		if strings.ToLower(os.Getenv("TERM")) == "dumb" {
   191  			return
   192  		}
   193  	}
   194  	w.mode = pagingWriterMaybe
   195  	w.pager = dlvpager
   196  	if w.pager == "" {
   197  		w.pager = os.Getenv("PAGER")
   198  		if w.pager == "" {
   199  			w.pager = "more"
   200  		}
   201  	}
   202  	w.lastnl = true
   203  	w.cancel = cancel
   204  	w.getWindowSize()
   205  }
   206  
   207  func (w *pagingWriter) largeOutput() bool {
   208  	lines := 0
   209  	lineStart := 0
   210  	for i := range w.buf {
   211  		if i-lineStart > w.columns || w.buf[i] == '\n' {
   212  			lineStart = i
   213  			lines++
   214  			if lines > w.lines {
   215  				return true
   216  			}
   217  		}
   218  	}
   219  	return false
   220  }