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 }