Skip to content

Commit

Permalink
dotsv2: remove jitters
Browse files Browse the repository at this point in the history
  • Loading branch information
howardjohn committed Sep 14, 2023
1 parent 0d1ee6a commit df90c90
Show file tree
Hide file tree
Showing 4 changed files with 1,116 additions and 1,087 deletions.
17 changes: 1 addition & 16 deletions internal/dotwriter/writer.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@ import (
"io"
)

// ESC is the ASCII code for escape character
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 +23,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
46 changes: 41 additions & 5 deletions internal/dotwriter/writer_posix.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,53 @@
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)
// ESC is the ASCII code for escape character
const ESC = 27

// 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
w.out.Write(line)
w.clearRest()
// Add a newline if this isn't the last line
if i != len(lines)-1 {
w.out.Write([]byte{'\n'})
}
}
w.buf.Reset()
return nil
}

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

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

// hideCursor hides the cursor and returns a function to restore the cursor back.
Expand Down
18 changes: 13 additions & 5 deletions internal/dotwriter/writer_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,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 +84,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 df90c90

Please sign in to comment.