77 "os"
88 "os/exec"
99 "os/signal"
10- "runtime"
1110 "strings"
11+ "sync"
1212 "syscall"
1313
1414 "github.com/alecthomas/bit/engine/csi"
@@ -201,6 +201,7 @@ func (l *Logger) Exec(dir, command string) error {
201201 defer t .Close ()
202202 defer p .Close ()
203203 lw := l .WriterAt (LogLevelInfo )
204+ defer lw .Close ()
204205 cmd := exec .Command ("/bin/sh" , "-c" , command )
205206 cmd .Dir = dir
206207 cmd .SysProcAttr = & syscall.SysProcAttr {
@@ -218,55 +219,105 @@ func (l *Logger) Exec(dir, command string) error {
218219}
219220
220221// WriterAt returns a writer that logs each line at the given level.
221- func (l * Logger ) WriterAt (level LogLevel ) * io.PipeWriter {
222+ func (l * Logger ) WriterAt (level LogLevel ) io.WriteCloser {
222223 // Based on MIT licensed Logrus https://github.com/sirupsen/logrus/blob/bdc0db8ead3853c56b7cd1ac2ba4e11b47d7da6b/writer.go#L27
223224 reader , writer := io .Pipe ()
224225
225- go l .writerScanner (reader , level )
226- runtime .SetFinalizer (writer , writerFinalizer )
226+ wg := & sync.WaitGroup {}
227+ wg .Add (1 )
228+ go l .writerScanner (wg , reader , level )
229+ return & pipeWait {r : reader , PipeWriter : writer , wg : wg }
230+ }
231+
232+ type pipeWait struct {
233+ r * io.PipeReader
234+ * io.PipeWriter
235+ wg * sync.WaitGroup
236+ }
227237
228- return writer
238+ func (p pipeWait ) Close () error {
239+ err := p .PipeWriter .Close ()
240+ p .wg .Wait ()
241+ return err
229242}
230243
231244// There is a bit of magic here to support cursor horizontal absolute escape
232245// sequences. When a new position is requested, we add the width of the margin.
233- func (l * Logger ) writerScanner (r * io.PipeReader , level LogLevel ) {
246+ func (l * Logger ) writerScanner (wg * sync. WaitGroup , r * io.PipeReader , level LogLevel ) {
234247 defer r .Close ()
248+ defer wg .Done ()
249+
250+ w , _ := os .OpenFile ("foo" , os .O_WRONLY | os .O_APPEND , 0600 )
251+ defer w .Close ()
235252
236253 esc := csi .NewReader (r )
254+ drawPrefix := true
255+ var newline []byte
237256 for {
238257 segment , err := esc .Read ()
239258 if errors .Is (err , io .EOF ) || errors .Is (err , io .ErrClosedPipe ) {
259+ os .Stdout .Write (newline )
240260 return
241261 } else if err != nil {
242262 l .Warnf ("error reading CSI sequence: %s" , err )
243263 return
244264 }
265+
266+ // If we have a CSI sequence, possibly intercept it to cater for the margin.
267+ // But in all cases we want to transform it to Text.
245268 if cs , ok := segment .(csi.CSI ); ok {
246- if cs .Final == 'G' {
247- params , err := cs .IntParams ()
248- if err != nil || len (params ) != 1 {
249- segment = csi .Text (cs .String ())
250- } else {
269+ // All the cases we intercept are single parameter sequences.
270+ params , err := cs .IntParams ()
271+ if err != nil || len (params ) != 1 {
272+ segment = csi .Text (cs .String ())
273+ } else {
274+ switch cs .Final {
275+ case 'G' : // G is cursor horizontal absolute.
251276 // We have a CHA sequence, so add the margin width.
252- col := params [0 ]
253- col += 18
277+ col := params [0 ] + 18
254278 segment = csi .Text (fmt .Sprintf ("\033 [%dG" , col ))
279+
280+ case 'K' : // K is erase in line. We want to intercept 1 (clear to start of line) and 2 (clear entire line).
281+ if params [0 ] == 1 || params [0 ] == 2 {
282+ // Save the cursor position.
283+ text := []byte ("\033 [s" )
284+ // Apply the CSI.
285+ text = append (text , cs .String ()... )
286+ // Move to the start of the line.
287+ text = append (text , "\033 [1G" ... )
288+ // Write the prefix.
289+ text = append (text , l .getPrefix (level )... )
290+ // Restore the cursor position.
291+ text = append (text , "\033 [u" ... )
292+
293+ segment = csi .Text (text )
294+ } else {
295+ segment = csi .Text (cs .String ())
296+ }
297+ default :
298+ segment = csi .Text (cs .String ())
255299 }
256- } else {
257- segment = csi .Text (cs .String ())
258300 }
259301 }
260302
261303 for _ , b := range segment .(csi.Text ) { //nolint:forcetypeassert
262- os . Stdout . Write ([]byte {b })
304+ w . Write ([]byte {b }) //nolint:errcheck
263305 if b == '\r' || b == '\n' {
306+ newline = append (newline , b )
307+ continue
308+ }
309+ if drawPrefix {
264310 os .Stdout .Write ([]byte (l .getPrefix (level )))
311+ drawPrefix = false
265312 }
313+ for _ , nl := range newline {
314+ os .Stdout .Write ([]byte {nl })
315+ if nl == '\n' {
316+ os .Stdout .Write ([]byte (l .getPrefix (level )))
317+ }
318+ }
319+ newline = nil
320+ os .Stdout .Write ([]byte {b })
266321 }
267322 }
268323}
269-
270- func writerFinalizer (writer * io.PipeWriter ) {
271- _ = writer .Close ()
272- }
0 commit comments