@@ -2,11 +2,13 @@ package dialog
6 "charm.land/bubbles/v2/help"
7 "charm.land/bubbles/v2/help"
7 "charm.land/bubbles/v2/key"
8 "charm.land/bubbles/v2/key"
8 "charm.land/bubbles/v2/textinput"
9 "charm.land/bubbles/v2/textinput"
9 tea "charm.land/bubbletea/v2"
10 tea "charm.land/bubbletea/v2"
11+ "charm.land/lipgloss/v2"
10 "github.com/charmbracelet/crush/internal/session"
12 "github.com/charmbracelet/crush/internal/session"
11 "github.com/charmbracelet/crush/internal/ui/common"
13 "github.com/charmbracelet/crush/internal/ui/common"
12 "github.com/charmbracelet/crush/internal/ui/list"
14 "github.com/charmbracelet/crush/internal/ui/list"
@@ -43,6 +45,9 @@ type Session struct {
43 Previous key.Binding
45 Previous key.Binding
44 UpDown key.Binding
46 UpDown key.Binding
45 Delete key.Binding
47 Delete key.Binding
49+ ConfirmRename key.Binding
50+ CancelRename key.Binding
46 ConfirmDelete key.Binding
51 ConfirmDelete key.Binding
47 CancelDelete key.Binding
52 CancelDelete key.Binding
48 Close key.Binding
53 Close key.Binding
@@ -103,6 +108,18 @@ func NewSessions(com *common.Common, selectedSessionID string) (*Session, error)
103 key.WithKeys("ctrl+x"),
108 key.WithKeys("ctrl+x"),
104 key.WithHelp("ctrl+x", "delete"),
109 key.WithHelp("ctrl+x", "delete"),
111+ s.keyMap.Rename = key.NewBinding(
112+ key.WithKeys("ctrl+r"),
113+ key.WithHelp("ctrl+r", "rename"),
115+ s.keyMap.ConfirmRename = key.NewBinding(
116+ key.WithKeys("enter"),
117+ key.WithHelp("enter", "confirm"),
119+ s.keyMap.CancelRename = key.NewBinding(
120+ key.WithKeys("esc"),
121+ key.WithHelp("esc", "cancel"),
106 s.keyMap.ConfirmDelete = key.NewBinding(
123 s.keyMap.ConfirmDelete = key.NewBinding(
107 key.WithKeys("y"),
124 key.WithKeys("y"),
108 key.WithHelp("y", "delete"),
125 key.WithHelp("y", "delete"),
@@ -129,15 +146,40 @@ func (s *Session) HandleMsg(msg tea.Msg) Action {
129 case sessionsModeDeleting:
146 case sessionsModeDeleting:
131 case key.Matches(msg, s.keyMap.ConfirmDelete):
148 case key.Matches(msg, s.keyMap.ConfirmDelete):
132− return s.confirmDeleteSession()
149+ action := s.confirmDeleteSession()
150+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
151+ s.list.SelectFirst()
152+ s.list.ScrollToSelected()
133 case key.Matches(msg, s.keyMap.CancelDelete):
154 case key.Matches(msg, s.keyMap.CancelDelete):
134 s.sessionsMode = sessionsModeNormal
155 s.sessionsMode = sessionsModeNormal
135 s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
156 s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
158+ case sessionsModeUpdating:
160+ case key.Matches(msg, s.keyMap.ConfirmRename):
161+ action := s.confirmRenameSession()
162+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
164+ case key.Matches(msg, s.keyMap.CancelRename):
165+ s.sessionsMode = sessionsModeNormal
166+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
168+ item := s.list.SelectedItem()
172+ if sessionItem, ok := item.(*SessionItem); ok {
173+ return sessionItem.HandleInput(msg)
139 case key.Matches(msg, s.keyMap.Close):
178 case key.Matches(msg, s.keyMap.Close):
140 return ActionClose{}
179 return ActionClose{}
180+ case key.Matches(msg, s.keyMap.Rename):
181+ s.sessionsMode = sessionsModeUpdating
182+ s.list.SetItems(sessionItems(s.com.Styles, sessionsModeUpdating, s.sessions...)...)
141 case key.Matches(msg, s.keyMap.Delete):
183 case key.Matches(msg, s.keyMap.Delete):
142 if s.isCurrentSessionBusy() {
184 if s.isCurrentSessionBusy() {
143 return ActionCmd{uiutil.ReportWarn("Agent is busy, please wait...")}
185 return ActionCmd{uiutil.ReportWarn("Agent is busy, please wait...")}
@@ -218,6 +260,51 @@ func (s *Session) Draw(scr uv.Screen, area uv.Rectangle) *tea.Cursor {
218 rc.TitleGradientToColor = t.Dialog.Sessions.DeletingTitleGradientToColor
260 rc.TitleGradientToColor = t.Dialog.Sessions.DeletingTitleGradientToColor
219 rc.ViewStyle = t.Dialog.Sessions.DeletingView
261 rc.ViewStyle = t.Dialog.Sessions.DeletingView
220 rc.AddPart(t.Dialog.Sessions.DeletingMessage.Render("Delete this session?"))
262 rc.AddPart(t.Dialog.Sessions.DeletingMessage.Render("Delete this session?"))
263+ case sessionsModeUpdating:
264+ rc.TitleStyle = t.Dialog.Sessions.UpdatingTitle
265+ rc.TitleGradientFromColor = t.Dialog.Sessions.UpdatingTitleGradientFromColor
266+ rc.TitleGradientToColor = t.Dialog.Sessions.UpdatingTitleGradientToColor
267+ rc.ViewStyle = t.Dialog.Sessions.UpdatingView
268+ message := t.Dialog.Sessions.UpdatingMessage.Render("Rename this session?")
270+ item := s.selectedSessionItem()
279+ start, end := s.list.VisibleItemIndices()
280+ selectedIndex := s.list.Selected()
282+ titleStyle := t.Dialog.Sessions.UpdatingTitle
283+ dialogStyle := t.Dialog.Sessions.UpdatingView
284+ inputStyle := t.Dialog.InputPrompt
286+ // Adjust cursor position to account for dialog layout + message
287+ cur.X += inputStyle.GetBorderLeftSize() +
288+ inputStyle.GetMarginLeft() +
289+ inputStyle.GetPaddingLeft() +
290+ dialogStyle.GetBorderLeftSize() +
291+ dialogStyle.GetPaddingLeft() +
292+ dialogStyle.GetMarginLeft()
293+ cur.Y += titleStyle.GetVerticalFrameSize() +
294+ inputStyle.GetBorderTopSize() +
295+ inputStyle.GetMarginTop() +
296+ inputStyle.GetPaddingTop() +
297+ inputStyle.GetBorderBottomSize() +
298+ inputStyle.GetMarginBottom() +
299+ inputStyle.GetPaddingBottom() +
300+ dialogStyle.GetPaddingTop() +
301+ dialogStyle.GetBorderTopSize() +
302+ lipgloss.Height(message) - 1
304+ // move the cursor by one down until we see the selectedIndex
305+ for ; start <= end && start != selectedIndex && selectedIndex > -1; start++ {
222 inputView := t.Dialog.InputPrompt.Render(s.input.View())
309 inputView := t.Dialog.InputPrompt.Render(s.input.View())
223 cur = s.Cursor()
310 cur = s.Cursor()
@@ -260,9 +347,6 @@ func (s *Session) removeSession(id string) {
260 newSessions = append(newSessions, sess)
347 newSessions = append(newSessions, sess)
262 s.sessions = newSessions
349 s.sessions = newSessions
263− s.list.SetItems(sessionItems(s.com.Styles, sessionsModeNormal, s.sessions...)...)
264− s.list.SelectFirst()
265− s.list.ScrollToSelected()
268func (s *Session) deleteSessionCmd(id string) tea.Cmd {
352func (s *Session) deleteSessionCmd(id string) tea.Cmd {
@@ -275,6 +359,42 @@ func (s *Session) deleteSessionCmd(id string) tea.Cmd {
362+func (s *Session) confirmRenameSession() Action {
363+ sessionItem := s.selectedSessionItem()
364+ s.sessionsMode = sessionsModeNormal
365+ if sessionItem == nil {
369+ newTitle := strings.TrimSpace(sessionItem.InputValue())
373+ session := sessionItem.Session
374+ session.Title = newTitle
375+ s.updateSession(session)
376+ return ActionCmd{s.updateSessionCmd(session)}
379+func (s *Session) updateSession(session session.Session) {
380+ for existingID, sess := range s.sessions {
381+ if sess.ID == session.ID {
382+ s.sessions[existingID] = session
388+func (s *Session) updateSessionCmd(session session.Session) tea.Cmd {
389+ return func() tea.Msg {
390+ _, err := s.com.App.Sessions.Save(context.TODO(), session)
392+ return uiutil.NewErrorMsg(err)
278func (s *Session) isCurrentSessionBusy() bool {
398func (s *Session) isCurrentSessionBusy() bool {
279 sessionItem := s.selectedSessionItem()
399 sessionItem := s.selectedSessionItem()
280 if sessionItem == nil {
400 if sessionItem == nil {
@@ -296,11 +416,17 @@ func (s *Session) ShortHelp() []key.Binding {
296 s.keyMap.ConfirmDelete,
416 s.keyMap.ConfirmDelete,
297 s.keyMap.CancelDelete,
417 s.keyMap.CancelDelete,
419+ case sessionsModeUpdating:
420+ return []key.Binding{
421+ s.keyMap.ConfirmRename,
422+ s.keyMap.CancelRename,
300 return []key.Binding{
425 return []key.Binding{
301 s.keyMap.UpDown,
426 s.keyMap.UpDown,
302− s.keyMap.Select,
427+ s.keyMap.Rename,
303 s.keyMap.Delete,
428 s.keyMap.Delete,
304 s.keyMap.Close,
430 s.keyMap.Close,
@@ -310,10 +436,10 @@ func (s *Session) ShortHelp() []key.Binding {
310func (s *Session) FullHelp() [][]key.Binding {
436func (s *Session) FullHelp() [][]key.Binding {
311 m := [][]key.Binding{}
437 m := [][]key.Binding{}
312 slice := []key.Binding{
438 slice := []key.Binding{
313− s.keyMap.Select,
439+ s.keyMap.UpDown,
314− s.keyMap.Next,
440+ s.keyMap.Rename,
316 s.keyMap.Delete,
441 s.keyMap.Delete,
317 s.keyMap.Close,
443 s.keyMap.Close,
@@ -323,6 +449,11 @@ func (s *Session) FullHelp() [][]key.Binding {
323 s.keyMap.ConfirmDelete,
449 s.keyMap.ConfirmDelete,
324 s.keyMap.CancelDelete,
450 s.keyMap.CancelDelete,
452+ case sessionsModeUpdating:
453+ slice = []key.Binding{
454+ s.keyMap.ConfirmRename,
455+ s.keyMap.CancelRename,
327 for i := 0; i < len(slice); i += 4 {
458 for i := 0; i < len(slice); i += 4 {
328 end := min(i+4, len(slice))
459 end := min(i+4, len(slice))