peanutbutter

package module
v0.0.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Feb 26, 2025 License: MIT Imports: 10 Imported by: 0

README

Pannelbubble

peanutbutter is a component library for bubbletea that provides the ability to compose terminal applications with multiple panels. The panels can be organized in a hierarchy, and the library provides mechanisms for focus management, contextual help, and global and local shortcuts. An initial implementation is provided for dynamic resizing of panels.

Design Notes

In order to make it easy to use bubble-tea components inside panels, the following concepts are introduced:

  1. Auto-Routing: Output tea.Msgs that are tea.Cmds generated by the children of a panel are auto-routed to the parent panel, which passes them back to the child for processing. This ensures that messages are not broadcasted to ui elements in other panels, that may potentially handle the same message.
  2. Shortcuts: Panels can be configured with global and local shortcuts. When a global shortcut is triggered, the parent panel can decide how to handle it - for example, by changing focus to a panel that implements the relevant workflow.
  3. MsgToParent: This goes slightly against the usual bubbletea pattern of messages being passed to the parent from the child. In the case where a panel wants to handle a message that the parent panel is also interested in, the panel can pass the message to the parent using this method. The parent can then decide how to handle it - for example, by passing it to another child panel. This avoids the need to pass the message down the hierarchy. This helps implement features like focus passing, where for example, pressing the down arrow key to focus the next item in a list, should focus the next item in the current panel if we're already at the last item, or focus the first item in the next panel.
  4. Contextual Help: When a panel is focused, it can send a message to the parent to display contextual help. The parent can decide to display it or not.
  5. Workflow Focus Movement: Panels can be configured to enable focus movement on key strokes. For example, a panel can be configured to move focus to the next panel when the down arrow key is pressed, and to the previous panel when the up arrow key is pressed. This is useful for implementing workflows, where, depending on the workflow, it might make sense to move focus to a certain sequence of panels.

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	Debug     = false
	DebugChan = make(chan string, 100)
)
View Source
var KeyShiftTabBinding = SingleKeyBinding(tcell.KeyBacktab)
View Source
var KeyTabBinding = SingleKeyBinding(tcell.KeyTAB)

Functions

func CalculateDimensions

func CalculateDimensions(dimensions []Dimension, total int) []int

Here we assume that the layout is valid

func DebugPrintf

func DebugPrintf(format string, a ...any)

func GetHandlingForMessageWithRoutePath

func GetHandlingForMessageWithRoutePath(msg IMessageWithRoutePath) func(msg Msg) Msg

func GetStylingSize

func GetStylingSize(s lipgloss.Style) (int, int, int, int)

func HandleBatchCmds

func HandleBatchCmds(msg Msg) []tea.Cmd

func IsSamePath

func IsSamePath(path1, path2 []int) bool

func MakeAutoRoutedCmd

func MakeAutoRoutedCmd(cmd tea.Cmd, path []int) tea.Cmd

func MapKeyMsg

func MapKeyMsg(msg KeyMsg) (tea.KeyMsg, bool)

func Run

func Run(model IRootModel, screen tcell.Screen)

func TcellDrawHelper

func TcellDrawHelper(j string, s tcellviews.View, preserveViews []*tcellviews.ViewPort)

Types

type AutoRoutedCmd

type AutoRoutedCmd struct {
	tea.Cmd
	OriginPath []int
}

func (AutoRoutedCmd) AsRouteTypedMsg

func (msg AutoRoutedCmd) AsRouteTypedMsg() Msg

type AutoRoutedMsg

type AutoRoutedMsg struct {
	Msg
	*RoutePath
}

type BroadcastMsg

type BroadcastMsg struct {
	Msg
}

type BroadcastMsgType

type BroadcastMsgType struct {
	Msg Msg
}

type ConsiderForGlobalShortcutMsg

type ConsiderForGlobalShortcutMsg struct {
	KeyMsg
}

type ConsiderForLocalShortcutMsg

type ConsiderForLocalShortcutMsg struct {
	KeyMsg
	*RoutePath
}

type ContextualHelpTextMsg

