Archived
c copies the absolute directory of the selected submodule via OSC 52. Keys are grouped into navigation (footer left), global (footer right) and submodule (detail pane palette, footer if the pane can't show it). Adds a key reference table to the UX guide. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
102 lines
2.6 KiB
Go
102 lines
2.6 KiB
Go
// Package footer renders key hints and short-lived status messages.
|
|
package footer
|
|
|
|
import (
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/charmbracelet/bubbles/help"
|
|
"github.com/charmbracelet/bubbles/key"
|
|
tea "github.com/charmbracelet/bubbletea"
|
|
"github.com/charmbracelet/lipgloss"
|
|
"github.com/charmbracelet/x/ansi"
|
|
|
|
"submarine/internal/tui/theme"
|
|
)
|
|
|
|
const flashDuration = 2 * time.Second
|
|
|
|
type clearFlashMsg struct{ id int }
|
|
|
|
// Keys provides the key groups. The short bar shows Navigation (and
|
|
// Submodule, if requested) on the left and Global on the right; full help
|
|
// shows FullHelp.
|
|
type Keys interface {
|
|
help.KeyMap
|
|
Navigation() []key.Binding
|
|
Submodule() []key.Binding
|
|
Global() []key.Binding
|
|
}
|
|
|
|
type Model struct {
|
|
theme theme.Theme
|
|
help help.Model
|
|
keys Keys
|
|
width int
|
|
flash string
|
|
flashID int
|
|
actions bool // show the submodule bindings in the short bar
|
|
}
|
|
|
|
func New(t theme.Theme, keys Keys) Model {
|
|
return Model{theme: t, help: theme.NewHelp(t), keys: keys}
|
|
}
|
|
|
|
// SetActions shows the submodule bindings in the short bar. Set it if the
|
|
// detail pane doesn't show them.
|
|
func (m *Model) SetActions(on bool) { m.actions = on }
|
|
|
|
func (m *Model) SetWidth(w int) {
|
|
m.width = w
|
|
m.help.Width = w - 2
|
|
}
|
|
|
|
// ToggleHelp switches between short and full key help.
|
|
func (m *Model) ToggleHelp() { m.help.ShowAll = !m.help.ShowAll }
|
|
|
|
// Flash shows msg for a short time instead of the key hints.
|
|
func (m *Model) Flash(msg string) tea.Cmd {
|
|
m.flashID++
|
|
m.flash = msg
|
|
id := m.flashID
|
|
return tea.Tick(flashDuration, func(time.Time) tea.Msg { return clearFlashMsg{id} })
|
|
}
|
|
|
|
func (m *Model) Update(msg tea.Msg) {
|
|
if c, ok := msg.(clearFlashMsg); ok && c.id == m.flashID {
|
|
m.flash = ""
|
|
}
|
|
}
|
|
|
|
func (m Model) View() string {
|
|
style := lipgloss.NewStyle().Width(m.width).Padding(0, 1)
|
|
if m.flash != "" {
|
|
return style.Foreground(m.theme.Warn).Render(m.flash)
|
|
}
|
|
if m.help.ShowAll {
|
|
return style.Render(m.help.View(m.keys))
|
|
}
|
|
return style.Render(m.shortBar(m.width - 2))
|
|
}
|
|
|
|
// shortBar puts the global keys on the right and as many of the others on the
|
|
// left as fit; the submodule keys are dropped first.
|
|
func (m Model) shortBar(w int) string {
|
|
h := m.help
|
|
h.Width = 0
|
|
right := h.ShortHelpView(m.keys.Global())
|
|
left := m.keys.Navigation()
|
|
if m.actions {
|
|
left = append(left[:len(left):len(left)], m.keys.Submodule()...)
|
|
}
|
|
for ; ; left = left[:len(left)-1] {
|
|
l := h.ShortHelpView(left)
|
|
if gap := w - lipgloss.Width(l) - lipgloss.Width(right); gap >= 2 || len(left) == 0 {
|
|
if gap < 0 { // not even the global keys fit
|
|
return ansi.Truncate(right, w, "…")
|
|
}
|
|
return l + strings.Repeat(" ", gap) + right
|
|
}
|
|
}
|
|
}
|