Termdash Documentation

repository·master·Indexed 25 days ago

https://github.com/mum4k/termdash

A cross-platform, customizable terminal-based dashboard library for Go. Termdash provides a rich set of widgets—including charts, gauges, and interactive inputs—and layout capabilities to build complex terminal user interfaces. The library is organized into a terminal layer, an infrastructure layer for event distribution and resizing, and a widget layer for UI components.

Tokens
4.2K
Snippets
31
Records
44
Agent score
84%

What's inside termdash

  1. Understand the Termdash architecture

    master

    Termdash is organized into three distinct layers:

    • Terminal Layer: An abstraction over terminal implementations (e.g., real terminal, fake terminal for testing, or image export). This layer is private and not intended for direct user interaction.
    • Infrastructure Layer: Manages container hierarchy, keyboard/mouse focus, and external events like terminal resizing. It handles the Event Distribution System (EDS) and decides when to flush the back buffer to the terminal.
    • Widgets Layer: Contains individual widget implementations. Widgets receive a Canvas from their parent container to draw content and can optionally register for input events (keyboard/mouse) which are forwarded by the infrastructure layer.

    Users interact primarily with the Widget API to construct components and the Container API to arrange them on the dashboard.

  2. Explore Termdash widgets

    master

    Termdash includes a wide variety of built-in widgets. You can run individual demos for each widget to see how they function. Examples include:

    • Button: Interactive buttons with callbacks.
    • TextInput: Text entry and editing, including form support.
    • Gauge: Progress indicators.
    • Pie/Donut: Data visualization charts.
    • Charts: SparkLine, BarChart, and LineChart.
    • Navigation/Selection: Checkbox, Dropdown, Radio, Slider, and Tabs.
    • Advanced: TreeView, HeatMap, Spectrum, Radar, and 3D models.
  3. Handle terminal resizing and widget constraints

    master

    The infrastructure layer manages terminal resizing by respecting widget constraints:

    1. Each widget specifies its desired size and minimum size when registering with its parent container.
    2. The parent container informs the widget of its actual canvas size.
    3. The infrastructure guarantees that the actual size will never be smaller than the widget's advertised minimum and will maintain the requested aspect ratio.
    4. Upon resize, the infrastructure resizes all containers, instructs widgets to redraw their canvases, and flushes the buffer to the terminal.
  4. Implement the widgetapi.Widget interface

    master
    To create a new widget in termdash, you must implement the widgetapi.Widget interface. Beyond the interface requirements, widgets typically expose custom methods to allow users to update the widget's content (for example, a gauge widget would have a method to set the displayed percentage).
  5. Handle widget scaling and resizing

    master

    Widgets must determine the size of the provided canvas during every Draw() call and scale their content accordingly. This ensures the widget responds correctly to terminal resizing and changes in container dimensions.

    If the widget's required size (which might depend on dynamic user data) exceeds the current canvas size, you should return draw.ResizeNeeded(cvs) to indicate a resize is necessary.

    func (w *Widget) Draw(cvs *canvas.Canvas, meta *widgetapi.Meta) error {
      min := w.minSize() // Output depends on the current state.
      needAr, err := area.FromSize(min)
      if err != nil {
        return err
      }
      if !needAr.In(cvs.Area()) {
        return draw.ResizeNeeded(cvs)
      }
    
      // Draw the widget.
      return nil
    }
  6. Unit test widgets using faketerm

    master

    Use the faketerm package to unit test widgets. faketerm provides an in-memory terminal and canvas implementation. You can use faketerm.Diff to compare the actual output of a widget against an expected terminal state, providing human-readable diffs for test failures.

    func TestWidget(t *testing.T) {
      tests := []struct {
        desc    string
        canvas  image.Rectangle
        meta    *widgetapi.Meta
        opts    []Option
        want    func(size image.Point) *faketerm.Terminal
        wantErr bool
      }{
        {
          desc: "a test case",
          // The metadata widget receives when drawn.
          meta: &widgetapi.Meta{},
          // canvas determines the size of the allocated canvas in the test case.
          canvas: image.Rect(0,0,10,10),
          // want creates the expected content on the fake terminal.
          want: func(size image.Point) *faketerm.Terminal {
            ft := faketerm.MustNew(size)
            c := testcanvas.MustNew(ft.Area())
    
            // Utilize functions in the testdraw package to create the expected content.
            testcanvas.MustApply(c, ft)
            return ft
          },
        },
      }
    
      for _, tc := range tests {
        t.Run(tc.desc, func(t *testing.T) {
          c, err := canvas.New(tc.canvas)
          if err != nil {
            t.Fatalf("canvas.New => unexpected error: %v", err)
          }
    
          widget := New()
          err = widget.Draw(c, tc.meta)
          if (err != nil) != tc.wantErr {
            t.Errorf("Draw => unexpected error: %v, wantErr: %v", err, tc.wantErr)
          }
          if err != nil {
            return
          }
    
          got, err := faketerm.New(c.Size())
          if err != nil {
            t.Fatalf("faketerm.New => unexpected error: %v", err)
          }
    
          if err := c.Apply(got); err != nil {
            t.Fatalf("Apply => unexpected error: %v", err)
          }
    
          if diff := faketerm.Diff(tc.want(c.Size()), got); diff != "" {
            t.Errorf("Draw => %v", diff)
          }
        })
      }
    }
  7. Configure widget size limits with widgetapi.Options

    master

    Use widgetapi.Options to set MinimumSize and MaximumSize for your widget to handle terminal resizing gracefully:

    • MinimumSize: If the terminal or container split results in a canvas smaller than the configured MinimumSize, the infrastructure will skip calling the Draw() method.
    • MaximumSize: If the container results in a canvas larger than the MaximumSize, the canvas will be limited to that specified size. You can limit both width and height, or just one of them.

    Note: If your Options() return values are dynamic (depend on user data), you must manually protect against the scenario where the canvas provided to Draw() does not match the values returned by Options(), as the infrastructure cannot guarantee data consistency between these two calls.