1package dialog
2
3import (
4 "charm.land/bubbles/v2/help"
5 "charm.land/bubbles/v2/key"
6 "charm.land/bubbles/v2/textinput"
7 tea "charm.land/bubbletea/v2"
8 "github.com/charmbracelet/crush/internal/session"
9 "github.com/charmbracelet/crush/internal/ui/common"
10 "github.com/charmbracelet/crush/internal/ui/list"
11)
12
13// SessionsID is the identifier for the session selector dialog.
14const SessionsID = "session"
15
16// Session is a session selector dialog.
17type Session struct {
18 width, height int
19 com *common.Common
20 help help.Model
21 list *list.FilterableList
22 input textinput.Model
23
24 keyMap struct {
25 Select key.Binding
26 Next key.Binding
27 Previous key.Binding
28 Close key.Binding
29 }
30}
31
32var _ Dialog = (*Session)(nil)
33
34// NewSessions creates a new Session dialog.
35func NewSessions(com *common.Common, sessions ...session.Session) *Session {
36 s := new(Session)
37 s.com = com
38 help := help.New()
39 help.Styles = com.Styles.DialogHelpStyles()
40
41 s.help = help
42 s.list = list.NewFilterableList(sessionItems(com.Styles, sessions...)...)
43 s.list.Focus()
44 s.list.SetSelected(0)
45
46 s.input = textinput.New()
47 s.input.SetVirtualCursor(false)
48 s.input.Placeholder = "Enter session name"
49 s.input.SetStyles(com.Styles.TextInput)
50 s.input.Focus()
51
52 s.keyMap.Select = key.NewBinding(
53 key.WithKeys("enter", "tab", "ctrl+y"),
54 key.WithHelp("enter", "choose"),
55 )
56 s.keyMap.Next = key.NewBinding(
57 key.WithKeys("down", "ctrl+n"),
58 key.WithHelp("↓", "next item"),
59 )
60 s.keyMap.Previous = key.NewBinding(
61 key.WithKeys("up", "ctrl+p"),
62 key.WithHelp("↑", "previous item"),
63 )
64 s.keyMap.Close = CloseKey
65 return s
66}
67
68// SetSize sets the size of the dialog.
69func (s *Session) SetSize(width, height int) {
70 s.width = width
71 s.height = height
72 innerWidth := width - s.com.Styles.Dialog.View.GetHorizontalFrameSize()
73 s.input.SetWidth(innerWidth - s.com.Styles.Dialog.InputPrompt.GetHorizontalFrameSize() - 1)
74 s.list.SetSize(innerWidth, height-6) // (1) title + (3) input + (1) padding + (1) help
75 s.help.SetWidth(width)
76}
77
78// ID implements Dialog.
79func (s *Session) ID() string {
80 return SessionsID
81}
82
83// Update implements Dialog.
84func (s *Session) Update(msg tea.Msg) tea.Msg {
85 switch msg := msg.(type) {
86 case tea.KeyPressMsg:
87 switch {
88 case key.Matches(msg, s.keyMap.Close):
89 return CloseMsg{}
90 case key.Matches(msg, s.keyMap.Previous):
91 s.list.Focus()
92 s.list.SelectPrev()
93 s.list.ScrollToSelected()
94 case key.Matches(msg, s.keyMap.Next):
95 s.list.Focus()
96 s.list.SelectNext()
97 s.list.ScrollToSelected()
98 case key.Matches(msg, s.keyMap.Select):
99 if item := s.list.SelectedItem(); item != nil {
100 sessionItem := item.(*SessionItem)
101 return SessionSelectedMsg{sessionItem.Session}
102 }
103 default:
104 var cmd tea.Cmd
105 s.input, cmd = s.input.Update(msg)
106 value := s.input.Value()
107 s.list.SetFilter(value)
108 s.list.ScrollToTop()
109 s.list.SetSelected(0)
110 return cmd
111 }
112 }
113 return nil
114}
115
116// Cursor returns the cursor position relative to the dialog.
117func (s *Session) Cursor() *tea.Cursor {
118 return InputCursor(s.com.Styles, s.input.Cursor())
119}
120
121// View implements [Dialog].
122func (s *Session) View() string {
123 titleStyle := s.com.Styles.Dialog.Title
124 dialogStyle := s.com.Styles.Dialog.View.Width(s.width)
125 header := common.DialogTitle(s.com.Styles, "Switch Session",
126 max(0, s.width-dialogStyle.GetHorizontalFrameSize()-
127 titleStyle.GetHorizontalFrameSize()))
128
129 return HeaderInputListHelpView(s.com.Styles, s.width, s.list.Height(), header,
130 s.input.View(), s.list.Render(), s.help.View(s))
131}
132
133// ShortHelp implements [help.KeyMap].
134func (s *Session) ShortHelp() []key.Binding {
135 updown := key.NewBinding(
136 key.WithKeys("down", "up"),
137 key.WithHelp("↑↓", "choose"),
138 )
139 return []key.Binding{
140 updown,
141 s.keyMap.Select,
142 s.keyMap.Close,
143 }
144}
145
146// FullHelp implements [help.KeyMap].
147func (s *Session) FullHelp() [][]key.Binding {
148 m := [][]key.Binding{}
149 slice := []key.Binding{
150 s.keyMap.Select,
151 s.keyMap.Next,
152 s.keyMap.Previous,
153 s.keyMap.Close,
154 }
155 for i := 0; i < len(slice); i += 4 {
156 end := min(i+4, len(slice))
157 m = append(m, slice[i:end])
158 }
159 return m
160}