Detailed changes
@@ -1,9 +1,9 @@
package common
import (
+ "charm.land/glamour/v2"
+ gstyles "charm.land/glamour/v2/styles"
"github.com/charmbracelet/crush/internal/ui/styles"
- "github.com/charmbracelet/glamour/v2"
- gstyles "github.com/charmbracelet/glamour/v2/styles"
)
// MarkdownRenderer returns a glamour [glamour.TermRenderer] configured with
@@ -4,6 +4,8 @@ import (
"charm.land/bubbles/v2/key"
tea "charm.land/bubbletea/v2"
"charm.land/lipgloss/v2"
+ "github.com/charmbracelet/crush/internal/ui/common"
+ uv "github.com/charmbracelet/ultraviolet"
)
// CloseKey is the default key binding to close dialogs.
@@ -12,35 +14,11 @@ var CloseKey = key.NewBinding(
key.WithHelp("esc", "exit"),
)
-// OverlayKeyMap defines key bindings for dialogs.
-type OverlayKeyMap struct {
- Close key.Binding
-}
-
-// ActionType represents the type of action taken by a dialog.
-type ActionType int
-
-const (
- // ActionNone indicates no action.
- ActionNone ActionType = iota
- // ActionClose indicates that the dialog should be closed.
- ActionClose
- // ActionSelect indicates that an item has been selected.
- ActionSelect
-)
-
-// Action represents an action taken by a dialog.
-// It can be used to signal closing or other operations.
-type Action struct {
- Type ActionType
- Payload any
-}
-
// Dialog is a component that can be displayed on top of the UI.
type Dialog interface {
ID() string
- Update(msg tea.Msg) (Action, tea.Cmd)
- Layer() *lipgloss.Layer
+ Update(msg tea.Msg) tea.Cmd
+ View() string
}
// Overlay manages multiple dialogs as an overlay.
@@ -122,27 +100,26 @@ func (d *Overlay) Update(msg tea.Msg) (*Overlay, tea.Cmd) {
}
}
- action, cmd := dialog.Update(msg)
- switch action.Type {
- case ActionClose:
+ if cmd := dialog.Update(msg); cmd != nil {
// Close the current dialog
d.removeDialog(idx)
return d, cmd
- case ActionSelect:
- // Pass the action up (without modifying the dialog stack)
- return d, cmd
}
- return d, cmd
+ return d, nil
}
-// Layers returns the current stack of dialogs as lipgloss layers.
-func (d *Overlay) Layers() []*lipgloss.Layer {
- layers := make([]*lipgloss.Layer, len(d.dialogs))
- for i, dialog := range d.dialogs {
- layers[i] = dialog.Layer()
+// Draw renders the overlay and its dialogs.
+func (d *Overlay) Draw(scr uv.Screen, area uv.Rectangle) {
+ for _, dialog := range d.dialogs {
+ view := dialog.View()
+ viewWidth := lipgloss.Width(view)
+ viewHeight := lipgloss.Height(view)
+ center := common.CenterRect(area, viewWidth, viewHeight)
+ if area.Overlaps(center) {
+ uv.NewStyledString(view).Draw(scr, center)
+ }
}
- return layers
}
// removeDialog removes a dialog from the stack.
@@ -73,30 +73,30 @@ func (*Quit) ID() string {
}
// Update implements [Model].
-func (q *Quit) Update(msg tea.Msg) (Action, tea.Cmd) {
+func (q *Quit) Update(msg tea.Msg) tea.Cmd {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch {
case key.Matches(msg, q.keyMap.LeftRight, q.keyMap.Tab):
q.selectedNo = !q.selectedNo
- return Action{}, nil
+ return nil
case key.Matches(msg, q.keyMap.EnterSpace):
if !q.selectedNo {
- return Action{}, tea.Quit
+ return tea.Quit
}
- return Action{}, nil
+ return nil
case key.Matches(msg, q.keyMap.Yes):
- return Action{}, tea.Quit
+ return tea.Quit
case key.Matches(msg, q.keyMap.No, q.keyMap.Close):
- return Action{}, nil
+ return nil
}
}
- return Action{}, nil
+ return nil
}
-// Layer implements [Model].
-func (q *Quit) Layer() *lipgloss.Layer {
+// View implements [Dialog].
+func (q *Quit) View() string {
const question = "Are you sure you want to quit?"
baseStyle := q.com.Styles.Base
buttonOpts := []common.ButtonOpts{
@@ -113,7 +113,7 @@ func (q *Quit) Layer() *lipgloss.Layer {
),
)
- return lipgloss.NewLayer(q.com.Styles.BorderFocus.Render(content))
+ return q.com.Styles.BorderFocus.Render(content)
}
// ShortHelp implements [help.KeyMap].
@@ -100,7 +100,7 @@ func (s *Session) ID() string {
}
// Update implements Dialog.
-func (s *Session) Update(msg tea.Msg) (Action, tea.Cmd) {
+func (s *Session) Update(msg tea.Msg) tea.Cmd {
switch msg := msg.(type) {
case tea.KeyPressMsg:
switch {
@@ -115,20 +115,20 @@ func (s *Session) Update(msg tea.Msg) (Action, tea.Cmd) {
case key.Matches(msg, s.keyMap.Select):
if item := s.list.SelectedItem(); item != nil {
sessionItem := item.(*SessionItem)
- return Action{Type: ActionSelect, Payload: sessionItem.Session}, SessionSelectCmd(sessionItem.Session)
+ return SessionSelectCmd(sessionItem.Session)
}
default:
var cmd tea.Cmd
s.input, cmd = s.input.Update(msg)
s.list.SetFilter(s.input.Value())
- return Action{}, cmd
+ return cmd
}
}
- return Action{}, nil
+ return nil
}
-// Layer implements Dialog.
-func (s *Session) Layer() *lipgloss.Layer {
+// View implements [Dialog].
+func (s *Session) View() string {
titleStyle := s.com.Styles.Dialog.Title
helpStyle := s.com.Styles.Dialog.HelpView
dialogStyle := s.com.Styles.Dialog.View.Width(s.width)
@@ -156,7 +156,7 @@ func (s *Session) Layer() *lipgloss.Layer {
helpStyle.Render(s.help.View(s)),
}, "\n")
- return lipgloss.NewLayer(dialogStyle.Render(content))
+ return dialogStyle.Render(content)
}
// ShortHelp implements [help.KeyMap].
@@ -535,19 +535,7 @@ func (m *UI) Draw(scr uv.Screen, area uv.Rectangle) {
// This needs to come last to overlay on top of everything
if m.dialog.HasDialogs() {
- dialogLayers := m.dialog.Layers()
- layers := make([]*lipgloss.Layer, 0)
- for _, layer := range dialogLayers {
- if layer == nil {
- continue
- }
- layerW, layerH := layer.Width(), layer.Height()
- layerArea := common.CenterRect(area, layerW, layerH)
- layers = append(layers, layer.X(layerArea.Min.X).Y(layerArea.Min.Y))
- }
-
- comp := lipgloss.NewCompositor(layers...)
- comp.Draw(scr, area)
+ m.dialog.Draw(scr, area)
}
}
@@ -8,10 +8,10 @@ import (
"charm.land/bubbles/v2/textarea"
"charm.land/bubbles/v2/textinput"
tea "charm.land/bubbletea/v2"
+ "charm.land/glamour/v2/ansi"
"charm.land/lipgloss/v2"
"github.com/alecthomas/chroma/v2"
"github.com/charmbracelet/crush/internal/tui/exp/diffview"
- "github.com/charmbracelet/glamour/v2/ansi"
"github.com/charmbracelet/x/exp/charmtone"
)