4 "charm.land/bubbles/v2/key"
5 tea "charm.land/bubbletea/v2"
6 "charm.land/lipgloss/v2"
7 "github.com/charmbracelet/crush/internal/ui/common"
10// OverlayKeyMap defines key bindings for dialogs.
11type OverlayKeyMap struct {
15// DefaultOverlayKeyMap returns the default key bindings for dialogs.
16func DefaultOverlayKeyMap() OverlayKeyMap {
18 Close: key.NewBinding(
19 key.WithKeys("esc", "alt+esc"),
24// Dialog is a component that can be displayed on top of the UI.
25type Dialog interface {
30// Overlay manages multiple dialogs as an overlay.
36// NewOverlay creates a new [Overlay] instance.
37func NewOverlay(dialogs ...Dialog) *Overlay {
40 keyMap: DefaultOverlayKeyMap(),
44// HasDialogs checks if there are any active dialogs.
45func (d *Overlay) HasDialogs() bool {
46 return len(d.dialogs) > 0
49// ContainsDialog checks if a dialog with the specified ID exists.
50func (d *Overlay) ContainsDialog(dialogID string) bool {
51 for _, dialog := range d.dialogs {
52 if dialog.ID() == dialogID {
59// AddDialog adds a new dialog to the stack.
60func (d *Overlay) AddDialog(dialog Dialog) {
61 d.dialogs = append(d.dialogs, dialog)
64// BringToFront brings the dialog with the specified ID to the front.
65func (d *Overlay) BringToFront(dialogID string) {
66 for i, dialog := range d.dialogs {
67 if dialog.ID() == dialogID {
68 // Move the dialog to the end of the slice
69 d.dialogs = append(d.dialogs[:i], d.dialogs[i+1:]...)
70 d.dialogs = append(d.dialogs, dialog)
76// Update handles dialog updates.
77func (d *Overlay) Update(msg tea.Msg) (*Overlay, tea.Cmd) {
78 if len(d.dialogs) == 0 {
82 idx := len(d.dialogs) - 1 // active dialog is the last one
83 dialog := d.dialogs[idx]
84 switch msg := msg.(type) {
86 if key.Matches(msg, d.keyMap.Close) {
87 // Close the current dialog
93 updatedDialog, cmd := dialog.Update(msg)
94 if updatedDialog == nil {
95 // Dialog requested to be closed
100 // Update the dialog in the stack
101 d.dialogs[idx] = updatedDialog
106// View implements [Model].
107func (d *Overlay) View() string {
108 if len(d.dialogs) == 0 {
112 // Compose all the dialogs into a single view
113 canvas := lipgloss.NewCanvas()
114 for _, dialog := range d.dialogs {
115 layer := lipgloss.NewLayer(dialog.View())
116 canvas.AddLayers(layer)
119 return canvas.Render()
122// ShortHelp implements [help.KeyMap].
123func (d *Overlay) ShortHelp() []key.Binding {
124 return []key.Binding{
129// FullHelp implements [help.KeyMap].
130func (d *Overlay) FullHelp() [][]key.Binding {
131 return [][]key.Binding{
136// removeDialog removes a dialog from the stack.
137func (d *Overlay) removeDialog(idx int) {
138 if idx < 0 || idx >= len(d.dialogs) {
141 d.dialogs = append(d.dialogs[:idx], d.dialogs[idx+1:]...)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list