type ContextualHelpTextMsg struct {
	Text string
	Line int
}

type Dimension

type Dimension struct {
	Min   int     //valid if > 0, assumed unspecified otherwise
	Max   int     //valid if > 0, assumed unspecified otherwise
	Fixed int     //valid if > 0, assumed unspecified otherwise
	Ratio float64 //valid if > 0, assumed unspecified otherwise
}

If all fields are 0, it is assumed that the panel should take up the remaining space

func (Dimension) IsConstrained

func (d Dimension) IsConstrained() bool

func (Dimension) IsPureFixed

func (d Dimension) IsPureFixed() bool

func (Dimension) IsPureRatio

func (d Dimension) IsPureRatio() bool

func (Dimension) IsUnspecified

func (d Dimension) IsUnspecified() bool

func (Dimension) IsValid

func (d Dimension) IsValid() bool

type FastKeyMapChecker

type FastKeyMapChecker struct {
	Keys map[string]struct{}
}

func MakeFastKeyMapChecker

func MakeFastKeyMapChecker(keymap interface{}) FastKeyMapChecker

func (FastKeyMapChecker) HasKey

func (k FastKeyMapChecker) HasKey(msg tea.KeyMsg) bool

type FocusGrantMsg

type FocusGrantMsg struct {
	*RoutePath
	Relation Relation
}

type FocusPropagatedMsgType

type FocusPropagatedMsgType struct {
	Msg Msg
}

type FocusRequestMsg

type FocusRequestMsg struct {
	RequestedPath []int // Path to identify the focus request, e.g., [0, 2] means first panel's second child
	Relation      Relation
}

type FocusRevokeMsg

type FocusRevokeMsg struct{}

type HasView

type HasView interface {
	View() string
}

Type enforces

type ILeafModel

type ILeafModel interface {
	Update(msg Msg) tea.Cmd
	Init() tea.Cmd
}

type ILeafModelWithDraw

type ILeafModelWithDraw interface {
	Draw(force bool, view *tcellviews.ViewPort) bool
}

type ILeafModelWithView

type ILeafModelWithView interface {
	NeedsRedraw() bool
	View() string
}

type IMessageWithRoutePath

type IMessageWithRoutePath interface {
	GetRoutePath() *RoutePath
}

type IMovementNode added in v0.0.2

type IMovementNode interface {
	IsListOfPanels() bool
	IsSelector() bool
	Next(*ShortCutPanel) *ShortCutPanel
	Previous(*ShortCutPanel) *ShortCutPanel
	CreateKeyBindings(IMovementNode, *KeyBinding, *KeyBinding)

	First() *ShortCutPanel
	Last() *ShortCutPanel
	// contains filtered or unexported methods
}

type IPanel

type IPanel interface {
	IsFocused() bool
	GetPath() []int
	SetPath(path []int)
	HandleMessage(msg Msg)
	SetView(view *tcellviews.ViewPort)
	Draw(force bool) bool
	Init(cmds chan tea.Cmd)
	GetLeafPanelCenters() []PanelCenter
	GetName() string
	SetTabHidden(hidden bool)
	IsInHiddenTab() bool
}

IPanel is an interface that allows a tea model to be focused. It is used to handle focus passing in the UI.

type IRootModel

type IRootModel interface {
	Update(msg Msg)
	Init(cmds chan tea.Cmd, view *tcellviews.ViewPort) tea.Cmd
	Draw() bool
}

type KeyBinding

type KeyBinding struct {
	KeyDefs   []KeyDef
	ShortHelp string
	LongHelp  string
	Enabled   bool
	Func      func() tea.Cmd
}

func NewKeyBinding

func NewKeyBinding(opts ...KeyBindingOption) *KeyBinding

func ShiftTabBinding added in v0.0.2

func ShiftTabBinding() KeyBinding

func SingleKeyBinding

func SingleKeyBinding(key tcell.Key) KeyBinding

func SingleRuneBinding

func SingleRuneBinding(rune rune) KeyBinding

func (*KeyBinding) IsEnabled

func (keybinding *KeyBinding) IsEnabled() bool

