From ac777b77cfbab686a8f924293626df5781af56e9 Mon Sep 17 00:00:00 2001 From: adamdottv <2363879+adamdottv@users.noreply.github.com> Date: Wed, 18 Jun 2025 15:12:18 -0500 Subject: [PATCH] fix(tui): modal visuals --- .../tui/internal/components/dialog/help.go | 2 +- .../tui/internal/components/dialog/models.go | 23 +++++----------- .../tui/internal/components/dialog/session.go | 20 +++++++------- packages/tui/internal/components/list/list.go | 5 ++-- .../tui/internal/components/modal/modal.go | 26 ++++++++++++++----- packages/tui/internal/tui/tui.go | 6 ++++- 6 files changed, 44 insertions(+), 38 deletions(-) diff --git a/packages/tui/internal/components/dialog/help.go b/packages/tui/internal/components/dialog/help.go index 6041fd5e..87d6e89f 100644 --- a/packages/tui/internal/components/dialog/help.go +++ b/packages/tui/internal/components/dialog/help.go @@ -83,6 +83,6 @@ type HelpDialog interface { func NewHelpDialog(commands []commands.Command) HelpDialog { return &helpDialog{ commands: commands, - modal: modal.New(), + modal: modal.New(modal.WithTitle("Help")), } } diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go index dfb11dff..786b092c 100644 --- a/packages/tui/internal/components/dialog/models.go +++ b/packages/tui/internal/components/dialog/models.go @@ -180,6 +180,7 @@ func (m *modelDialog) switchProvider(offset int) { m.hScrollOffset = newOffset m.provider = m.availableProviders[m.hScrollOffset] + m.modal.SetTitle(fmt.Sprintf("Select %s Model", m.provider.Name)) m.setupModelsForProvider(m.provider.Id) } @@ -189,14 +190,6 @@ func (m *modelDialog) View() string { Background(t.BackgroundElement()). Foreground(t.Text()) - // Capitalize first letter of provider name - title := baseStyle. - Foreground(t.Primary()). - Bold(true). - Width(maxDialogWidth). - Padding(0, 0, 1). - Render(fmt.Sprintf("Select %s Model", m.provider.Name)) - // Render visible models endIdx := min(m.scrollOffset+numVisibleModels, len(m.provider.Models)) modelItems := make([]string, 0, endIdx-m.scrollOffset) @@ -217,8 +210,9 @@ func (m *modelDialog) View() string { content := lipgloss.JoinVertical( lipgloss.Left, - title, - baseStyle.Width(maxDialogWidth).Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)), + baseStyle. + Width(maxDialogWidth). + Render(lipgloss.JoinVertical(lipgloss.Left, modelItems...)), scrollIndicator, ) @@ -238,12 +232,7 @@ func (m *modelDialog) getScrollIndicators(maxWidth int) string { } if m.hScrollPossible { - if m.hScrollOffset > 0 { - indicator = "← " + indicator - } - if m.hScrollOffset < len(m.availableProviders)-1 { - indicator += "→" - } + indicator = "← " + indicator + "→" } if indicator == "" { @@ -313,6 +302,6 @@ func NewModelDialog(app *app.App) ModelDialog { hScrollOffset: 0, hScrollPossible: len(availableProviders) > 1, provider: availableProviders[0], - modal: modal.New(), + modal: modal.New(modal.WithTitle(fmt.Sprintf("Select %s Model", availableProviders[0].Name))), } } diff --git a/packages/tui/internal/components/dialog/session.go b/packages/tui/internal/components/dialog/session.go index b59ebe3d..52eac1e2 100644 --- a/packages/tui/internal/components/dialog/session.go +++ b/packages/tui/internal/components/dialog/session.go @@ -19,14 +19,12 @@ type SessionDialog interface { layout.Modal } -type sessionItem struct { - session client.SessionInfo -} +type sessionItem client.SessionInfo func (s sessionItem) Render(selected bool, width int) string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle(). - Width(width - 2). + Width(width - 4). Background(t.BackgroundElement()) if selected { @@ -39,7 +37,7 @@ func (s sessionItem) Render(selected bool, width int) string { Foreground(t.Text()) } - return baseStyle.Padding(0, 1).Render(s.session.Title) + return baseStyle.Padding(0, 1).Render(s.Title) } type sessionDialog struct { @@ -60,15 +58,14 @@ func (s *sessionDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) { s.width = msg.Width s.height = msg.Height s.list.SetMaxWidth(layout.Current.Container.Width - 12) - case tea.KeyMsg: + case tea.KeyPressMsg: switch msg.String() { case "enter": if item, idx := s.list.GetSelectedItem(); idx >= 0 { - selectedSession := item.session - s.selectedSessionID = selectedSession.Id + s.selectedSessionID = item.Id return s, tea.Sequence( util.CmdHandler(modal.CloseModalMsg{}), - util.CmdHandler(app.SessionSelectedMsg(&selectedSession)), + util.CmdHandler(app.SessionSelectedMsg(&item)), ) } } @@ -94,7 +91,10 @@ func NewSessionDialog(app *app.App) SessionDialog { var sessionItems []sessionItem for _, sess := range sessions { - sessionItems = append(sessionItems, sessionItem{session: sess}) + if sess.ParentID != nil { + continue + } + sessionItems = append(sessionItems, sessionItem(sess)) } list := list.NewListComponent( diff --git a/packages/tui/internal/components/list/list.go b/packages/tui/internal/components/list/list.go index cefcaabe..cd0bf18c 100644 --- a/packages/tui/internal/components/list/list.go +++ b/packages/tui/internal/components/list/list.go @@ -1,9 +1,10 @@ package list import ( + "strings" + "github.com/charmbracelet/bubbles/v2/key" tea "github.com/charmbracelet/bubbletea/v2" - "github.com/charmbracelet/lipgloss/v2" ) type ListItem interface { @@ -148,7 +149,7 @@ func (c *listComponent[T]) View() string { listItems = append(listItems, title) } - return lipgloss.JoinVertical(lipgloss.Left, listItems...) + return strings.Join(listItems, "\n") } func NewListComponent[T ListItem](items []T, maxVisibleItems int, fallbackMsg string, useAlphaNumericKeys bool) List[T] { diff --git a/packages/tui/internal/components/modal/modal.go b/packages/tui/internal/components/modal/modal.go index 88e37c50..e57de0af 100644 --- a/packages/tui/internal/components/modal/modal.go +++ b/packages/tui/internal/components/modal/modal.go @@ -1,6 +1,8 @@ package modal import ( + "strings" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" @@ -66,6 +68,10 @@ func New(opts ...ModalOption) *Modal { return m } +func (m *Modal) SetTitle(title string) { + m.title = title +} + // Render renders the modal centered on the screen func (m *Modal) Render(contentView string, background string) string { t := theme.CurrentTheme() @@ -95,15 +101,21 @@ func (m *Modal) Render(contentView string, background string) string { titleStyle := baseStyle. Foreground(t.Primary()). Bold(true). - Width(innerWidth). Padding(0, 1) - titleView := titleStyle.Render(m.title) - finalContent = lipgloss.JoinVertical( - lipgloss.Left, - titleView, - contentView, - ) + // titleView := titleStyle.Render(m.title) + escStyle := baseStyle.Foreground(t.TextMuted()).Bold(false) + 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) + spacer := strings.Repeat(" ", spacesNeeded) + titleLine := m.title + spacer + escText + titleLine = titleStyle.Render(titleLine) + + finalContent = strings.Join([]string{titleLine, contentView}, "\n") + "\n" } else { finalContent = contentView } diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go index 9fe52cda..98d16ecc 100644 --- a/packages/tui/internal/tui/tui.go +++ b/packages/tui/internal/tui/tui.go @@ -204,8 +204,12 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { } a.layout.SetSize(a.width, a.height) case app.SessionSelectedMsg: + var err error a.app.Session = msg - a.app.Messages, _ = a.app.ListMessages(context.Background(), msg.Id) + a.app.Messages, err = a.app.ListMessages(context.Background(), msg.Id) + if err != nil { + slog.Error("Failed to list messages", "error", err) + } case app.ModelSelectedMsg: a.app.Provider = &msg.Provider a.app.Model = &msg.Model