sessions.go

  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}