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