diff options
| author | adamdotdevin <[email protected]> | 2025-07-16 12:42:52 -0500 |
|---|---|---|
| committer | adamdotdevin <[email protected]> | 2025-07-16 12:43:02 -0500 |
| commit | cdc1d8a94d12a31d5d162cb37744305738177ac2 (patch) | |
| tree | a9a9494ddc5b4a1a30c28e9ef19c99541292e630 /packages/tui/internal/components/chat | |
| parent | fdd6d6600f090ad4f15b2bbfc0bb75c36a722f4e (diff) | |
| download | opencode-cdc1d8a94d12a31d5d162cb37744305738177ac2.tar.gz opencode-cdc1d8a94d12a31d5d162cb37744305738177ac2.zip | |
feat(tui): layout config to render full width
Diffstat (limited to 'packages/tui/internal/components/chat')
| -rw-r--r-- | packages/tui/internal/components/chat/editor.go | 25 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 21 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 122 |
3 files changed, 115 insertions, 53 deletions
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index 67f1f75ea..de74b4895 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -27,8 +27,8 @@ import ( type EditorComponent interface { tea.Model - View(width int) string - Content(width int) string + tea.ViewModel + Content() string Lines() int Value() string Length() int @@ -46,6 +46,7 @@ type EditorComponent interface { type editorComponent struct { app *app.App + width int textarea textarea.Model spinner spinner.Model interruptKeyInDebounce bool @@ -61,6 +62,12 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + m.width = min(msg.Width-4, app.MAX_CONTAINER_WIDTH) + if m.app.Config.Layout == opencode.LayoutConfigStretch { + m.width = msg.Width - 4 + } + return m, nil case spinner.TickMsg: m.spinner, cmd = m.spinner.Update(msg) return m, cmd @@ -227,7 +234,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } -func (m *editorComponent) Content(width int) string { +func (m *editorComponent) Content() string { t := theme.CurrentTheme() base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render @@ -236,7 +243,7 @@ func (m *editorComponent) Content(width int) string { Bold(true) prompt := promptStyle.Render(">") - m.textarea.SetWidth(width - 6) + m.textarea.SetWidth(m.width - 6) textarea := lipgloss.JoinHorizontal( lipgloss.Top, prompt, @@ -248,7 +255,7 @@ func (m *editorComponent) Content(width int) string { } textarea = styles.NewStyle(). Background(t.BackgroundElement()). - Width(width). + Width(m.width). PaddingTop(1). PaddingBottom(1). BorderStyle(lipgloss.ThickBorder()). @@ -284,7 +291,7 @@ func (m *editorComponent) Content(width int) string { model = muted(m.app.Provider.Name) + base(" "+m.app.Model.Name) } - space := width - 2 - lipgloss.Width(model) - lipgloss.Width(hint) + space := m.width - 2 - lipgloss.Width(model) - lipgloss.Width(hint) spacer := styles.NewStyle().Background(t.Background()).Width(space).Render("") info := hint + spacer + model @@ -294,10 +301,10 @@ func (m *editorComponent) Content(width int) string { return content } -func (m *editorComponent) View(width int) string { +func (m *editorComponent) View() string { if m.Lines() > 1 { return lipgloss.Place( - width, + m.width, 5, lipgloss.Center, lipgloss.Center, @@ -305,7 +312,7 @@ func (m *editorComponent) View(width int) string { styles.WhitespaceStyle(theme.CurrentTheme().Background()), ) } - return m.Content(width) + return m.Content() } func (m *editorComponent) Focused() bool { diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 35ff675bd..d3263b053 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -10,6 +10,7 @@ import ( "github.com/charmbracelet/lipgloss/v2" "github.com/charmbracelet/lipgloss/v2/compat" "github.com/charmbracelet/x/ansi" + "github.com/muesli/reflow/truncate" "github.com/sst/opencode-sdk-go" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/diff" @@ -319,11 +320,19 @@ func renderToolDetails( if diffField != nil { patch := diffField.(string) var formattedDiff string - formattedDiff, _ = diff.FormatUnifiedDiff( - filename, - patch, - diff.WithWidth(width-2), - ) + if width < 120 { + formattedDiff, _ = diff.FormatUnifiedDiff( + filename, + patch, + diff.WithWidth(width-2), + ) + } else { + formattedDiff, _ = diff.FormatDiff( + filename, + patch, + diff.WithWidth(width-2), + ) + } body = strings.TrimSpace(formattedDiff) style := styles.NewStyle(). Background(backgroundColor). @@ -551,6 +560,8 @@ func renderToolTitle( toolName := renderToolName(toolCall.Tool) title = fmt.Sprintf("%s %s", toolName, toolArgs) } + + title = truncate.StringWithTail(title, uint(width-6), "...") return title } diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index 739f69109..e17847645 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -19,8 +19,7 @@ import ( type MessagesComponent interface { tea.Model - View(width, height int) string - SetWidth(width int) tea.Cmd + tea.ViewModel PageUp() (tea.Model, tea.Cmd) PageDown() (tea.Model, tea.Cmd) HalfPageUp() (tea.Model, tea.Cmd) @@ -32,8 +31,9 @@ type MessagesComponent interface { } type messagesComponent struct { - width int + width, height int app *app.App + header string viewport viewport.Model cache *PartCache rendering bool @@ -53,6 +53,17 @@ func (m *messagesComponent) Init() tea.Cmd { func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { + case tea.WindowSizeMsg: + effectiveWidth := msg.Width - 4 + // Clear cache on resize since width affects rendering + if m.width != effectiveWidth { + m.cache.Clear() + } + m.width = effectiveWidth + m.height = msg.Height - 7 + m.viewport.SetWidth(m.width) + m.header = m.renderHeader() + return m, m.Reload() case app.SendMsg: m.viewport.GotoBottom() m.tail = true @@ -82,21 +93,18 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case opencode.EventListResponseEventSessionUpdated: if msg.Properties.Info.ID == m.app.Session.ID { - m.renderView(m.width) - if m.tail { - m.viewport.GotoBottom() - } + m.header = m.renderHeader() } case opencode.EventListResponseEventMessageUpdated: if msg.Properties.Info.SessionID == m.app.Session.ID { - m.renderView(m.width) + m.renderView() if m.tail { m.viewport.GotoBottom() } } case opencode.EventListResponseEventMessagePartUpdated: if msg.Properties.Part.SessionID == m.app.Session.ID { - m.renderView(m.width) + m.renderView() if m.tail { m.viewport.GotoBottom() } @@ -111,10 +119,12 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Batch(cmds...) } -func (m *messagesComponent) renderView(width int) { +func (m *messagesComponent) renderView() { measure := util.Measure("messages.renderView") defer measure("messageCount", len(m.app.Messages)) + m.header = m.renderHeader() + t := theme.CurrentTheme() blocks := make([]string, 0) m.partCount = 0 @@ -122,6 +132,11 @@ func (m *messagesComponent) renderView(width int) { orphanedToolCalls := make([]opencode.ToolPart, 0) + width := min(m.width, app.MAX_CONTAINER_WIDTH) + if m.app.Config.Layout == opencode.LayoutConfigStretch { + width = m.width + } + for _, message := range m.app.Messages { var content string var cached bool @@ -185,6 +200,12 @@ func (m *messagesComponent) renderView(width int) { width, files, ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) m.cache.Set(key, content) } if content != "" { @@ -246,6 +267,12 @@ func (m *messagesComponent) renderView(width int) { "", toolCallParts..., ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) m.cache.Set(key, content) } } else { @@ -259,6 +286,12 @@ func (m *messagesComponent) renderView(width int) { "", toolCallParts..., ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) } if content != "" { m.partCount++ @@ -273,6 +306,13 @@ func (m *messagesComponent) renderView(width int) { continue } + width := width + if m.app.Config.Layout == opencode.LayoutConfigAuto && + part.Tool == "edit" && + part.State.Error == "" { + width = min(m.width, app.EDIT_DIFF_MAX_WIDTH) + } + if part.State.Status == opencode.ToolPartStateStatusCompleted || part.State.Status == opencode.ToolPartStateStatusError { key := m.cache.GenerateKey(casted.ID, part.ID, @@ -286,6 +326,12 @@ func (m *messagesComponent) renderView(width int) { part, width, ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) m.cache.Set(key, content) } } else { @@ -295,6 +341,12 @@ func (m *messagesComponent) renderView(width int) { part, width, ) + content = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + content, + styles.WhitespaceStyle(t.Background()), + ) } if content != "" { m.partCount++ @@ -333,22 +385,27 @@ func (m *messagesComponent) renderView(width int) { } } + m.viewport.SetHeight(m.height - lipgloss.Height(m.header)) m.viewport.SetContent("\n" + strings.Join(blocks, "\n\n")) - } -func (m *messagesComponent) header(width int) string { +func (m *messagesComponent) renderHeader() string { if m.app.Session.ID == "" { return "" } + headerWidth := min(m.width, app.MAX_CONTAINER_WIDTH) + if m.app.Config.Layout == opencode.LayoutConfigStretch { + headerWidth = m.width + } + t := theme.CurrentTheme() base := styles.NewStyle().Foreground(t.Text()).Background(t.Background()).Render muted := styles.NewStyle().Foreground(t.TextMuted()).Background(t.Background()).Render headerLines := []string{} headerLines = append( headerLines, - util.ToMarkdown("# "+m.app.Session.Title, width-6, t.Background()), + util.ToMarkdown("# "+m.app.Session.Title, headerWidth-6, t.Background()), ) share := "" @@ -397,7 +454,7 @@ func (m *messagesComponent) header(width int) string { Direction: layout.Row, Justify: layout.JustifySpaceBetween, Align: layout.AlignStretch, - Width: width - 6, + Width: headerWidth - 6, }, layout.FlexItem{ View: share, @@ -408,12 +465,10 @@ func (m *messagesComponent) header(width int) string { ) headerLines = append(headerLines, share) - header := strings.Join(headerLines, "\n") - header = styles.NewStyle(). Background(t.Background()). - Width(width). + Width(headerWidth). PaddingLeft(2). PaddingRight(2). BorderLeft(true). @@ -422,6 +477,12 @@ func (m *messagesComponent) header(width int) string { BorderForeground(t.BackgroundElement()). BorderStyle(lipgloss.ThickBorder()). Render(header) + header = lipgloss.PlaceHorizontal( + m.width, + lipgloss.Center, + header, + styles.WhitespaceStyle(t.Background()), + ) return "\n" + header + "\n" } @@ -473,44 +534,27 @@ func formatTokensAndCost( ) } -func (m *messagesComponent) View(width, height int) string { +func (m *messagesComponent) View() string { t := theme.CurrentTheme() if m.rendering { return lipgloss.Place( - width, - height, + m.width, + m.height, lipgloss.Center, lipgloss.Center, styles.NewStyle().Background(t.Background()).Render(""), styles.WhitespaceStyle(t.Background()), ) } - header := m.header(width) - m.viewport.SetWidth(width) - m.viewport.SetHeight(height - lipgloss.Height(header)) return styles.NewStyle(). Background(t.Background()). - Render(header + "\n" + m.viewport.View()) -} - -func (m *messagesComponent) SetWidth(width int) tea.Cmd { - if m.width == width { - return nil - } - // Clear cache on resize since width affects rendering - if m.width != width { - m.cache.Clear() - } - m.width = width - m.viewport.SetWidth(width) - m.renderView(width) - return nil + Render(m.header + "\n" + m.viewport.View()) } func (m *messagesComponent) Reload() tea.Cmd { return func() tea.Msg { - m.renderView(m.width) + m.renderView() return renderFinishedMsg{} } } |
