forked from Mirrors/bubbletea
fix: move output handling to renderer
This commit is contained in:
parent
7cf0d54bd4
commit
ea36e19bee
|
@ -8,4 +8,11 @@ func (n nilRenderer) kill() {}
|
|||
func (n nilRenderer) write(v string) {}
|
||||
func (n nilRenderer) repaint() {}
|
||||
func (n nilRenderer) altScreen() bool { return false }
|
||||
func (n nilRenderer) setAltScreen(v bool) {}
|
||||
func (n nilRenderer) enterAltScreen() {}
|
||||
func (n nilRenderer) exitAltScreen() {}
|
||||
func (n nilRenderer) showCursor() {}
|
||||
func (n nilRenderer) hideCursor() {}
|
||||
func (n nilRenderer) enableMouseCellMotion() {}
|
||||
func (n nilRenderer) disableMouseCellMotion() {}
|
||||
func (n nilRenderer) enableMouseAllMotion() {}
|
||||
func (n nilRenderer) disableMouseAllMotion() {}
|
||||
|
|
|
@ -9,7 +9,7 @@ func TestNilRenderer(t *testing.T) {
|
|||
r.kill()
|
||||
r.write("a")
|
||||
r.repaint()
|
||||
r.setAltScreen(true)
|
||||
r.enterAltScreen()
|
||||
if r.altScreen() {
|
||||
t.Errorf("altScreen should always return false")
|
||||
}
|
||||
|
|
26
renderer.go
26
renderer.go
|
@ -23,10 +23,30 @@ type renderer interface {
|
|||
|
||||
// Whether or not the alternate screen buffer is enabled.
|
||||
altScreen() bool
|
||||
// Enable the alternate screen buffer.
|
||||
enterAltScreen()
|
||||
// Disable the alternate screen buffer.
|
||||
exitAltScreen()
|
||||
|
||||
// Record internally that the alternate screen buffer is enabled. This
|
||||
// does not actually toggle the alternate screen buffer.
|
||||
setAltScreen(bool)
|
||||
// Show the cursor.
|
||||
showCursor()
|
||||
// Hide the cursor.
|
||||
hideCursor()
|
||||
|
||||
// enableMouseCellMotion enables mouse click, release, wheel and motion
|
||||
// events if a mouse button is pressed (i.e., drag events).
|
||||
enableMouseCellMotion()
|
||||
|
||||
// DisableMouseCellMotion disables Mouse Cell Motion tracking.
|
||||
disableMouseCellMotion()
|
||||
|
||||
// EnableMouseAllMotion enables mouse click, release, wheel and motion
|
||||
// events, regardless of whether a mouse button is pressed. Many modern
|
||||
// terminals support this, but not all.
|
||||
enableMouseAllMotion()
|
||||
|
||||
// DisableMouseAllMotion disables All Motion mouse tracking.
|
||||
disableMouseAllMotion()
|
||||
}
|
||||
|
||||
// repaintMsg forces a full repaint.
|
||||
|
|
|
@ -25,12 +25,13 @@ 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 {
|
||||
mtx *sync.Mutex
|
||||
out *termenv.Output
|
||||
|
||||
buf bytes.Buffer
|
||||
queuedMessageLines []string
|
||||
framerate time.Duration
|
||||
ticker *time.Ticker
|
||||
mtx *sync.Mutex
|
||||
done chan struct{}
|
||||
lastRender string
|
||||
linesRendered int
|
||||
|
@ -50,10 +51,10 @@ 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 *termenv.Output, mtx *sync.Mutex, useANSICompressor bool) renderer {
|
||||
func newRenderer(out *termenv.Output, useANSICompressor bool) renderer {
|
||||
r := &standardRenderer{
|
||||
out: out,
|
||||
mtx: mtx,
|
||||
mtx: &sync.Mutex{},
|
||||
framerate: defaultFramerate,
|
||||
useANSICompressor: useANSICompressor,
|
||||
queuedMessageLines: []string{},
|
||||
|
@ -248,11 +249,69 @@ func (r *standardRenderer) altScreen() bool {
|
|||
return r.altScreenActive
|
||||
}
|
||||
|
||||
func (r *standardRenderer) setAltScreen(v bool) {
|
||||
r.altScreenActive = v
|
||||
func (r *standardRenderer) enterAltScreen() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.altScreenActive = true
|
||||
|
||||
r.out.AltScreen()
|
||||
r.out.MoveCursor(1, 1)
|
||||
r.repaint()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) exitAltScreen() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.altScreenActive = false
|
||||
|
||||
r.out.ExitAltScreen()
|
||||
r.repaint()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) showCursor() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.ShowCursor()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) hideCursor() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.HideCursor()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) enableMouseCellMotion() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.EnableMouseCellMotion()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) disableMouseCellMotion() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.DisableMouseCellMotion()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) enableMouseAllMotion() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.EnableMouseAllMotion()
|
||||
}
|
||||
|
||||
func (r *standardRenderer) disableMouseAllMotion() {
|
||||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
r.out.DisableMouseAllMotion()
|
||||
}
|
||||
|
||||
// setIgnoredLines specifies lines not to be touched by the standard Bubble Tea
|
||||
// renderer.
|
||||
func (r *standardRenderer) setIgnoredLines(from int, to int) {
|
||||
|
@ -371,6 +430,7 @@ func (r *standardRenderer) handleMessages(msg Msg) {
|
|||
r.mtx.Lock()
|
||||
r.width = msg.Width
|
||||
r.height = msg.Height
|
||||
r.repaint()
|
||||
r.mtx.Unlock()
|
||||
|
||||
case clearScrollAreaMsg:
|
||||
|
|
113
tea.go
113
tea.go
|
@ -16,7 +16,6 @@ import (
|
|||
"os"
|
||||
"os/signal"
|
||||
"runtime/debug"
|
||||
"sync"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -83,7 +82,6 @@ type Program struct {
|
|||
startupOptions startupOptions
|
||||
|
||||
ctx context.Context
|
||||
mtx *sync.Mutex
|
||||
|
||||
msgs chan Msg
|
||||
errs chan error
|
||||
|
@ -95,7 +93,6 @@ type Program struct {
|
|||
cancelReader cancelreader.CancelReader
|
||||
|
||||
renderer renderer
|
||||
altScreenActive bool
|
||||
altScreenWasActive bool // was the altscreen active before releasing the terminal?
|
||||
|
||||
// CatchPanics is incredibly useful for restoring the terminal to a usable
|
||||
|
@ -252,7 +249,6 @@ type hideCursorMsg struct{}
|
|||
// NewProgram creates a new Program.
|
||||
func NewProgram(model Model, opts ...ProgramOption) *Program {
|
||||
p := &Program{
|
||||
mtx: &sync.Mutex{},
|
||||
initialModel: model,
|
||||
input: os.Stdin,
|
||||
msgs: make(chan Msg),
|
||||
|
@ -389,17 +385,17 @@ func (p *Program) StartReturningModel() (Model, error) {
|
|||
|
||||
// If no renderer is set use the standard one.
|
||||
if p.renderer == nil {
|
||||
p.renderer = newRenderer(p.output, p.mtx, p.startupOptions.has(withANSICompressor))
|
||||
p.renderer = newRenderer(p.output, p.startupOptions.has(withANSICompressor))
|
||||
}
|
||||
|
||||
// Honor program startup options.
|
||||
if p.startupOptions&withAltScreen != 0 {
|
||||
p.EnterAltScreen()
|
||||
p.renderer.enterAltScreen()
|
||||
}
|
||||
if p.startupOptions&withMouseCellMotion != 0 {
|
||||
p.EnableMouseCellMotion()
|
||||
p.renderer.enableMouseCellMotion()
|
||||
} else if p.startupOptions&withMouseAllMotion != 0 {
|
||||
p.EnableMouseAllMotion()
|
||||
p.renderer.enableMouseAllMotion()
|
||||
}
|
||||
|
||||
// Initialize the program.
|
||||
|
@ -418,7 +414,6 @@ func (p *Program) StartReturningModel() (Model, error) {
|
|||
|
||||
// Start the renderer.
|
||||
p.renderer.start()
|
||||
p.renderer.setAltScreen(p.altScreenActive)
|
||||
|
||||
// Render the initial view.
|
||||
p.renderer.write(model.View())
|
||||
|
@ -503,40 +498,35 @@ func (p *Program) StartReturningModel() (Model, error) {
|
|||
p.shutdown(false)
|
||||
return model, nil
|
||||
|
||||
case enterAltScreenMsg:
|
||||
p.renderer.enterAltScreen()
|
||||
|
||||
case exitAltScreenMsg:
|
||||
p.renderer.exitAltScreen()
|
||||
|
||||
case enableMouseCellMotionMsg:
|
||||
p.renderer.enableMouseCellMotion()
|
||||
|
||||
case enableMouseAllMotionMsg:
|
||||
p.renderer.enableMouseAllMotion()
|
||||
|
||||
case disableMouseMsg:
|
||||
p.renderer.disableMouseCellMotion()
|
||||
p.renderer.disableMouseAllMotion()
|
||||
|
||||
case hideCursorMsg:
|
||||
p.renderer.hideCursor()
|
||||
|
||||
case execMsg:
|
||||
// NB: this blocks.
|
||||
p.exec(msg.cmd, msg.fn)
|
||||
|
||||
case batchMsg:
|
||||
for _, cmd := range msg {
|
||||
cmds <- cmd
|
||||
}
|
||||
continue
|
||||
|
||||
case WindowSizeMsg:
|
||||
p.mtx.Lock()
|
||||
p.renderer.repaint()
|
||||
p.mtx.Unlock()
|
||||
|
||||
case enterAltScreenMsg:
|
||||
p.EnterAltScreen()
|
||||
|
||||
case exitAltScreenMsg:
|
||||
p.ExitAltScreen()
|
||||
|
||||
case enableMouseCellMotionMsg:
|
||||
p.EnableMouseCellMotion()
|
||||
|
||||
case enableMouseAllMotionMsg:
|
||||
p.EnableMouseAllMotion()
|
||||
|
||||
case disableMouseMsg:
|
||||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
|
||||
case hideCursorMsg:
|
||||
p.output.HideCursor()
|
||||
|
||||
case execMsg:
|
||||
// NB: this blocks.
|
||||
p.exec(msg.cmd, msg.fn)
|
||||
|
||||
case sequenceMsg:
|
||||
go func() {
|
||||
// Execute commands one at a time, in order.
|
||||
|
@ -621,19 +611,8 @@ func (p *Program) shutdown(kill bool) {
|
|||
//
|
||||
// Deprecated: Use the WithAltScreen ProgramOption instead.
|
||||
func (p *Program) EnterAltScreen() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
if p.altScreenActive {
|
||||
return
|
||||
}
|
||||
|
||||
p.output.AltScreen()
|
||||
p.output.MoveCursor(1, 1)
|
||||
|
||||
p.altScreenActive = true
|
||||
if p.renderer != nil {
|
||||
p.renderer.setAltScreen(p.altScreenActive)
|
||||
p.renderer.enterAltScreen()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -641,18 +620,8 @@ func (p *Program) EnterAltScreen() {
|
|||
//
|
||||
// Deprecated: The altscreen will exited automatically when the program exits.
|
||||
func (p *Program) ExitAltScreen() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
if !p.altScreenActive {
|
||||
return
|
||||
}
|
||||
|
||||
p.output.ExitAltScreen()
|
||||
|
||||
p.altScreenActive = false
|
||||
if p.renderer != nil {
|
||||
p.renderer.setAltScreen(p.altScreenActive)
|
||||
p.renderer.exitAltScreen()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -661,10 +630,7 @@ func (p *Program) ExitAltScreen() {
|
|||
//
|
||||
// Deprecated: Use the WithMouseCellMotion ProgramOption instead.
|
||||
func (p *Program) EnableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
p.output.EnableMouseCellMotion()
|
||||
p.renderer.enableMouseCellMotion()
|
||||
}
|
||||
|
||||
// DisableMouseCellMotion disables Mouse Cell Motion tracking. This will be
|
||||
|
@ -672,10 +638,7 @@ func (p *Program) EnableMouseCellMotion() {
|
|||
//
|
||||
// Deprecated: The mouse will automatically be disabled when the program exits.
|
||||
func (p *Program) DisableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
p.output.DisableMouseCellMotion()
|
||||
p.renderer.disableMouseCellMotion()
|
||||
}
|
||||
|
||||
// EnableMouseAllMotion enables mouse click, release, wheel and motion events,
|
||||
|
@ -684,10 +647,7 @@ func (p *Program) DisableMouseCellMotion() {
|
|||
//
|
||||
// Deprecated: Use the WithMouseAllMotion ProgramOption instead.
|
||||
func (p *Program) EnableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
p.output.EnableMouseAllMotion()
|
||||
p.renderer.enableMouseAllMotion()
|
||||
}
|
||||
|
||||
// DisableMouseAllMotion disables All Motion mouse tracking. This will be
|
||||
|
@ -695,10 +655,7 @@ func (p *Program) EnableMouseAllMotion() {
|
|||
//
|
||||
// Deprecated: The mouse will automatically be disabled when the program exits.
|
||||
func (p *Program) DisableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
|
||||
p.output.DisableMouseAllMotion()
|
||||
p.renderer.disableMouseAllMotion()
|
||||
}
|
||||
|
||||
// ReleaseTerminal restores the original terminal state and cancels the input
|
||||
|
@ -708,8 +665,8 @@ func (p *Program) ReleaseTerminal() error {
|
|||
p.cancelInput()
|
||||
p.waitForReadLoop()
|
||||
|
||||
p.altScreenWasActive = p.altScreenActive
|
||||
if p.altScreenActive {
|
||||
p.altScreenWasActive = p.renderer.altScreen()
|
||||
if p.renderer.altScreen() {
|
||||
p.ExitAltScreen()
|
||||
time.Sleep(time.Millisecond * 10) // give the terminal a moment to catch up
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue