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			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}