init layout, notifications, tabs & more

Signed-off-by: Hadi <112569860+anotherhadi@users.noreply.github.com>
This commit is contained in:
Hadi
2026-08-17 22:09:32 +02:00
parent f3a1877832
commit 5f99fa7521
68 changed files with 6375 additions and 330 deletions
+130
View File
@@ -0,0 +1,130 @@
package layout
import (
"charm.land/bubbles/v2/key"
"charm.land/lipgloss/v2"
)
// HelpProvider is how a pane opts into the help bar: implement it and
// return whatever bindings you want shown while you're focused. A pane
// that doesn't implement it just contributes nothing - the help bar still
// shows layout's own controls (ctrl+hjkl, ?), it never disappears entirely.
type HelpProvider interface {
HelpBindings() []key.Binding
}
// HelpBindings implements HelpProvider by delegating to FocusedHelp, so a
// Model embedded as a HelpProvider behaves identically to one consulted as
// a Navigable.
func (m Model) HelpBindings() []key.Binding {
return m.FocusedHelp()
}
// FocusedHelp implements Navigable: the bindings for whatever pane
// currently has focus, drilling into a nested Navigable automatically until
// it reaches the real pane at the bottom. Returns nil if the focused pane
// (at any depth) implements neither Navigable nor HelpProvider.
func (m Model) FocusedHelp() []key.Binding {
focused, ok := findNode(m.root, m.state.id)
if !ok {
return nil
}
if nav, ok := focused.model.(Navigable); ok {
return nav.FocusedHelp()
}
if hp, ok := focused.model.(HelpProvider); ok {
return hp.HelpBindings()
}
return nil
}
// helpKeyMap adapts a focused pane's flat HelpProvider bindings plus
// layout's own KeyMap into the shape bubbles/help.KeyMap expects.
type helpKeyMap struct {
pane []key.Binding
own KeyMap
width int
}
// ShortHelp implements help.KeyMap. own leads (its ToggleHelp is always
// first, see KeyMap.ShortHelp), the focused pane's own bindings follow.
func (h helpKeyMap) ShortHelp() []key.Binding {
return append(append([]key.Binding{}, h.own.ShortHelp()...), h.pane...)
}
// FullHelp implements help.KeyMap. Rather than the fixed grouping ShortHelp
// mirrors, it flattens every binding into one ordered list - own first, so
// ToggleHelp lands in the first column's first row, then the pane's - and
// re-flows it into as many columns as fit within width, maximizing columns
// to minimize the number of rows the full help view takes.
func (h helpKeyMap) FullHelp() [][]key.Binding {
all := append(append([]key.Binding{}, flattenGroups(h.own.FullHelp())...), h.pane...)
return flowColumns(all, h.width)
}
func flattenGroups(groups [][]key.Binding) []key.Binding {
var flat []key.Binding
for _, g := range groups {
flat = append(flat, g...)
}
return flat
}
// flowColumns arranges bindings into as many columns as fit within width
// without overflowing. It fills each column top-to-bottom before moving to
// the next, which is what bubbles/help.FullHelpView expects: one inner
// slice per column, rendered as a vertical stack.
func flowColumns(bindings []key.Binding, width int) [][]key.Binding {
enabled := make([]key.Binding, 0, len(bindings))
for _, kb := range bindings {
if kb.Enabled() {
enabled = append(enabled, kb)
}
}
if len(enabled) == 0 {
return nil
}
if width <= 0 {
return [][]key.Binding{enabled}
}
for rows := 1; rows <= len(enabled); rows++ {
groups := chunkRows(enabled, rows)
if columnsWidth(groups) <= width {
return groups
}
}
return [][]key.Binding{enabled}
}
// chunkRows splits bindings into groups of at most rows items each, filling
// each group before moving to the next - the column-major order help's
// FullHelpView renders (first group is the leftmost column).
func chunkRows(bindings []key.Binding, rows int) [][]key.Binding {
var groups [][]key.Binding
for i := 0; i < len(bindings); i += rows {
end := min(i+rows, len(bindings))
groups = append(groups, bindings[i:end])
}
return groups
}
// columnsWidth mirrors bubbles/help.FullHelpView's own width accounting:
// each column is as wide as its longest key plus a space plus its longest
// description, columns separated by FullSeparator's width (4 cells, " ").
func columnsWidth(groups [][]key.Binding) int {
const separatorWidth = 4
total := 0
for i, group := range groups {
if i > 0 {
total += separatorWidth
}
var keyWidth, descWidth int
for _, kb := range group {
keyWidth = max(keyWidth, lipgloss.Width(kb.Help().Key))
descWidth = max(descWidth, lipgloss.Width(kb.Help().Desc))
}
total += keyWidth + 1 + descWidth
}
return total
}