|
|
@@ -4,6 +4,7 @@ import (
|
|
|
"github.com/charmbracelet/bubbles/v2/key"
|
|
|
tea "github.com/charmbracelet/bubbletea/v2"
|
|
|
"github.com/charmbracelet/lipgloss/v2"
|
|
|
+ utilComponents "github.com/sst/opencode/internal/components/util"
|
|
|
"github.com/sst/opencode/internal/layout"
|
|
|
"github.com/sst/opencode/internal/styles"
|
|
|
"github.com/sst/opencode/internal/theme"
|
|
|
@@ -24,32 +25,43 @@ type SessionDialog interface {
|
|
|
SetSelectedSession(sessionID string)
|
|
|
}
|
|
|
|
|
|
+type sessionItem struct {
|
|
|
+ session client.SessionInfo
|
|
|
+}
|
|
|
+
|
|
|
+func (s sessionItem) Render(selected bool, width int) string {
|
|
|
+ t := theme.CurrentTheme()
|
|
|
+ baseStyle := styles.BaseStyle().
|
|
|
+ Width(width - 2).
|
|
|
+ Background(t.Background())
|
|
|
+
|
|
|
+ if selected {
|
|
|
+ baseStyle = baseStyle.
|
|
|
+ Background(t.Primary()).
|
|
|
+ Foreground(t.Background()).
|
|
|
+ Bold(true)
|
|
|
+ } else {
|
|
|
+ baseStyle = baseStyle.
|
|
|
+ Foreground(t.Text())
|
|
|
+ }
|
|
|
+
|
|
|
+ return baseStyle.Padding(0, 1).Render(s.session.Title)
|
|
|
+}
|
|
|
+
|
|
|
type sessionDialogComponent struct {
|
|
|
sessions []client.SessionInfo
|
|
|
- selectedIdx int
|
|
|
width int
|
|
|
height int
|
|
|
selectedSessionID string
|
|
|
+ list utilComponents.SimpleList[sessionItem]
|
|
|
}
|
|
|
|
|
|
type sessionKeyMap struct {
|
|
|
- Up key.Binding
|
|
|
- Down key.Binding
|
|
|
Enter key.Binding
|
|
|
Escape key.Binding
|
|
|
- J key.Binding
|
|
|
- K key.Binding
|
|
|
}
|
|
|
|
|
|
var sessionKeys = sessionKeyMap{
|
|
|
- Up: key.NewBinding(
|
|
|
- key.WithKeys("up"),
|
|
|
- key.WithHelp("↑", "previous session"),
|
|
|
- ),
|
|
|
- Down: key.NewBinding(
|
|
|
- key.WithKeys("down"),
|
|
|
- key.WithHelp("↓", "next session"),
|
|
|
- ),
|
|
|
Enter: key.NewBinding(
|
|
|
key.WithKeys("enter"),
|
|
|
key.WithHelp("enter", "select session"),
|
|
|
@@ -58,14 +70,6 @@ var sessionKeys = sessionKeyMap{
|
|
|
key.WithKeys("esc"),
|
|
|
key.WithHelp("esc", "close"),
|
|
|
),
|
|
|
- J: key.NewBinding(
|
|
|
- key.WithKeys("j"),
|
|
|
- key.WithHelp("j", "next session"),
|
|
|
- ),
|
|
|
- K: key.NewBinding(
|
|
|
- key.WithKeys("k"),
|
|
|
- key.WithHelp("k", "previous session"),
|
|
|
- ),
|
|
|
}
|
|
|
|
|
|
func (s *sessionDialogComponent) Init() tea.Cmd {
|
|
|
@@ -79,19 +83,9 @@ func (s *sessionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
s.height = msg.Height
|
|
|
case tea.KeyMsg:
|
|
|
switch {
|
|
|
- case key.Matches(msg, sessionKeys.Up) || key.Matches(msg, sessionKeys.K):
|
|
|
- if s.selectedIdx > 0 {
|
|
|
- s.selectedIdx--
|
|
|
- }
|
|
|
- return s, nil
|
|
|
- case key.Matches(msg, sessionKeys.Down) || key.Matches(msg, sessionKeys.J):
|
|
|
- if s.selectedIdx < len(s.sessions)-1 {
|
|
|
- s.selectedIdx++
|
|
|
- }
|
|
|
- return s, nil
|
|
|
case key.Matches(msg, sessionKeys.Enter):
|
|
|
- if len(s.sessions) > 0 {
|
|
|
- selectedSession := s.sessions[s.selectedIdx]
|
|
|
+ if item, idx := s.list.GetSelectedItem(); idx >= 0 {
|
|
|
+ selectedSession := item.session
|
|
|
s.selectedSessionID = selectedSession.Id
|
|
|
|
|
|
return s, util.CmdHandler(CloseSessionDialogMsg{
|
|
|
@@ -100,110 +94,82 @@ func (s *sessionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
|
}
|
|
|
case key.Matches(msg, sessionKeys.Escape):
|
|
|
return s, util.CmdHandler(CloseSessionDialogMsg{})
|
|
|
+ default:
|
|
|
+ // Pass other key messages to the list component
|
|
|
+ var cmd tea.Cmd
|
|
|
+ listModel, cmd := s.list.Update(msg)
|
|
|
+ s.list = listModel.(utilComponents.SimpleList[sessionItem])
|
|
|
+ return s, cmd
|
|
|
}
|
|
|
}
|
|
|
- return s, nil
|
|
|
+
|
|
|
+ // For non-key messages
|
|
|
+ var cmd tea.Cmd
|
|
|
+ listModel, cmd := s.list.Update(msg)
|
|
|
+ s.list = listModel.(utilComponents.SimpleList[sessionItem])
|
|
|
+ return s, cmd
|
|
|
}
|
|
|
|
|
|
func (s *sessionDialogComponent) View() string {
|
|
|
t := theme.CurrentTheme()
|
|
|
- baseStyle := styles.BaseStyle()
|
|
|
+ baseStyle := styles.BaseStyle().Background(t.Background())
|
|
|
+ width := layout.Current.Container.Width - 4
|
|
|
|
|
|
if len(s.sessions) == 0 {
|
|
|
return baseStyle.Padding(1, 2).
|
|
|
Border(lipgloss.RoundedBorder()).
|
|
|
BorderBackground(t.Background()).
|
|
|
BorderForeground(t.TextMuted()).
|
|
|
- Width(40).
|
|
|
+ Width(width).
|
|
|
Render("No sessions available")
|
|
|
}
|
|
|
|
|
|
- // Calculate max width needed for session titles
|
|
|
- maxWidth := 40 // Minimum width
|
|
|
- for _, sess := range s.sessions {
|
|
|
- if len(sess.Title) > maxWidth-4 { // Account for padding
|
|
|
- maxWidth = len(sess.Title) + 0
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- maxWidth = max(30, min(maxWidth, s.width-15)) // Limit width to avoid overflow
|
|
|
-
|
|
|
- // Limit height to avoid taking up too much screen space
|
|
|
- maxVisibleSessions := min(10, len(s.sessions))
|
|
|
-
|
|
|
- // Build the session list
|
|
|
- sessionItems := make([]string, 0, maxVisibleSessions)
|
|
|
- startIdx := 0
|
|
|
-
|
|
|
- // If we have more sessions than can be displayed, adjust the start index
|
|
|
- if len(s.sessions) > maxVisibleSessions {
|
|
|
- // Center the selected item when possible
|
|
|
- halfVisible := maxVisibleSessions / 2
|
|
|
- if s.selectedIdx >= halfVisible && s.selectedIdx < len(s.sessions)-halfVisible {
|
|
|
- startIdx = s.selectedIdx - halfVisible
|
|
|
- } else if s.selectedIdx >= len(s.sessions)-halfVisible {
|
|
|
- startIdx = len(s.sessions) - maxVisibleSessions
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- endIdx := min(startIdx+maxVisibleSessions, len(s.sessions))
|
|
|
-
|
|
|
- for i := startIdx; i < endIdx; i++ {
|
|
|
- sess := s.sessions[i]
|
|
|
- itemStyle := baseStyle.Width(maxWidth)
|
|
|
-
|
|
|
- if i == s.selectedIdx {
|
|
|
- itemStyle = itemStyle.
|
|
|
- Background(t.Primary()).
|
|
|
- Foreground(t.Background()).
|
|
|
- Bold(true)
|
|
|
- }
|
|
|
-
|
|
|
- sessionItems = append(sessionItems, itemStyle.Padding(0, 1).Render(sess.Title))
|
|
|
- }
|
|
|
+ // Set the max width for the list
|
|
|
+ s.list.SetMaxWidth(width)
|
|
|
|
|
|
title := baseStyle.
|
|
|
Foreground(t.Primary()).
|
|
|
Bold(true).
|
|
|
- Width(maxWidth).
|
|
|
+ Width(width).
|
|
|
Padding(0, 1).
|
|
|
Render("Switch Session")
|
|
|
|
|
|
content := lipgloss.JoinVertical(
|
|
|
lipgloss.Left,
|
|
|
title,
|
|
|
- baseStyle.Width(maxWidth).Render(""),
|
|
|
- baseStyle.Width(maxWidth-2).Render(lipgloss.JoinVertical(lipgloss.Left, sessionItems...)),
|
|
|
- baseStyle.Width(maxWidth).Render(""),
|
|
|
+ // baseStyle.Width(width).Render(""),
|
|
|
+ "",
|
|
|
+ s.list.View(),
|
|
|
+ "",
|
|
|
+ // baseStyle.Width(width).Render(""),
|
|
|
)
|
|
|
|
|
|
return baseStyle.Padding(1, 2).
|
|
|
Border(lipgloss.RoundedBorder()).
|
|
|
BorderBackground(t.Background()).
|
|
|
BorderForeground(t.TextMuted()).
|
|
|
- Width(lipgloss.Width(content) + 4).
|
|
|
+ Width(layout.Current.Container.Width).
|
|
|
Render(content)
|
|
|
}
|
|
|
|
|
|
func (s *sessionDialogComponent) BindingKeys() []key.Binding {
|
|
|
- return layout.KeyMapToSlice(sessionKeys)
|
|
|
+ // Combine session dialog keys with list keys
|
|
|
+ dialogKeys := layout.KeyMapToSlice(sessionKeys)
|
|
|
+ listKeys := s.list.BindingKeys()
|
|
|
+ return append(dialogKeys, listKeys...)
|
|
|
}
|
|
|
|
|
|
func (s *sessionDialogComponent) SetSessions(sessions []client.SessionInfo) {
|
|
|
s.sessions = sessions
|
|
|
|
|
|
- // If we have a selected session ID, find its index
|
|
|
- if s.selectedSessionID != "" {
|
|
|
- for i, sess := range sessions {
|
|
|
- if sess.Id == s.selectedSessionID {
|
|
|
- s.selectedIdx = i
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
+ // Convert sessions to sessionItems
|
|
|
+ var sessionItems []sessionItem
|
|
|
+
|
|
|
+ for _, sess := range sessions {
|
|
|
+ sessionItems = append(sessionItems, sessionItem{session: sess})
|
|
|
}
|
|
|
|
|
|
- // Default to first session if selected not found
|
|
|
- s.selectedIdx = 0
|
|
|
+ s.list.SetItems(sessionItems)
|
|
|
}
|
|
|
|
|
|
func (s *sessionDialogComponent) SetSelectedSession(sessionID string) {
|
|
|
@@ -211,20 +177,23 @@ func (s *sessionDialogComponent) SetSelectedSession(sessionID string) {
|
|
|
|
|
|
// Update the selected index if sessions are already loaded
|
|
|
if len(s.sessions) > 0 {
|
|
|
- for i, sess := range s.sessions {
|
|
|
- if sess.Id == sessionID {
|
|
|
- s.selectedIdx = i
|
|
|
- return
|
|
|
- }
|
|
|
- }
|
|
|
+ // Re-set the sessions to update the selection
|
|
|
+ s.SetSessions(s.sessions)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// NewSessionDialogCmp creates a new session switching dialog
|
|
|
func NewSessionDialogCmp() SessionDialog {
|
|
|
+ list := utilComponents.NewSimpleList[sessionItem](
|
|
|
+ []sessionItem{},
|
|
|
+ 10, // maxVisibleSessions
|
|
|
+ "No sessions available",
|
|
|
+ true, // useAlphaNumericKeys
|
|
|
+ )
|
|
|
+
|
|
|
return &sessionDialogComponent{
|
|
|
sessions: []client.SessionInfo{},
|
|
|
- selectedIdx: 0,
|
|
|
selectedSessionID: "",
|
|
|
+ list: list,
|
|
|
}
|
|
|
}
|