summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-22 06:09:23 -0500
committeradamdottv <[email protected]>2025-06-22 06:09:23 -0500
commitdc1947838c2403b63f2c9c83f0729b5f7c946f76 (patch)
tree68a7def2ec0ad6e7522bc96c973812814420840c
parent3ea2daaa4c1fef54c3e827300a63bd3b41b88806 (diff)
downloadopencode-dc1947838c2403b63f2c9c83f0729b5f7c946f76.tar.gz
opencode-dc1947838c2403b63f2c9c83f0729b5f7c946f76.zip
fix(tui): cleanup modal visuals
-rw-r--r--packages/tui/internal/components/dialog/models.go216
-rw-r--r--packages/tui/internal/components/dialog/session.go60
-rw-r--r--packages/tui/internal/components/list/list.go5
-rw-r--r--packages/tui/internal/components/modal/modal.go4
4 files changed, 95 insertions, 190 deletions
diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go
index 786b092c0..5da3c9eef 100644
--- a/packages/tui/internal/components/dialog/models.go
+++ b/packages/tui/internal/components/dialog/models.go
@@ -11,6 +11,7 @@ import (
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/app"
+ "github.com/sst/opencode/internal/components/list"
"github.com/sst/opencode/internal/components/modal"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
@@ -33,20 +34,15 @@ type modelDialog struct {
app *app.App
availableProviders []client.ProviderInfo
provider client.ProviderInfo
-
- selectedIdx int
- width int
- height int
- scrollOffset int
- hScrollOffset int
- hScrollPossible bool
-
- modal *modal.Modal
+ width int
+ height int
+ hScrollOffset int
+ hScrollPossible bool
+ modal *modal.Modal
+ modelList list.List[list.StringItem]
}
type modelKeyMap struct {
- Up key.Binding
- Down key.Binding
Left key.Binding
Right key.Binding
Enter key.Binding
@@ -54,14 +50,6 @@ type modelKeyMap struct {
}
var modelKeys = modelKeyMap{
- Up: key.NewBinding(
- key.WithKeys("up", "k"),
- key.WithHelp("↑", "previous model"),
- ),
- Down: key.NewBinding(
- key.WithKeys("down", "j"),
- key.WithHelp("↓", "next model"),
- ),
Left: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("←", "scroll left"),
@@ -81,15 +69,7 @@ var modelKeys = modelKeyMap{
}
func (m *modelDialog) Init() tea.Cmd {
- // cfg := config.Get()
- // modelInfo := GetSelectedModel(cfg)
- // m.availableProviders = getEnabledProviders(cfg)
- // m.hScrollPossible = len(m.availableProviders) > 1
-
- // m.provider = modelInfo.Provider
- // m.hScrollOffset = findProviderIndex(m.availableProviders, m.provider)
-
- // m.setupModelsForProvider(m.provider)
+ m.setupModelsForProvider(m.provider.Id)
return nil
}
@@ -97,26 +77,32 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
- case key.Matches(msg, modelKeys.Up):
- m.moveSelectionUp()
- case key.Matches(msg, modelKeys.Down):
- m.moveSelectionDown()
case key.Matches(msg, modelKeys.Left):
if m.hScrollPossible {
m.switchProvider(-1)
}
+ return m, nil
case key.Matches(msg, modelKeys.Right):
if m.hScrollPossible {
m.switchProvider(1)
}
+ return m, nil
case key.Matches(msg, modelKeys.Enter):
+ selectedItem, _ := m.modelList.GetSelectedItem()
models := m.models()
+ var selectedModel client.ModelInfo
+ for _, model := range models {
+ if model.Name == string(selectedItem) {
+ selectedModel = model
+ break
+ }
+ }
return m, tea.Sequence(
util.CmdHandler(modal.CloseModalMsg{}),
util.CmdHandler(
app.ModelSelectedMsg{
Provider: m.provider,
- Model: models[m.selectedIdx],
+ Model: selectedModel,
}),
)
case key.Matches(msg, modelKeys.Escape):
@@ -127,7 +113,10 @@ func (m *modelDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.height = msg.Height
}
- return m, nil
+ // Update the list component
+ updatedList, cmd := m.modelList.Update(msg)
+ m.modelList = updatedList.(list.List[list.StringItem])
+ return m, cmd
}
func (m *modelDialog) models() []client.ModelInfo {
@@ -137,40 +126,9 @@ func (m *modelDialog) models() []client.ModelInfo {
return models
}
-// moveSelectionUp moves the selection up or wraps to bottom
-func (m *modelDialog) moveSelectionUp() {
- if m.selectedIdx > 0 {
- m.selectedIdx--
- } else {
- m.selectedIdx = len(m.provider.Models) - 1
- m.scrollOffset = max(0, len(m.provider.Models)-numVisibleModels)
- }
-
- // Keep selection visible
- if m.selectedIdx < m.scrollOffset {
- m.scrollOffset = m.selectedIdx
- }
-}
-
-// moveSelectionDown moves the selection down or wraps to top
-func (m *modelDialog) moveSelectionDown() {
- if m.selectedIdx < len(m.provider.Models)-1 {
- m.selectedIdx++
- } else {
- m.selectedIdx = 0
- m.scrollOffset = 0
- }
-
- // Keep selection visible
- if m.selectedIdx >= m.scrollOffset+numVisibleModels {
- m.scrollOffset = m.selectedIdx - (numVisibleModels - 1)
- }
-}
-
func (m *modelDialog) switchProvider(offset int) {
newOffset := m.hScrollOffset + offset
- // Ensure we stay within bounds
if newOffset < 0 {
newOffset = len(m.availableProviders) - 1
}
@@ -185,105 +143,46 @@ func (m *modelDialog) switchProvider(offset int) {
}
func (m *modelDialog) View() string {
- t := theme.CurrentTheme()
- baseStyle := lipgloss.NewStyle().
- Background(t.BackgroundElement()).
- Foreground(t.Text())
-
- // Render visible models
- endIdx := min(m.scrollOffset+numVisibleModels, len(m.provider.Models))
- modelItems := make([]string, 0, endIdx-m.scrollOffset)
-
- models := m.models()
- for i := m.scrollOffset; i < endIdx; i++ {
- itemStyle := baseStyle.Width(maxDialogWidth)
- if i == m.selectedIdx {
- itemStyle = itemStyle.
- Background(t.Primary()).
- Foreground(t.BackgroundElement()).
- Bold(true)
- }
- modelItems = append(modelItems, itemStyle.Render(models[i].Name))
- }
-
+ listView := m.modelList.View()
scrollIndicator := m.getScrollIndicators(maxDialogWidth)
-
- content := lipgloss.JoinVertical(
- lipgloss.Left,
- baseStyle.
- Width(maxDialogWidth).
- Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)),
- scrollIndicator,
- )
-
- return content
+ return strings.Join([]string{listView, scrollIndicator}, "\n")
}
func (m *modelDialog) getScrollIndicators(maxWidth int) string {
var indicator string
-
- if len(m.provider.Models) > numVisibleModels {
- if m.scrollOffset > 0 {
- indicator += "↑ "
- }
- if m.scrollOffset+numVisibleModels < len(m.provider.Models) {
- indicator += "↓ "
- }
- }
-
if m.hScrollPossible {
- indicator = "← " + indicator + "→"
+ indicator = "← → (switch provider) "
}
-
if indicator == "" {
return ""
}
t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle()
-
- return baseStyle.
- Foreground(t.Primary()).
+ return styles.BaseStyle().
+ Foreground(t.TextMuted()).
Width(maxWidth).
Align(lipgloss.Right).
- Bold(true).
Render(indicator)
}
-// findProviderIndex returns the index of the provider in the list, or -1 if not found
-// func findProviderIndex(providers []string, provider string) int {
-// for i, p := range providers {
-// if p == provider {
-// return i
-// }
-// }
-// return -1
-// }
-
-func (m *modelDialog) setupModelsForProvider(_ string) {
- m.selectedIdx = 0
- m.scrollOffset = 0
-
- // cfg := config.Get()
- // agentCfg := cfg.Agents[config.AgentPrimary]
- // selectedModelId := agentCfg.Model
+func (m *modelDialog) setupModelsForProvider(providerId string) {
+ models := m.models()
+ modelNames := make([]string, len(models))
+ for i, model := range models {
+ modelNames[i] = model.Name
+ }
- // m.provider = provider
- // m.models = getModelsForProvider(provider)
+ m.modelList = list.NewStringList(modelNames, numVisibleModels, "No models available", true)
+ m.modelList.SetMaxWidth(maxDialogWidth)
- // Try to select the current model if it belongs to this provider
- // if provider == models.SupportedModels[selectedModelId].Provider {
- // for i, model := range m.models {
- // if model.ID == selectedModelId {
- // m.selectedIdx = i
- // // Adjust scroll position to keep selected model visible
- // if m.selectedIdx >= numVisibleModels {
- // m.scrollOffset = m.selectedIdx - (numVisibleModels - 1)
- // }
- // break
- // }
- // }
- // }
+ if m.app.Provider != nil && m.app.Model != nil && m.app.Provider.Id == providerId {
+ for i, model := range models {
+ if model.Id == m.app.Model.Id {
+ m.modelList.SetSelectedIndex(i)
+ break
+ }
+ }
+ }
}
func (m *modelDialog) Render(background string) string {
@@ -297,11 +196,30 @@ func (s *modelDialog) Close() tea.Cmd {
func NewModelDialog(app *app.App) ModelDialog {
availableProviders, _ := app.ListProviders(context.Background())
- return &modelDialog{
+ currentProvider := availableProviders[0]
+ hScrollOffset := 0
+ if app.Provider != nil {
+ for i, provider := range availableProviders {
+ if provider.Id == app.Provider.Id {
+ currentProvider = provider
+ hScrollOffset = i
+ break
+ }
+ }
+ }
+
+ dialog := &modelDialog{
+ app: app,
availableProviders: availableProviders,
- hScrollOffset: 0,
+ hScrollOffset: hScrollOffset,
hScrollPossible: len(availableProviders) > 1,
- provider: availableProviders[0],
- modal: modal.New(modal.WithTitle(fmt.Sprintf("Select %s Model", availableProviders[0].Name))),
+ provider: currentProvider,
+ modal: modal.New(
+ modal.WithTitle(fmt.Sprintf("Select %s Model", currentProvider.Name)),
+ modal.WithMaxWidth(maxDialogWidth+4),
+ ),
}
+
+ dialog.setupModelsForProvider(currentProvider.Id)
+ return dialog
}
diff --git a/packages/tui/internal/components/dialog/session.go b/packages/tui/internal/components/dialog/session.go
index 52eac1e29..2887ae736 100644
--- a/packages/tui/internal/components/dialog/session.go
+++ b/packages/tui/internal/components/dialog/session.go
@@ -8,8 +8,6 @@ import (
"github.com/sst/opencode/internal/components/list"
"github.com/sst/opencode/internal/components/modal"
"github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/styles"
- "github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"github.com/sst/opencode/pkg/client"
)
@@ -19,33 +17,12 @@ type SessionDialog interface {
layout.Modal
}
-type sessionItem client.SessionInfo
-
-func (s sessionItem) Render(selected bool, width int) string {
- t := theme.CurrentTheme()
- baseStyle := styles.BaseStyle().
- Width(width - 4).
- Background(t.BackgroundElement())
-
- if selected {
- baseStyle = baseStyle.
- Background(t.Primary()).
- Foreground(t.BackgroundElement()).
- Bold(true)
- } else {
- baseStyle = baseStyle.
- Foreground(t.Text())
- }
-
- return baseStyle.Padding(0, 1).Render(s.Title)
-}
-
type sessionDialog struct {
- width int
- height int
- modal *modal.Modal
- selectedSessionID string
- list list.List[sessionItem]
+ width int
+ height int
+ modal *modal.Modal
+ sessions []client.SessionInfo
+ list list.List[list.StringItem]
}
func (s *sessionDialog) Init() tea.Cmd {
@@ -61,11 +38,11 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.KeyPressMsg:
switch msg.String() {
case "enter":
- if item, idx := s.list.GetSelectedItem(); idx >= 0 {
- s.selectedSessionID = item.Id
+ if _, idx := s.list.GetSelectedItem(); idx >= 0 && idx < len(s.sessions) {
+ selectedSession := s.sessions[idx]
return s, tea.Sequence(
util.CmdHandler(modal.CloseModalMsg{}),
- util.CmdHandler(app.SessionSelectedMsg(&item)),
+ util.CmdHandler(app.SessionSelectedMsg(&selectedSession)),
)
}
}
@@ -73,7 +50,7 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmd tea.Cmd
listModel, cmd := s.list.Update(msg)
- s.list = listModel.(list.List[sessionItem])
+ s.list = listModel.(list.List[list.StringItem])
return s, cmd
}
@@ -89,23 +66,30 @@ func (s *sessionDialog) Close() tea.Cmd {
func NewSessionDialog(app *app.App) SessionDialog {
sessions, _ := app.ListSessions(context.Background())
- var sessionItems []sessionItem
+ var filteredSessions []client.SessionInfo
+ var sessionTitles []string
for _, sess := range sessions {
if sess.ParentID != nil {
continue
}
- sessionItems = append(sessionItems, sessionItem(sess))
+ filteredSessions = append(filteredSessions, sess)
+ sessionTitles = append(sessionTitles, sess.Title)
}
- list := list.NewListComponent(
- sessionItems,
+ list := list.NewStringList(
+ sessionTitles,
10, // maxVisibleSessions
"No sessions available",
true, // useAlphaNumericKeys
)
+ list.SetMaxWidth(layout.Current.Container.Width - 12)
return &sessionDialog{
- list: list,
- modal: modal.New(modal.WithTitle("Switch Session"), modal.WithMaxWidth(80)),
+ sessions: filteredSessions,
+ list: list,
+ modal: modal.New(
+ modal.WithTitle("Switch Session"),
+ modal.WithMaxWidth(layout.Current.Container.Width-8),
+ ),
}
}
diff --git a/packages/tui/internal/components/list/list.go b/packages/tui/internal/components/list/list.go
index c6ddbaf7e..d5cc4b4f9 100644
--- a/packages/tui/internal/components/list/list.go
+++ b/packages/tui/internal/components/list/list.go
@@ -6,6 +6,7 @@ import (
"github.com/charmbracelet/bubbles/v2/key"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
+ "github.com/muesli/reflow/truncate"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
)
@@ -175,6 +176,8 @@ func (s StringItem) Render(selected bool, width int) string {
t := theme.CurrentTheme()
baseStyle := styles.BaseStyle()
+ truncatedStr := truncate.StringWithTail(string(s), uint(width-1), "...")
+
var itemStyle lipgloss.Style
if selected {
itemStyle = baseStyle.
@@ -187,7 +190,7 @@ func (s StringItem) Render(selected bool, width int) string {
PaddingLeft(1)
}
- return itemStyle.Render(string(s))
+ return itemStyle.Render(truncatedStr)
}
// NewStringList creates a new list component with string items
diff --git a/packages/tui/internal/components/modal/modal.go b/packages/tui/internal/components/modal/modal.go
index 98e34d627..62cafe843 100644
--- a/packages/tui/internal/components/modal/modal.go
+++ b/packages/tui/internal/components/modal/modal.go
@@ -103,13 +103,13 @@ func (m *Modal) Render(contentView string, background string) string {
Bold(true).
Padding(0, 1)
- escStyle := baseStyle.Foreground(t.TextMuted()).Bold(false)
+ escStyle := baseStyle.Foreground(t.TextMuted())
escText := escStyle.Render("esc")
// Calculate position for esc text
titleWidth := lipgloss.Width(m.title)
escWidth := lipgloss.Width(escText)
- spacesNeeded := max(0, innerWidth-titleWidth-escWidth-3)
+ spacesNeeded := max(0, innerWidth-titleWidth-escWidth-2)
spacer := strings.Repeat(" ", spacesNeeded)
titleLine := m.title + spacer + escText
titleLine = titleStyle.Render(titleLine)