@@ -0,0 +1,196 @@
6+ "charm.land/bubbles/v2/help"
7+ "charm.land/bubbles/v2/key"
8+ "charm.land/bubbles/v2/textinput"
9+ tea "charm.land/bubbletea/v2"
10+ "charm.land/lipgloss/v2"
11+ "github.com/charmbracelet/crush/internal/session"
12+ "github.com/charmbracelet/crush/internal/ui/common"
13+ "github.com/charmbracelet/crush/internal/ui/list"
16+// SessionDialogID is the identifier for the session selector dialog.
17+const SessionDialogID = "session"
19+// Session is a session selector dialog.
24+ list *list.FilterableList
25+ input textinput.Model
35+var _ Dialog = (*Session)(nil)
37+// SessionSelectedMsg is a message sent when a session is selected.
38+type SessionSelectedMsg struct {
39+ Session session.Session
42+// NewSessions creates a new Session dialog.
43+func NewSessions(com *common.Common, sessions ...session.Session) *Session {
47+ help.Styles = com.Styles.DialogHelpStyles()
50+ s.list = list.NewFilterableList(sessionItems(com.Styles, sessions...)...)
52+ s.list.SetSelected(0)
54+ s.input = textinput.New()
55+ s.input.SetVirtualCursor(false)
56+ s.input.Placeholder = "Enter session name"
57+ s.input.SetStyles(com.Styles.TextInput)
60+ s.keyMap.Select = key.NewBinding(
61+ key.WithKeys("enter", "tab", "ctrl+y"),
62+ key.WithHelp("enter", "choose"),
64+ s.keyMap.Next = key.NewBinding(
65+ key.WithKeys("down", "ctrl+n"),
66+ key.WithHelp("↓", "next item"),
68+ s.keyMap.Previous = key.NewBinding(
69+ key.WithKeys("up", "ctrl+p"),
70+ key.WithHelp("↑", "previous item"),
72+ s.keyMap.Close = CloseKey
76+// Cursor returns the cursor position relative to the dialog.
77+func (s *Session) Cursor() *tea.Cursor {
78+ return s.input.Cursor()
81+// SetSize sets the size of the dialog.
82+func (s *Session) SetSize(width, height int) {
85+ innerWidth := width - s.com.Styles.Dialog.View.GetHorizontalFrameSize()
86+ s.input.SetWidth(innerWidth - s.com.Styles.Dialog.InputPrompt.GetHorizontalFrameSize() - 1)
87+ s.list.SetSize(innerWidth, height-6) // (1) title + (3) input + (1) padding + (1) help
88+ s.help.SetWidth(width)
91+// SelectedItem returns the currently selected item. It may be nil if no item
93+func (s *Session) SelectedItem() list.Item {
94+ return s.list.SelectedItem()
97+// ID implements Dialog.
98+func (s *Session) ID() string {
99+ return SessionDialogID
102+// Update implements Dialog.
103+func (s *Session) Update(msg tea.Msg) (Action, tea.Cmd) {
104+ switch msg := msg.(type) {
105+ case tea.KeyPressMsg:
107+ case key.Matches(msg, s.keyMap.Previous):
110+ s.list.ScrollToSelected()
111+ case key.Matches(msg, s.keyMap.Next):
114+ s.list.ScrollToSelected()
115+ case key.Matches(msg, s.keyMap.Select):
116+ if item := s.list.SelectedItem(); item != nil {
117+ sessionItem := item.(*SessionItem)
118+ return Action{Type: ActionSelect, Payload: sessionItem.Session}, SessionSelectCmd(sessionItem.Session)
122+ s.input, cmd = s.input.Update(msg)
123+ s.list.SetFilter(s.input.Value())
124+ return Action{}, cmd
127+ return Action{}, nil
130+// Layer implements Dialog.
131+func (s *Session) Layer() *lipgloss.Layer {
132+ titleStyle := s.com.Styles.Dialog.Title
133+ helpStyle := s.com.Styles.Dialog.HelpView
134+ dialogStyle := s.com.Styles.Dialog.View.Width(s.width)
135+ inputStyle := s.com.Styles.Dialog.InputPrompt
136+ helpStyle = helpStyle.Width(s.width - dialogStyle.GetHorizontalFrameSize())
137+ listContent := s.list.Render()
138+ if nlines := lipgloss.Height(listContent); nlines < s.list.Height() {
139+ // pad the list content to avoid jumping when navigating
140+ listContent += strings.Repeat("\n", max(0, s.list.Height()-nlines))
143+ content := strings.Join([]string{
149+ dialogStyle.GetHorizontalFrameSize()-
150+ titleStyle.GetHorizontalFrameSize()))),
152+ inputStyle.Render(s.input.View()),
156+ helpStyle.Render(s.help.View(s)),
159+ return lipgloss.NewLayer(dialogStyle.Render(content))
162+// ShortHelp implements [help.KeyMap].
163+func (s *Session) ShortHelp() []key.Binding {
164+ updown := key.NewBinding(
165+ key.WithKeys("down", "up"),
166+ key.WithHelp("↑↓", "choose"),
168+ return []key.Binding{
175+// FullHelp implements [help.KeyMap].
176+func (s *Session) FullHelp() [][]key.Binding {
177+ m := [][]key.Binding{}
178+ slice := []key.Binding{
184+ for i := 0; i < len(slice); i += 4 {
185+ end := min(i+4, len(slice))
186+ m = append(m, slice[i:end])
191+// SessionSelectCmd creates a command that sends a SessionSelectMsg.
192+func SessionSelectCmd(s session.Session) tea.Cmd {
193+ return func() tea.Msg {
194+ return SessionSelectedMsg{Session: s}