mirror of
https://github.com/taigrr/bubbletea.git
synced 2026-04-02 02:59:09 -07:00
feat: use Termenv.Output to write to tty
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user