func (*KeyBinding) IsMatch

func (keybinding *KeyBinding) IsMatch(eventKey *tcell.EventKey) bool

type KeyBindingOption

type KeyBindingOption func(*KeyBinding)

func WithEnabled

func WithEnabled(enabled bool) KeyBindingOption

func WithFunc

func WithFunc(fn func() tea.Cmd) KeyBindingOption

func WithKeyDef

func WithKeyDef(keyDef KeyDef) KeyBindingOption

func WithKeyDefs

func WithKeyDefs(keyDefs []KeyDef) KeyBindingOption

func WithLongHelp

func WithLongHelp(longHelp string) KeyBindingOption

func WithShortHelp

func WithShortHelp(shortHelp string) KeyBindingOption

type KeyDef

type KeyDef struct {
	Key       tcell.Key
	Modifiers tcell.ModMask
	Rune      rune
}

func (*KeyDef) Matches added in v0.0.2

func (keyDef *KeyDef) Matches(eventKey *tcell.EventKey) bool

type KeyMsg

type KeyMsg struct {
	*tcell.EventKey
	Unused *bool
}

func (*KeyMsg) IsUsed added in v0.0.2

func (keyMsg *KeyMsg) IsUsed() bool

func (*KeyMsg) Matches added in v0.0.2

func (keyMsg *KeyMsg) Matches(keyDef KeyDef) bool

func (*KeyMsg) SetUnused added in v0.0.2

func (keyMsg *KeyMsg) SetUnused()

type Layout

type Layout struct {
	Orientation Orientation
	Dimensions  []Dimension
}

Layout is a struct that describes the layout of a panel

func (Layout) AreDimensionsValid

func (l Layout) AreDimensionsValid(printErrors bool) bool

func (Layout) CalculateDims

func (l Layout) CalculateDims(total int) []int

Preconditions: The layout is Vertical or Horizontal Total > 0

func (Layout) IsLayoutValid

func (l Layout) IsLayoutValid() bool

type ListPanel

type ListPanel struct {
	Panels []IPanel

	MsgForParent tea.Msg
	Layout       Layout
	Selected     int // Index of the selected panel, only used if the orientation is ZStacked
	Name         string
	// contains filtered or unexported fields
}

ListPanel can house a list of panels that can be displayed in a horizontal, vertical, or stacked layout List panels also support handling focus propagation

func NewListPanel

func NewListPanel(models []IPanel, layout Layout) ListPanel

func (ListPanel) AreDimensionsValid

func (l ListPanel) AreDimensionsValid(printErrors bool) bool

func (*ListPanel) Draw

func (p *ListPanel) Draw(force bool) bool

func (*ListPanel) GetFocusIndex

func (m *ListPanel) GetFocusIndex() int

func (ListPanel) GetLayout

func (m ListPanel) GetLayout() Layout

func (*ListPanel) GetLeafPanelCenters

func (m *ListPanel) GetLeafPanelCenters() []PanelCenter

func (*ListPanel) GetMsgForParent

func (m *ListPanel) GetMsgForParent() tea.Msg

func (*ListPanel) GetName added in v0.0.2

func (m *ListPanel) GetName() string

func (*ListPanel) GetPath

func (m *ListPanel) GetPath() []int

func (*ListPanel) GetSelected

func (m *ListPanel) GetSelected() IPanel

func (*ListPanel) GetSelectedIndex

func (m *ListPanel) GetSelectedIndex() int

func (*ListPanel) HandleFocusRequestMsg

func (m *ListPanel) HandleFocusRequestMsg(msg FocusRequestMsg) *FocusGrantMsg

func (*ListPanel) HandleMessage

func (m *ListPanel) HandleMessage(msg Msg)

func (*ListPanel) HandleSizeMsg

func (m *ListPanel) HandleSizeMsg(msg ResizeMsg)

func (*ListPanel) HandleZStackedMsg

func (m *ListPanel) HandleZStackedMsg(msg tea.Msg)

func (*ListPanel) HandleZStackedSizeMsg

func (m *ListPanel) HandleZStackedSizeMsg(msg ResizeMsg)

