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 if s.list.IsSelectedFirst() {
93 s.list.SelectLast()
94 s.list.ScrollToBottom()
95 break
96 }
97 s.list.SelectPrev()
98 s.list.ScrollToSelected()
99 case key.Matches(msg, s.keyMap.Next):
100 s.list.Focus()
101 if s.list.IsSelectedLast() {
102 s.list.SelectFirst()
103 s.list.ScrollToTop()
104 break
105 }
106 s.list.SelectNext()
107 s.list.ScrollToSelected()
108 case key.Matches(msg, s.keyMap.Select):
109 if item := s.list.SelectedItem(); item != nil {
110 sessionItem := item.(*SessionItem)
111 return SessionSelectedMsg{sessionItem.Session}
112 }
113 default:
114 var cmd tea.Cmd
115 s.input, cmd = s.input.Update(msg)
116 value := s.input.Value()
117 s.list.SetFilter(value)
118 s.list.ScrollToTop()
119 s.list.SetSelected(0)
120 return cmd
121 }
122 }
123 return nil
124}
125
126// Cursor returns the cursor position relative to the dialog.
127func (s *Session) Cursor() *tea.Cursor {
128 return InputCursor(s.com.Styles, s.input.Cursor())
129}
130
131// View implements [Dialog].
132func (s *Session) View() string {
133 titleStyle := s.com.Styles.Dialog.Title
134 dialogStyle := s.com.Styles.Dialog.View.Width(s.width)
135 header := common.DialogTitle(s.com.Styles, "Switch Session",
136 max(0, s.width-dialogStyle.GetHorizontalFrameSize()-
137 titleStyle.GetHorizontalFrameSize()))
138
139 return HeaderInputListHelpView(s.com.Styles, s.width, s.list.Height(), header,
140 s.input.View(), s.list.Render(), s.help.View(s))
141}
142
143// ShortHelp implements [help.KeyMap].
144func (s *Session) ShortHelp() []key.Binding {
145 updown := key.NewBinding(
146 key.WithKeys("down", "up"),
147 key.WithHelp("↑↓", "choose"),
148 )
149 return []key.Binding{
150 updown,
151 s.keyMap.Select,
152 s.keyMap.Close,
153 }
154}
155
156// FullHelp implements [help.KeyMap].
157func (s *Session) FullHelp() [][]key.Binding {
158 m := [][]key.Binding{}
159 slice := []key.Binding{
160 s.keyMap.Select,
161 s.keyMap.Next,
162 s.keyMap.Previous,
163 s.keyMap.Close,
164 }
165 for i := 0; i < len(slice); i += 4 {
166 end := min(i+4, len(slice))
167 m = append(m, slice[i:end])
168 }
169 return m
170}