adamdottv 8 месяцев назад
Родитель
Сommit
fe86e58bbb

+ 71 - 102
packages/tui/internal/components/dialog/session.go

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

+ 2 - 3
packages/tui/internal/styles/markdown.go

@@ -132,9 +132,8 @@ func generateMarkdownStyleConfig(backgroundColor compat.AdaptiveColor) ansi.Styl
 			Color:       stringPtr(AdaptiveColorToString(t.MarkdownListEnumeration())),
 		},
 		Task: ansi.StyleTask{
-			StylePrimitive: ansi.StylePrimitive{},
-			Ticked:         "[✓] ",
-			Unticked:       "[ ] ",
+			Ticked:   "[✓] ",
+			Unticked: "[ ] ",
 		},
 		Link: ansi.StylePrimitive{
 			Color:     stringPtr(AdaptiveColorToString(t.MarkdownLink())),