summaryrefslogtreecommitdiff
path: root/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go
diff options
context:
space:
mode:
Diffstat (limited to 'examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go')
-rw-r--r--examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go362
1 files changed, 0 insertions, 362 deletions
diff --git a/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go b/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go
deleted file mode 100644
index 8103ee717..000000000
--- a/examples/go-dashboard/src/github.com/mum4k/termdash/termdash.go
+++ /dev/null
@@ -1,362 +0,0 @@
-// Copyright 2018 Google Inc.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-/*
-Package termdash implements a terminal based dashboard.
-
-While running, the terminal dashboard performs the following:
- - Periodic redrawing of the canvas and all the widgets.
- - Event based redrawing of the widgets (i.e. on Keyboard or Mouse events).
- - Forwards input events to widgets and optional subscribers.
- - Handles terminal resize events.
-*/
-package termdash
-
-import (
- "context"
- "errors"
- "fmt"
- "sync"
- "time"
-
- "github.com/mum4k/termdash/container"
- "github.com/mum4k/termdash/private/event"
- "github.com/mum4k/termdash/terminal/terminalapi"
-)
-
-// DefaultRedrawInterval is the default for the RedrawInterval option.
-const DefaultRedrawInterval = 250 * time.Millisecond
-
-// Option is used to provide options.
-type Option interface {
- // set sets the provided option.
- set(td *termdash)
-}
-
-// option implements Option.
-type option func(td *termdash)
-
-// set implements Option.set.
-func (o option) set(td *termdash) {
- o(td)
-}
-
-// RedrawInterval sets how often termdash redraws the container and all the widgets.
-// Defaults to DefaultRedrawInterval. Use the controller to disable the
-// periodic redraw.
-func RedrawInterval(t time.Duration) Option {
- return option(func(td *termdash) {
- td.redrawInterval = t
- })
-}
-
-// ErrorHandler is used to provide a function that will be called with all
-// errors that occur while the dashboard is running. If not provided, any
-// errors panic the application.
-// The provided function must be thread-safe.
-func ErrorHandler(f func(error)) Option {
- return option(func(td *termdash) {
- td.errorHandler = f
- })
-}
-
-// KeyboardSubscriber registers a subscriber for Keyboard events. Each
-// keyboard event is forwarded to the container and the registered subscriber.
-// The provided function must be thread-safe.
-func KeyboardSubscriber(f func(*terminalapi.Keyboard)) Option {
- return option(func(td *termdash) {
- td.keyboardSubscriber = f
- })
-}
-
-// MouseSubscriber registers a subscriber for Mouse events. Each mouse event
-// is forwarded to the container and the registered subscriber.
-// The provided function must be thread-safe.
-func MouseSubscriber(f func(*terminalapi.Mouse)) Option {
- return option(func(td *termdash) {
- td.mouseSubscriber = f
- })
-}
-
-// withEDS indicates that termdash should run with the provided event
-// distribution system instead of creating one.
-// Useful for tests.
-func withEDS(eds *event.DistributionSystem) Option {
- return option(func(td *termdash) {
- td.eds = eds
- })
-}
-
-// Run runs the terminal dashboard with the provided container on the terminal.
-// Redraws the terminal periodically. If you prefer a manual redraw, use the
-// Controller instead.
-// Blocks until the context expires.
-func Run(ctx context.Context, t terminalapi.Terminal, c *container.Container, opts ...Option) error {
- td := newTermdash(t, c, opts...)
-
- err := td.start(ctx)
- // Only return the status (error or nil) after the termdash event
- // processing goroutine actually exits.
- td.stop()
- return err
-}
-
-// Controller controls a termdash instance.
-// The controller instance is only valid until Close() is called.
-// The controller is not thread-safe.
-type Controller struct {
- td *termdash
- cancel context.CancelFunc
-}
-
-// NewController initializes termdash and returns an instance of the controller.
-// Periodic redrawing is disabled when using the controller, the RedrawInterval
-// option is ignored.
-// Close the controller when it isn't needed anymore.
-func NewController(t terminalapi.Terminal, c *container.Container, opts ...Option) (*Controller, error) {
- ctx, cancel := context.WithCancel(context.Background())
- ctrl := &Controller{
- td: newTermdash(t, c, opts...),
- cancel: cancel,
- }
-
- // stops when Close() is called.
- go ctrl.td.processEvents(ctx)
- if err := ctrl.td.periodicRedraw(); err != nil {
- return nil, err
- }
- return ctrl, nil
-}
-
-// Redraw triggers redraw of the terminal.
-func (c *Controller) Redraw() error {
- if c.td == nil {
- return errors.New("the termdash instance is no longer running, this controller is now invalid")
- }
-
- c.td.mu.Lock()
- defer c.td.mu.Unlock()
- return c.td.redraw()
-}
-
-// Close closes the Controller and its termdash instance.
-func (c *Controller) Close() {
- c.cancel()
- c.td.stop()
- c.td = nil
-}
-
-// termdash is a terminal based dashboard.
-// This object is thread-safe.
-type termdash struct {
- // term is the terminal the dashboard runs on.
- term terminalapi.Terminal
-
- // container maintains terminal splits and places widgets.
- container *container.Container
-
- // eds distributes input events to subscribers.
- eds *event.DistributionSystem
-
- // closeCh gets closed when Stop() is called, which tells the event
- // collecting goroutine to exit.
- closeCh chan struct{}
- // exitCh gets closed when the event collecting goroutine actually exits.
- exitCh chan struct{}
-
- // clearNeeded indicates if the terminal needs to be cleared next time
- // we're drawing it. Terminal needs to be cleared if its sized changed.
- clearNeeded bool
-
- // mu protects termdash.
- mu sync.Mutex
-
- // Options.
- redrawInterval time.Duration
- errorHandler func(error)
- mouseSubscriber func(*terminalapi.Mouse)
- keyboardSubscriber func(*terminalapi.Keyboard)
-}
-
-// newTermdash creates a new termdash.
-func newTermdash(t terminalapi.Terminal, c *container.Container, opts ...Option) *termdash {
- td := &termdash{
- term: t,
- container: c,
- eds: event.NewDistributionSystem(),
- closeCh: make(chan struct{}),
- exitCh: make(chan struct{}),
- redrawInterval: DefaultRedrawInterval,
- }
-
- for _, opt := range opts {
- opt.set(td)
- }
- td.subscribers()
- c.Subscribe(td.eds)
- return td
-}
-
-// subscribers subscribes event receivers that live in this package to EDS.
-func (td *termdash) subscribers() {
- // Handler for all errors that occur during input event processing.
- td.eds.Subscribe([]terminalapi.Event{terminalapi.NewError("")}, func(ev terminalapi.Event) {
- td.handleError(ev.(*terminalapi.Error).Error())
- })
-
- // Handles terminal resize events.
- td.eds.Subscribe([]terminalapi.Event{&terminalapi.Resize{}}, func(terminalapi.Event) {
- td.setClearNeeded()
- })
-
- // Redraws the screen on Keyboard and Mouse events.
- // These events very likely change the content of the widgets (e.g. zooming
- // a LineChart) so a redraw is needed to make that visible.
- td.eds.Subscribe([]terminalapi.Event{
- &terminalapi.Keyboard{},
- &terminalapi.Mouse{},
- }, func(terminalapi.Event) {
- td.evRedraw()
- }, event.MaxRepetitive(0)) // No repetitive events that cause terminal redraw.
-
- // Keyboard and Mouse subscribers specified via options.
- if td.keyboardSubscriber != nil {
- td.eds.Subscribe([]terminalapi.Event{&terminalapi.Keyboard{}}, func(ev terminalapi.Event) {
- td.keyboardSubscriber(ev.(*terminalapi.Keyboard))
- })
- }
- if td.mouseSubscriber != nil {
- td.eds.Subscribe([]terminalapi.Event{&terminalapi.Mouse{}}, func(ev terminalapi.Event) {
- td.mouseSubscriber(ev.(*terminalapi.Mouse))
- })
- }
-}
-
-// handleError forwards the error to the error handler if one was
-// provided or panics.
-func (td *termdash) handleError(err error) {
- if td.errorHandler != nil {
- td.errorHandler(err)
- } else {
- panic(err)
- }
-}
-
-// setClearNeeded flags that the terminal needs to be cleared next time we're
-// drawing it.
-func (td *termdash) setClearNeeded() {
- td.mu.Lock()
- defer td.mu.Unlock()
- td.clearNeeded = true
-}
-
-// redraw redraws the container and its widgets.
-// The caller must hold td.mu.
-func (td *termdash) redraw() error {
- if td.clearNeeded {
- if err := td.term.Clear(); err != nil {
- return fmt.Errorf("term.Clear => error: %v", err)
- }
- td.clearNeeded = false
- }
-
- if err := td.container.Draw(); err != nil {
- return fmt.Errorf("container.Draw => error: %v", err)
- }
-
- if err := td.term.Flush(); err != nil {
- return fmt.Errorf("term.Flush => error: %v", err)
- }
- return nil
-}
-
-// evRedraw redraws the container and its widgets.
-func (td *termdash) evRedraw() error {
- td.mu.Lock()
- defer td.mu.Unlock()
-
- // Don't redraw immediately, give widgets that are performing enough time
- // to update.
- // We don't want to actually synchronize until all widgets update, we are
- // purposefully leaving slow widgets behind.
- time.Sleep(25 * time.Millisecond)
- return td.redraw()
-}
-
-// periodicRedraw is called once each RedrawInterval.
-func (td *termdash) periodicRedraw() error {
- td.mu.Lock()
- defer td.mu.Unlock()
- return td.redraw()
-}
-
-// processEvents processes terminal input events.
-// This is the body of the event collecting goroutine.
-func (td *termdash) processEvents(ctx context.Context) {
- defer close(td.exitCh)
-
- for {
- ev := td.term.Event(ctx)
- if ev != nil {
- td.eds.Event(ev)
- }
-
- select {
- case <-ctx.Done():
- return
- default:
- }
- }
-}
-
-// start starts the terminal dashboard. Blocks until the context expires or
-// until stop() is called.
-func (td *termdash) start(ctx context.Context) error {
- // Redraw once to initialize the container sizes.
- if err := td.periodicRedraw(); err != nil {
- close(td.exitCh)
- return err
- }
-
- redrawTimer := time.NewTicker(td.redrawInterval)
- defer redrawTimer.Stop()
-
- ctx, cancel := context.WithCancel(ctx)
- defer cancel()
-
- // stops when stop() is called or the context expires.
- go td.processEvents(ctx)
-
- for {
- select {
- case <-redrawTimer.C:
- if err := td.periodicRedraw(); err != nil {
- return err
- }
-
- case <-ctx.Done():
- return nil
-
- case <-td.closeCh:
- return nil
- }
- }
-}
-
-// stop tells the event collecting goroutine to stop.
-// Blocks until it exits.
-func (td *termdash) stop() {
- close(td.closeCh)
- <-td.exitCh
-}