func (*ListPanel) Init

func (m *ListPanel) Init(cmds chan tea.Cmd)

func (*ListPanel) IsFocused

func (m *ListPanel) IsFocused() bool

func (*ListPanel) IsInHiddenTab added in v0.0.2

func (m *ListPanel) IsInHiddenTab() bool

func (ListPanel) IsLayoutValid

func (l ListPanel) IsLayoutValid() bool

func (*ListPanel) ListView

func (m *ListPanel) ListView() string

func (*ListPanel) SetMsgForParent

func (m *ListPanel) SetMsgForParent(msg tea.Msg)

func (*ListPanel) SetPath

func (m *ListPanel) SetPath(path []int)

func (*ListPanel) SetSelected

func (m *ListPanel) SetSelected(i int) tea.Cmd

func (*ListPanel) SetTabHidden added in v0.0.2

func (m *ListPanel) SetTabHidden(hidden bool)

func (*ListPanel) SetView

func (p *ListPanel) SetView(view *tcellviews.ViewPort)

func (*ListPanel) TabNext added in v0.0.2

func (m *ListPanel) TabNext() tea.Cmd

func (*ListPanel) TabPrev added in v0.0.2

func (m *ListPanel) TabPrev() tea.Cmd

type Msg

type Msg interface{}

func GetMessageHandlingType

func GetMessageHandlingType(msg Msg) Msg

type Orientation

type Orientation int
const (
	Horizontal Orientation = iota
	Vertical
	ZStacked
)

type PanelCenter

type PanelCenter struct {
	X    int
	Y    int
	Path []int
}

type PanelSequence added in v0.0.2

type PanelSequence struct {
	// contains filtered or unexported fields
}

func NewPanelSequence added in v0.0.2

func NewPanelSequence(panels ...*ShortCutPanel) *PanelSequence

func (*PanelSequence) CreateKeyBindings added in v0.0.2

func (m *PanelSequence) CreateKeyBindings(root IMovementNode, kb *KeyBinding, rev_kb *KeyBinding)

Panel list can only create keybindings to go back and forth between the panels in the list It will leave out the entry/exit keybindings

func (*PanelSequence) First added in v0.0.2

func (m *PanelSequence) First() *ShortCutPanel

func (*PanelSequence) IsListOfPanels added in v0.0.2

func (m *PanelSequence) IsListOfPanels() bool

func (*PanelSequence) IsSelector added in v0.0.2

func (m *PanelSequence) IsSelector() bool

func (*PanelSequence) Last added in v0.0.2

func (m *PanelSequence) Last() *ShortCutPanel

func (*PanelSequence) Next added in v0.0.2

func (m *PanelSequence) Next(panel *ShortCutPanel) *ShortCutPanel

func (*PanelSequence) Previous added in v0.0.2

func (m *PanelSequence) Previous(panel *ShortCutPanel) *ShortCutPanel

type PanelStyle

type PanelStyle struct {
	FocusedBorder   lipgloss.Style
	UnfocusedBorder lipgloss.Style
}

type Relation

type Relation int
const (
	Self Relation = iota
	Parent
	Up
	Down
	Left
	Right
)

type RequestMsgType

type RequestMsgType struct {
	Msg Msg
}

type ResizeMsg

type ResizeMsg struct {
	EventResize *tcell.EventResize
	X           int
	Y           int
	Width       int
	Height      int
}

type RoutePath

type RoutePath struct {
	Path []int
}

func (*RoutePath) GetRoutePath

func (routedPath *RoutePath) GetRoutePath() *RoutePath

type RoutedMsgType

type RoutedMsgType struct {
	Msg Msg
	*RoutePath
}

type SelectTabIndexMsg

type SelectTabIndexMsg struct {
	Index         int
	ListPanelName string
}

type SelectedTabIndexMsg

type SelectedTabIndexMsg struct {
	Index         int
	ListPanelName string
}

type SelectorNode added in v0.0.2

type SelectorNode struct {
	// contains filtered or unexported fields
}

func NewLoopAroundTabMap added in v0.0.2

