Skip to content

Commit

Permalink
Merge pull request #368 from howardjohn/dotsv2/line-by-line
Browse files Browse the repository at this point in the history
dotsv2: remove jitters entirely
  • Loading branch information
dnephin committed Sep 16, 2023
2 parents 0d1ee6a + eb1c70d commit ec99a25
Show file tree
Hide file tree
Showing 4 changed files with 1,120 additions and 1,084 deletions.
14 changes: 1 addition & 13 deletions internal/dotwriter/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const ESC = 27

// Writer buffers writes until Flush is called. Flush clears previously written
// lines before writing new lines from the buffer.
// The main logic is platform specific, see the related files.
type Writer struct {
out io.Writer
buf bytes.Buffer
Expand All @@ -25,19 +26,6 @@ func New(out io.Writer) *Writer {
return &Writer{out: out}
}

// Flush the buffer, writing all buffered lines to out
func (w *Writer) Flush() error {
if w.buf.Len() == 0 {
return nil
}
defer w.hideCursor()()
w.clearLines(w.lineCount)
w.lineCount = bytes.Count(w.buf.Bytes(), []byte{'\n'})
_, err := w.out.Write(w.buf.Bytes())
w.buf.Reset()
return err
}

// Write saves buf to a buffer
func (w *Writer) Write(buf []byte) (int, error) {
return w.buf.Write(buf)
Expand Down
49 changes: 44 additions & 5 deletions internal/dotwriter/writer_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,56 @@
package dotwriter

import (
"bytes"
"fmt"
"strings"
)

// clear the line and move the cursor up
var clear = fmt.Sprintf("%c[%dA%c[2K", ESC, 1, ESC)
// hide cursor
var hide = fmt.Sprintf("%c[?25l", ESC)

// show cursor
var show = fmt.Sprintf("%c[?25h", ESC)

func (w *Writer) clearLines(count int) {
_, _ = fmt.Fprint(w.out, strings.Repeat(clear, count))
// Flush the buffer, writing all buffered lines to out
func (w *Writer) Flush() error {
if w.buf.Len() == 0 {
return nil
}
// Hide cursor during write to avoid it moving around the screen
defer w.hideCursor()()

// Move up to the top of our last output.
w.up(w.lineCount)
lines := bytes.Split(w.buf.Bytes(), []byte{'\n'})
w.lineCount = len(lines) - 1 // Record how many lines we will write for the next Flush()
for i, line := range lines {
// For each line, write the contents and clear everything else on the line
_, err := w.out.Write(line)
if err != nil {
return err
}
w.clearRest()
// Add a newline if this isn't the last line
if i != len(lines)-1 {
_, err := w.out.Write([]byte{'\n'})
if err != nil {
return err
}
}
}
w.buf.Reset()
return nil
}

func (w *Writer) up(count int) {
if count == 0 {
return
}
_, _ = fmt.Fprintf(w.out, "%c[%dA", ESC, count)
}

func (w *Writer) clearRest() {
_, _ = fmt.Fprintf(w.out, "%c[0K", ESC)
}

// hideCursor hides the cursor and returns a function to restore the cursor back.
Expand Down
19 changes: 14 additions & 5 deletions internal/dotwriter/writer_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package dotwriter

import (
"bytes"
"fmt"
"io"
"strings"
Expand Down Expand Up @@ -35,6 +36,19 @@ type fdWriter interface {
Fd() uintptr
}

// Flush implementation on windows is not ideal; we clear the entire screen before writing, which can result in flashing output
// Windows likely can adopt the same approach as posix if someone invests some effort
func (w *Writer) Flush() error {
if w.buf.Len() == 0 {
return nil
}
w.clearLines(w.lineCount)
w.lineCount = bytes.Count(w.buf.Bytes(), []byte{'\n'})
_, err := w.out.Write(w.buf.Bytes())
w.buf.Reset()
return err
}

func (w *Writer) clearLines(count int) {
f, ok := w.out.(fdWriter)
if ok && !isConsole(f.Fd()) {
Expand Down Expand Up @@ -71,8 +85,3 @@ func isConsole(fd uintptr) bool {
err := windows.GetConsoleMode(windows.Handle(fd), &mode)
return err == nil
}

// This may work on Windows but I am not sure how to do it and its optional. For now, just do nothing.
func (w *Writer) hideCursor() func() {
return func() {}
}
Loading

0 comments on commit ec99a25

Please sign in to comment.