Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose the Renderer #246

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 8 additions & 7 deletions nil_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package tea

type nilRenderer struct{}

func (n nilRenderer) start() {}
func (n nilRenderer) stop() {}
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) Start() {}
func (n nilRenderer) Stop() {}
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) HandleMessages(msg Msg) {}
14 changes: 7 additions & 7 deletions nil_renderer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import "testing"

func TestNilRenderer(t *testing.T) {
r := nilRenderer{}
r.start()
r.stop()
r.kill()
r.write("a")
r.repaint()
r.setAltScreen(true)
if r.altScreen() {
r.Start()
r.Stop()
r.Kill()
r.Write("a")
r.Repaint()
r.SetAltScreen(true)
if r.AltScreen() {
t.Errorf("altScreen should always return false")
}
}
10 changes: 10 additions & 0 deletions options.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,16 @@ func WithoutRenderer() ProgramOption {
}
}

// WithRenderer overrides the standard renderer with a user-provided one.
//
// For advanced usage only, when trying to override rendering / redrawing
// logic or extend the renderer
func WithRenderer(renderer Renderer) ProgramOption {
return func(m *Program) {
m.renderer = renderer
}
}

// WithANSICompressor removes redundant ANSI sequences to produce potentially
// smaller output, at the cost of some processing overhead.
//
Expand Down
20 changes: 11 additions & 9 deletions renderer.go
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
package tea

// renderer is the interface for Bubble Tea renderers.
type renderer interface {
// Renderer is the interface for Bubble Tea renderers.
type Renderer interface {
// Start the renderer.
start()
Start()

// Stop the renderer, but render the final frame in the buffer, if any.
stop()
Stop()

// Stop the renderer without doing any final rendering.
kill()
Kill()

// Write a frame to the renderer. The renderer can write this data to
// output at its discretion.
write(string)
Write(string)

// Request a full re-render.
repaint()
Repaint()

// Whether or not the alternate screen buffer is enabled.
altScreen() bool
AltScreen() bool

// Record internally that the alternate screen buffer is enabled. This
// does not actually toggle the alternate screen buffer.
setAltScreen(bool)
SetAltScreen(bool)

HandleMessages(Msg)
}
20 changes: 10 additions & 10 deletions standard_renderer.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,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 io.Writer, mtx *sync.Mutex, useANSICompressor bool) Renderer {
r := &standardRenderer{
out: out,
mtx: mtx,
Expand All @@ -61,7 +61,7 @@ func newRenderer(out io.Writer, mtx *sync.Mutex, useANSICompressor bool) rendere
}

// start starts the renderer.
func (r *standardRenderer) start() {
func (r *standardRenderer) Start() {
if r.ticker == nil {
r.ticker = time.NewTicker(r.framerate)
}
Expand All @@ -70,7 +70,7 @@ func (r *standardRenderer) start() {
}

// stop permanently halts the renderer, rendering the final frame.
func (r *standardRenderer) stop() {
func (r *standardRenderer) Stop() {
r.flush()
clearLine(r.out)
r.once.Do(func() {
Expand All @@ -85,7 +85,7 @@ func (r *standardRenderer) stop() {
}

// kill halts the renderer. The final frame will not be rendered.
func (r *standardRenderer) kill() {
func (r *standardRenderer) Kill() {
clearLine(r.out)
r.once.Do(func() {
close(r.done)
Expand Down Expand Up @@ -213,7 +213,7 @@ func (r *standardRenderer) flush() {

// write writes to the internal buffer. The buffer will be outputted via the
// ticker which calls flush().
func (r *standardRenderer) write(s string) {
func (r *standardRenderer) Write(s string) {
r.mtx.Lock()
defer r.mtx.Unlock()
r.buf.Reset()
Expand All @@ -229,17 +229,17 @@ func (r *standardRenderer) write(s string) {
_, _ = r.buf.WriteString(s)
}

func (r *standardRenderer) repaint() {
func (r *standardRenderer) Repaint() {
r.lastRender = ""
}

func (r *standardRenderer) altScreen() bool {
func (r *standardRenderer) AltScreen() bool {
return r.altScreenActive
}

func (r *standardRenderer) setAltScreen(v bool) {
func (r *standardRenderer) SetAltScreen(v bool) {
r.altScreenActive = v
r.repaint()
r.Repaint()
}

// setIgnoredLines specifies lines not to be touched by the standard Bubble Tea
Expand Down Expand Up @@ -343,7 +343,7 @@ func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBound
}

// handleMessages handles internal messages for the renderer.
func (r *standardRenderer) handleMessages(msg Msg) {
func (r *standardRenderer) HandleMessages(msg Msg) {
switch msg := msg.(type) {
case WindowSizeMsg:
r.mtx.Lock()
Expand Down
26 changes: 12 additions & 14 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ type Program struct {

output io.Writer // where to send output. this will usually be os.Stdout.
input io.Reader // this will usually be os.Stdin.
renderer renderer
renderer Renderer
altScreenActive bool

// CatchPanics is incredibly useful for restoring the terminal to a usable
Expand Down Expand Up @@ -398,11 +398,11 @@ func (p *Program) StartReturningModel() (Model, error) {
}

// Start the renderer.
p.renderer.start()
p.renderer.setAltScreen(p.altScreenActive)
p.renderer.Start()
p.renderer.SetAltScreen(p.altScreenActive)

// Render the initial view.
p.renderer.write(model.View())
p.renderer.Write(model.View())

cancelReader, err := newCancelReader(p.input)
if err != nil {
Expand Down Expand Up @@ -516,7 +516,7 @@ func (p *Program) StartReturningModel() (Model, error) {
continue

case WindowSizeMsg:
p.renderer.repaint()
p.renderer.Repaint()

case enterAltScreenMsg:
p.EnterAltScreen()
Expand All @@ -538,15 +538,13 @@ func (p *Program) StartReturningModel() (Model, error) {
hideCursor(p.output)
}

// Process internal messages for the renderer.
if r, ok := p.renderer.(*standardRenderer); ok {
r.handleMessages(msg)
}
// Process messages for the renderer.
p.renderer.HandleMessages(msg)

var cmd Cmd
model, cmd = model.Update(msg) // run update
cmds <- cmd // process command (if any)
p.renderer.write(model.View()) // send view to renderer
p.renderer.Write(model.View()) // send view to renderer
}
}
}
Expand Down Expand Up @@ -599,9 +597,9 @@ func (p *Program) Kill() {
func (p *Program) shutdown(kill bool) {
if p.renderer != nil {
if kill {
p.renderer.kill()
p.renderer.Kill()
} else {
p.renderer.stop()
p.renderer.Stop()
}
}
p.ExitAltScreen()
Expand All @@ -627,7 +625,7 @@ func (p *Program) EnterAltScreen() {

p.altScreenActive = true
if p.renderer != nil {
p.renderer.setAltScreen(p.altScreenActive)
p.renderer.SetAltScreen(p.altScreenActive)
}
}

Expand All @@ -646,7 +644,7 @@ func (p *Program) ExitAltScreen() {

p.altScreenActive = false
if p.renderer != nil {
p.renderer.setAltScreen(p.altScreenActive)
p.renderer.SetAltScreen(p.altScreenActive)
}
}

Expand Down