func NewLoopAroundTabMap(chains ...IMovementNode) *SelectorNode

func NewVisibleSelectorNode added in v0.0.2

func NewVisibleSelectorNode(chains ...IMovementNode) *SelectorNode

func (*SelectorNode) CreateKeyBindings added in v0.0.2

func (m *SelectorNode) CreateKeyBindings(root IMovementNode, kb *KeyBinding, rev_kb *KeyBinding)

func (*SelectorNode) First added in v0.0.2

func (m *SelectorNode) First() *ShortCutPanel

func (*SelectorNode) IsListOfPanels added in v0.0.2

func (m *SelectorNode) IsListOfPanels() bool

func (*SelectorNode) IsSelector added in v0.0.2

func (m *SelectorNode) IsSelector() bool

func (*SelectorNode) Last added in v0.0.2

func (m *SelectorNode) Last() *ShortCutPanel

func (*SelectorNode) Next added in v0.0.2

func (m *SelectorNode) Next(panel *ShortCutPanel) *ShortCutPanel

func (*SelectorNode) Previous added in v0.0.2

func (m *SelectorNode) Previous(panel *ShortCutPanel) *ShortCutPanel

type ShortCutPanel

type ShortCutPanel struct {
	ShortCutPanelConfig

	Model ILeafModel

	Name string

	MarkMessageNotUsed func(msg *KeyMsg)
	// contains filtered or unexported fields
}

ShortCutPanel is an extension of Panel that can handle -- global and local shortcuts -- focus movement on key strokes -- up, down, backspace and enter For focus movement to work, the child panel must send the key-strokes to this panel via GetMsgForParent() It listens for ConsiderForGlobalShortcutMsg and ConsiderForLocalShortcutMsg for local and global shortcuts respectively When focused, it sends a ContextualHelpTextMsg to the parent to set ContextualHelp. The parent can decide to display it or not

func NewShortCutPanel

func NewShortCutPanel(model ILeafModel, opts ...ShortCutPanelOption) *ShortCutPanel

func (*ShortCutPanel) AddKeyBinding added in v0.0.2

func (p *ShortCutPanel) AddKeyBinding(kb KeyBinding)

func (*ShortCutPanel) Draw

func (p *ShortCutPanel) Draw(force bool) bool

func (*ShortCutPanel) DrawModelWithDraw

func (p *ShortCutPanel) DrawModelWithDraw(m ILeafModelWithDraw, force bool) bool

func (*ShortCutPanel) DrawModelWithView

func (p *ShortCutPanel) DrawModelWithView(m ILeafModelWithView, force bool) bool

func (*ShortCutPanel) FocusRequestCmd

func (p *ShortCutPanel) FocusRequestCmd(direction Relation) tea.Cmd

func (*ShortCutPanel) GetLeafPanelCenters

func (p *ShortCutPanel) GetLeafPanelCenters() []PanelCenter

func (*ShortCutPanel) GetName added in v0.0.2

func (p *ShortCutPanel) GetName() string

func (*ShortCutPanel) GetPath

func (p *ShortCutPanel) GetPath() []int

func (*ShortCutPanel) HandleMessage

func (p *ShortCutPanel) HandleMessage(msg Msg)

func (*ShortCutPanel) HandleMessageFromChild

func (p *ShortCutPanel) HandleMessageFromChild(msg Msg) tea.Cmd

func (*ShortCutPanel) HandleSizeMsg

func (p *ShortCutPanel) HandleSizeMsg(msg ResizeMsg) tea.Cmd

func (*ShortCutPanel) Init

func (p *ShortCutPanel) Init(cmds chan tea.Cmd)

func (*ShortCutPanel) IsFocused

func (p *ShortCutPanel) IsFocused() bool

func (*ShortCutPanel) IsInHiddenTab added in v0.0.2

func (p *ShortCutPanel) IsInHiddenTab() bool

func (*ShortCutPanel) RoutedCmd

func (p *ShortCutPanel) RoutedCmd(cmd tea.Cmd) tea.Cmd

func (*ShortCutPanel) SetPath

func (p *ShortCutPanel) SetPath(path []int)

