feat: use Termenv.Output to write to tty

This commit is contained in:
Christian Muehlhaeuser
2022-06-04 15:14:03 +02:00
parent 5c4218e5f6
commit 6c449e55bf
13 changed files with 111 additions and 219 deletions

View File

@@ -10,6 +10,7 @@ import (
"github.com/muesli/ansi/compressor"
"github.com/muesli/reflow/truncate"
"github.com/muesli/termenv"
)
const (
@@ -24,7 +25,7 @@ const (
// In cases where very high performance is needed the renderer can be told
// to exclude ranges of lines, allowing them to be written to directly.
type standardRenderer struct {
out io.Writer
out *termenv.Output
buf bytes.Buffer
queuedMessageLines []string
framerate time.Duration
@@ -49,7 +50,7 @@ type standardRenderer struct {
// newRenderer creates a new renderer. Normally you'll want to initialize it
// with os.Stdout as the first argument.
func newRenderer(out io.Writer, mtx *sync.Mutex, useANSICompressor bool) renderer {
func newRenderer(out *termenv.Output, mtx *sync.Mutex, useANSICompressor bool) renderer {
r := &standardRenderer{
out: out,
mtx: mtx,
@@ -58,7 +59,7 @@ func newRenderer(out io.Writer, mtx *sync.Mutex, useANSICompressor bool) rendere
queuedMessageLines: []string{},
}
if r.useANSICompressor {
r.out = &compressor.Writer{Forward: out}
r.out = termenv.NewOutput(&compressor.Writer{Forward: out})
}
return r
}
@@ -75,13 +76,13 @@ func (r *standardRenderer) start() {
// stop permanently halts the renderer, rendering the final frame.
func (r *standardRenderer) stop() {
r.flush()
clearLine(r.out)
r.out.ClearLine()
r.once.Do(func() {
close(r.done)
})
if r.useANSICompressor {
if w, ok := r.out.(io.WriteCloser); ok {
if w, ok := r.out.TTY().(io.WriteCloser); ok {
_ = w.Close()
}
}
@@ -89,7 +90,7 @@ func (r *standardRenderer) stop() {
// kill halts the renderer. The final frame will not be rendered.
func (r *standardRenderer) kill() {
clearLine(r.out)
r.out.ClearLine()
r.once.Do(func() {
close(r.done)
})
@@ -122,7 +123,8 @@ func (r *standardRenderer) flush() {
}
// Output buffer
out := new(bytes.Buffer)
buf := &bytes.Buffer{}
out := termenv.NewOutput(buf)
newLines := strings.Split(r.buf.String(), "\n")
numLinesThisFlush := len(newLines)
@@ -145,10 +147,10 @@ func (r *standardRenderer) flush() {
if (len(newLines) <= len(oldLines)) && (len(newLines) > i && len(oldLines) > i) && (newLines[i] == oldLines[i]) {
skipLines[i] = struct{}{}
} else if _, exists := r.ignoreLines[i]; !exists {
clearLine(out)
out.ClearLine()
}
cursorUp(out)
out.CursorUp(1)
}
if _, exists := r.ignoreLines[0]; !exists {
@@ -161,8 +163,8 @@ func (r *standardRenderer) flush() {
// standard (whereas others are proprietary to, say, VT100/VT52).
// If cursor previous line (ESC[ + <n> + F) were better supported
// we could use that above to eliminate this step.
cursorBack(out, r.width)
clearLine(out)
out.CursorBack(r.width)
out.ClearLine()
}
}
@@ -179,7 +181,7 @@ func (r *standardRenderer) flush() {
if _, skip := skipLines[i]; skip {
// Unless this is the last line, move the cursor down.
if i < len(newLines)-1 {
cursorDown(out)
out.CursorDown(1)
}
} else {
line := newLines[i]
@@ -195,10 +197,10 @@ func (r *standardRenderer) flush() {
line = truncate.String(line, uint(r.width))
}
_, _ = io.WriteString(out, line)
_, _ = out.WriteString(line)
if i < len(newLines)-1 {
_, _ = io.WriteString(out, "\r\n")
_, _ = out.WriteString("\r\n")
}
}
}
@@ -210,12 +212,12 @@ func (r *standardRenderer) flush() {
// This case fixes a bug in macOS terminal. In other terminals the
// other case seems to do the job regardless of whether or not we're
// using the full terminal window.
moveCursor(out, r.linesRendered, 0)
out.MoveCursor(r.linesRendered, 0)
} else {
cursorBack(out, r.width)
out.CursorBack(r.width)
}
_, _ = r.out.Write(out.Bytes())
_, _ = r.out.Write(buf.Bytes())
r.lastRender = r.buf.String()
r.buf.Reset()
}
@@ -270,15 +272,17 @@ func (r *standardRenderer) setIgnoredLines(from int, to int) {
// Erase ignored lines
if r.linesRendered > 0 {
out := new(bytes.Buffer)
buf := &bytes.Buffer{}
out := termenv.NewOutput(buf)
for i := r.linesRendered - 1; i >= 0; i-- {
if _, exists := r.ignoreLines[i]; exists {
clearLine(out)
out.ClearLine()
}
cursorUp(out)
out.CursorUp(1)
}
moveCursor(out, r.linesRendered, 0) // put cursor back
_, _ = r.out.Write(out.Bytes())
out.MoveCursor(r.linesRendered, 0) // put cursor back
_, _ = r.out.Write(buf.Bytes())
}
}
@@ -311,18 +315,19 @@ func (r *standardRenderer) insertTop(lines []string, topBoundary, bottomBoundary
r.mtx.Lock()
defer r.mtx.Unlock()
b := new(bytes.Buffer)
buf := &bytes.Buffer{}
out := termenv.NewOutput(buf)
changeScrollingRegion(b, topBoundary, bottomBoundary)
moveCursor(b, topBoundary, 0)
insertLine(b, len(lines))
_, _ = io.WriteString(b, strings.Join(lines, "\r\n"))
changeScrollingRegion(b, 0, r.height)
out.ChangeScrollingRegion(topBoundary, bottomBoundary)
out.MoveCursor(topBoundary, 0)
out.InsertLines(len(lines))
_, _ = out.WriteString(strings.Join(lines, "\r\n"))
out.ChangeScrollingRegion(0, r.height)
// Move cursor back to where the main rendering routine expects it to be
moveCursor(b, r.linesRendered, 0)
out.MoveCursor(r.linesRendered, 0)
_, _ = r.out.Write(b.Bytes())
_, _ = r.out.Write(buf.Bytes())
}
// insertBottom effectively scrolls down. It inserts lines at the bottom of
@@ -338,17 +343,18 @@ func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBound
r.mtx.Lock()
defer r.mtx.Unlock()
b := new(bytes.Buffer)
buf := &bytes.Buffer{}
out := termenv.NewOutput(buf)
changeScrollingRegion(b, topBoundary, bottomBoundary)
moveCursor(b, bottomBoundary, 0)
_, _ = io.WriteString(b, "\r\n"+strings.Join(lines, "\r\n"))
changeScrollingRegion(b, 0, r.height)
out.ChangeScrollingRegion(topBoundary, bottomBoundary)
out.MoveCursor(bottomBoundary, 0)
_, _ = out.WriteString("\r\n" + strings.Join(lines, "\r\n"))
out.ChangeScrollingRegion(0, r.height)
// Move cursor back to where the main rendering routine expects it to be
moveCursor(b, r.linesRendered, 0)
out.MoveCursor(r.linesRendered, 0)
_, _ = r.out.Write(b.Bytes())
_, _ = r.out.Write(buf.Bytes())
}
// handleMessages handles internal messages for the renderer.