func (*ShortCutPanel) SetTabHidden added in v0.0.2

func (p *ShortCutPanel) SetTabHidden(hidden bool)

func (*ShortCutPanel) SetView

func (p *ShortCutPanel) SetView(view *tcellviews.ViewPort)

type ShortCutPanelConfig

type ShortCutPanelConfig struct {
	ContextualHelp string
	Title          string
	TitleStyle     lipgloss.Style
	PanelStyle     PanelStyle
	KeyBindings    []KeyBinding
}

type ShortCutPanelOption

type ShortCutPanelOption func(*ShortCutPanel)

func WithConfig

func WithConfig(config ShortCutPanelConfig) ShortCutPanelOption

func WithContextualHelp

func WithContextualHelp(help string) ShortCutPanelOption

func WithKeyBindingMaker

func WithKeyBindingMaker(keyBindingMaker func(*ShortCutPanel) KeyBinding) ShortCutPanelOption

func WithName

func WithName(name string) ShortCutPanelOption

func WithPanelStyle

func WithPanelStyle(style PanelStyle) ShortCutPanelOption

func WithTitle

func WithTitle(title string) ShortCutPanelOption

func WithTitleStyle

func WithTitleStyle(style lipgloss.Style) ShortCutPanelOption

type TabBar

type TabBar struct {
	ListPanel *ListPanel
	TabNext   KeyBinding
	TabPrev   KeyBinding
	TabStyles
	// contains filtered or unexported fields
}

func NewTabBar added in v0.0.2

func NewTabBar(listPanel *ListPanel, opts ...TabBarOption) *TabBar

func (TabBar) Init

func (m TabBar) Init() tea.Cmd

func (*TabBar) NeedsRedraw added in v0.0.2

func (m *TabBar) NeedsRedraw() bool

func (TabBar) Update

func (m TabBar) Update(msg Msg) tea.Cmd

func (TabBar) View

func (m TabBar) View() string

type TabBarOption added in v0.0.2

type TabBarOption func(*TabBar)

func WithTabNext added in v0.0.2

func WithTabNext(tabNext KeyBinding) TabBarOption

func WithTabPrev added in v0.0.2

func WithTabPrev(tabPrev KeyBinding) TabBarOption

func WithTabStyles added in v0.0.2

func WithTabStyles(tabStyles TabStyles) TabBarOption

type TabStyles

type TabStyles struct {
	SelectedTabStyle   lipgloss.Style
	UnselectedTabStyle lipgloss.Style
	RenderFunc         func(...string) string
}

type TcellKeyToTeaMsgResult

type TcellKeyToTeaMsgResult struct {
	Msg tea.KeyMsg
	Ok  bool
}

func MapTCellKeyToTeaMsg

func MapTCellKeyToTeaMsg(ev tcell.EventKey) TcellKeyToTeaMsgResult

type TeaCmdMsgEvent

type TeaCmdMsgEvent struct {
	Time time.Time
	Msg  tea.Msg
}

func (*TeaCmdMsgEvent) When

func (t *TeaCmdMsgEvent) When() time.Time

type TopLevelListPanel

type TopLevelListPanel struct {
	*ListPanel
	// contains filtered or unexported fields
}

TopLevelListPanel is a special case of ListPanel that is the topmost panel in the hierarchy It handles responding to focus-request messages with focus-grant messages It also initializes the path of all its children And gives all children panels a chance to request focus for key-strokes by passing them a ConsiderForGlobalShortcutMsg before passing a key-stroke as a regular key-stroke message

func (*TopLevelListPanel) FigureOutFocusGrant

func (m *TopLevelListPanel) FigureOutFocusGrant(msg FocusRequestMsg) *FocusGrantMsg

func (*TopLevelListPanel) HandleMessage

func (m *TopLevelListPanel) HandleMessage(msg Msg)

func (*TopLevelListPanel) Init

func (m *TopLevelListPanel) Init(cmds chan tea.Cmd)

type UntypedMsgType

type UntypedMsgType struct {
	Msg Msg
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL
JackTT - Gopher 🇻🇳