diff options
| author | adamdottv <[email protected]> | 2025-06-12 05:35:40 -0500 |
|---|---|---|
| committer | adamdottv <[email protected]> | 2025-06-12 16:00:24 -0500 |
| commit | cce2e4ad754479917fc8f6f24c1421cf19c04573 (patch) | |
| tree | 62c0ec32358932a468af73b44fd28e7de7859bbe /packages/tui/internal/components | |
| parent | a1ce35c208bf9ebca37f722e845035bd7fd5e801 (diff) | |
| download | opencode-cce2e4ad754479917fc8f6f24c1421cf19c04573.tar.gz opencode-cce2e4ad754479917fc8f6f24c1421cf19c04573.zip | |
wip: refactoring tui
Diffstat (limited to 'packages/tui/internal/components')
20 files changed, 284 insertions, 462 deletions
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go index 52f198492..198958b09 100644 --- a/packages/tui/internal/components/chat/editor.go +++ b/packages/tui/internal/components/chat/editor.go @@ -5,15 +5,13 @@ import ( "log/slog" "os" "os/exec" - "slices" "strings" - "unicode" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/textarea" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/spinner" + "github.com/charmbracelet/bubbles/v2/textarea" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/dialog" "github.com/sst/opencode/internal/image" @@ -129,18 +127,18 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.attachments = nil return m, nil } - if m.deleteMode && len(msg.Runes) > 0 && unicode.IsDigit(msg.Runes[0]) { - num := int(msg.Runes[0] - '0') - m.deleteMode = false - if num < 10 && len(m.attachments) > num { - if num == 0 { - m.attachments = m.attachments[num+1:] - } else { - m.attachments = slices.Delete(m.attachments, num, num+1) - } - return m, nil - } - } + // if m.deleteMode && len(msg.Runes) > 0 && unicode.IsDigit(msg.Runes[0]) { + // num := int(msg.Runes[0] - '0') + // m.deleteMode = false + // if num < 10 && len(m.attachments) > num { + // if num == 0 { + // m.attachments = m.attachments[num+1:] + // } else { + // m.attachments = slices.Delete(m.attachments, num, num+1) + // } + // return m, nil + // } + // } if key.Matches(msg, messageKeys.PageUp) || key.Matches(msg, messageKeys.PageDown) || key.Matches(msg, messageKeys.HalfPageUp) || key.Matches(msg, messageKeys.HalfPageDown) { return m, nil @@ -258,7 +256,7 @@ func (m *editorComponent) View() string { m.textarea.View(), ) textarea = styles.BaseStyle(). - Width(m.width-2). + Width(m.width). // -2). Border(lipgloss.NormalBorder(), true, true). BorderForeground(t.Border()). Render(textarea) @@ -286,10 +284,7 @@ func (m *editorComponent) View() string { info, ) - return styles.ForceReplaceBackgroundWithLipgloss( - content, - t.Background(), - ) + return content } func (m *editorComponent) SetSize(width, height int) tea.Cmd { @@ -414,14 +409,14 @@ func createTextArea(existing *textarea.Model) textarea.Model { ta := textarea.New() ta.Placeholder = "It's prompting time..." - ta.BlurredStyle.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor) - ta.BlurredStyle.CursorLine = styles.BaseStyle().Background(bgColor) - ta.BlurredStyle.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor) - ta.BlurredStyle.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor) - ta.FocusedStyle.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor) - ta.FocusedStyle.CursorLine = styles.BaseStyle().Background(bgColor) - ta.FocusedStyle.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor) - ta.FocusedStyle.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor) + ta.Styles.Blurred.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor) + ta.Styles.Blurred.CursorLine = styles.BaseStyle().Background(bgColor) + ta.Styles.Blurred.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor) + ta.Styles.Blurred.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor) + ta.Styles.Focused.Base = styles.BaseStyle().Background(bgColor).Foreground(textColor) + ta.Styles.Focused.CursorLine = styles.BaseStyle().Background(bgColor) + ta.Styles.Focused.Placeholder = styles.BaseStyle().Background(bgColor).Foreground(textMutedColor) + ta.Styles.Focused.Text = styles.BaseStyle().Background(bgColor).Foreground(textColor) ta.Prompt = " " ta.ShowLineNumbers = false @@ -437,7 +432,7 @@ func createTextArea(existing *textarea.Model) textarea.Model { return ta } -func NewEditorComponent(app *app.App) tea.Model { +func NewEditorComponent(app *app.App) layout.ModelWithView { s := spinner.New(spinner.WithSpinner(spinner.Ellipsis), spinner.WithStyle(styles.Muted().Width(3))) ta := createTextArea(nil) diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go index 131898c84..199b4a070 100644 --- a/packages/tui/internal/components/chat/message.go +++ b/packages/tui/internal/components/chat/message.go @@ -9,7 +9,8 @@ import ( "time" "unicode" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/lipgloss/v2/compat" "github.com/charmbracelet/x/ansi" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/diff" @@ -21,8 +22,8 @@ import ( "golang.org/x/text/language" ) -func toMarkdown(content string, width int) string { - r := styles.GetMarkdownRenderer(width) +func toMarkdown(content string, width int, backgroundColor compat.AdaptiveColor) string { + r := styles.GetMarkdownRenderer(width, backgroundColor) content = strings.ReplaceAll(content, app.Info.Path.Root+"/", "") rendered, _ := r.Render(content) lines := strings.Split(rendered, "\n") @@ -50,7 +51,7 @@ func toMarkdown(content string, width int) string { type blockRenderer struct { align *lipgloss.Position - borderColor *lipgloss.AdaptiveColor + borderColor *compat.AdaptiveColor fullWidth bool paddingTop int paddingBottom int @@ -70,7 +71,7 @@ func WithAlign(align lipgloss.Position) renderingOption { } } -func WithBorderColor(color lipgloss.AdaptiveColor) renderingOption { +func WithBorderColor(color compat.AdaptiveColor) renderingOption { return func(c *blockRenderer) { c.borderColor = &color } @@ -137,9 +138,8 @@ func renderContentBlock(content string, options ...renderingOption) string { BorderLeftBackground(t.Background()) } - content = styles.ForceReplaceBackgroundWithLipgloss(content, t.BackgroundSubtle()) if renderer.fullWidth { - style = style.Width(layout.Current.Container.Width - 2) + style = style.Width(layout.Current.Container.Width) } content = style.Render(content) if renderer.paddingTop > 0 { @@ -152,13 +152,11 @@ func renderContentBlock(content string, options ...renderingOption) string { layout.Current.Container.Width, align, content, - lipgloss.WithWhitespaceBackground(t.Background()), ) content = lipgloss.PlaceHorizontal( layout.Current.Viewport.Width, lipgloss.Center, content, - lipgloss.WithWhitespaceBackground(t.Background()), ) return content } @@ -181,9 +179,7 @@ func renderText(message client.MessageInfo, text string, author string) string { // don't show the date if it's today timestamp = timestamp[12:] } - info := styles.BaseStyle(). - Foreground(t.TextMuted()). - Render(fmt.Sprintf("%s (%s)", author, timestamp)) + info := fmt.Sprintf("%s (%s)", author, timestamp) align := lipgloss.Left switch message.Role { @@ -195,7 +191,7 @@ func renderText(message client.MessageInfo, text string, author string) string { textWidth := lipgloss.Width(text) markdownWidth := min(textWidth, width-padding-4) // -4 for the border and padding - content := toMarkdown(text, markdownWidth) + content := toMarkdown(text, markdownWidth, t.BackgroundSubtle()) content = lipgloss.JoinVertical(align, content, info) switch message.Role { @@ -313,7 +309,7 @@ func renderToolInvocation( lipgloss.Center, lipgloss.Center, body, - lipgloss.WithWhitespaceBackground(t.Background()), + lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())), ) } case "opencode_write": @@ -328,7 +324,7 @@ func renderToolInvocation( command := toolArgsMap["command"].(string) stdout := metadata["stdout"].(string) body = fmt.Sprintf("```console\n> %s\n%s```", command, stdout) - body = toMarkdown(body, innerWidth) + body = toMarkdown(body, innerWidth, t.BackgroundSubtle()) body = renderContentBlock(body, WithFullWidth(), WithPaddingTop(1), WithPaddingBottom(1)) } case "opencode_webfetch": @@ -336,7 +332,7 @@ func renderToolInvocation( format := toolArgsMap["format"].(string) body = truncateHeight(body, 10) if format == "html" || format == "markdown" { - body = toMarkdown(body, innerWidth) + body = toMarkdown(body, innerWidth, t.BackgroundSubtle()) } body = renderContentBlock(body, WithFullWidth(), WithPaddingTop(1), WithPaddingBottom(1)) case "opencode_todowrite": @@ -356,7 +352,7 @@ func renderToolInvocation( body += fmt.Sprintf("- [ ] %s\n", content) } } - body = toMarkdown(body, innerWidth) + body = toMarkdown(body, innerWidth, t.BackgroundSubtle()) body = renderContentBlock(body, WithFullWidth(), WithPaddingTop(1), WithPaddingBottom(1)) } default: @@ -368,7 +364,7 @@ func renderToolInvocation( content := style.Render(title) content = lipgloss.PlaceHorizontal(layout.Current.Viewport.Width, lipgloss.Center, content) - content = styles.ForceReplaceBackgroundWithLipgloss(content, t.Background()) + // content = styles.ForceReplaceBackgroundWithLipgloss(content, t.Background()) if showResult && body != "" { content += "\n" + body } @@ -411,6 +407,7 @@ func WithTruncate(height int) fileRenderingOption { } func renderFile(filename string, content string, options ...fileRenderingOption) string { + t := theme.CurrentTheme() renderer := &fileRenderer{ filename: filename, content: content, @@ -419,7 +416,6 @@ func renderFile(filename string, content string, options ...fileRenderingOption) option(renderer) } - // TODO: is this even needed? lines := []string{} for line := range strings.SplitSeq(content, "\n") { line = strings.TrimRightFunc(line, unicode.IsSpace) @@ -428,23 +424,12 @@ func renderFile(filename string, content string, options ...fileRenderingOption) } content = strings.Join(lines, "\n") - width := layout.Current.Container.Width - 6 + width := layout.Current.Container.Width - 8 if renderer.height > 0 { content = truncateHeight(content, renderer.height) } content = fmt.Sprintf("```%s\n%s\n```", extension(renderer.filename), content) - content = toMarkdown(content, width) - - // ensure no line is wider than the width - // truncated := []string{} - // for line := range strings.SplitSeq(content, "\n") { - // line = strings.TrimRightFunc(line, unicode.IsSpace) - // // if lipgloss.Width(line) > width-3 { - // line = ansi.Truncate(line, width-3, "") - // // } - // truncated = append(truncated, line) - // } - // content = strings.Join(truncated, "\n") + content = toMarkdown(content, width, t.BackgroundSubtle()) return renderContentBlock(content, WithFullWidth(), WithPaddingTop(1), WithPaddingBottom(1)) } diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go index 093c8cf94..0036d0669 100644 --- a/packages/tui/internal/components/chat/messages.go +++ b/packages/tui/internal/components/chat/messages.go @@ -4,11 +4,11 @@ import ( "strings" "time" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/spinner" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/spinner" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/components/dialog" "github.com/sst/opencode/internal/layout" @@ -220,11 +220,11 @@ func (m *messagesComponent) renderView() { m.width, lipgloss.Center, block, - lipgloss.WithWhitespaceBackground(t.Background()), + lipgloss.WithWhitespaceStyle(lipgloss.NewStyle().Background(t.Background())), )) } - m.viewport.Height = m.height - lipgloss.Height(m.header()) + m.viewport.SetHeight(m.height - lipgloss.Height(m.header())) m.viewport.SetContent(strings.Join(centered, "\n")) } @@ -238,7 +238,7 @@ func (m *messagesComponent) header() string { base := styles.BaseStyle().Render muted := styles.Muted().Render headerLines := []string{} - headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width)) + headerLines = append(headerLines, toMarkdown("# "+m.app.Session.Title, width, t.Background())) if m.app.Session.Share != nil && m.app.Session.Share.Url != "" { headerLines = append(headerLines, muted(m.app.Session.Share.Url)) } else { @@ -255,7 +255,7 @@ func (m *messagesComponent) header() string { Background(t.Background()). Render(header) - return styles.ForceReplaceBackgroundWithLipgloss(header, t.Background()) + return header } func (m *messagesComponent) View() string { @@ -269,73 +269,8 @@ func (m *messagesComponent) View() string { ) } -// func hasToolsWithoutResponse(messages []message.Message) bool { -// toolCalls := make([]message.ToolCall, 0) -// toolResults := make([]message.ToolResult, 0) -// for _, m := range messages { -// toolCalls = append(toolCalls, m.ToolCalls()...) -// toolResults = append(toolResults, m.ToolResults()...) -// } -// -// for _, v := range toolCalls { -// found := false -// for _, r := range toolResults { -// if v.ID == r.ToolCallID { -// found = true -// break -// } -// } -// if !found && v.Finished { -// return true -// } -// } -// return false -// } - -// func hasUnfinishedToolCalls(messages []message.Message) bool { -// toolCalls := make([]message.ToolCall, 0) -// for _, m := range messages { -// toolCalls = append(toolCalls, m.ToolCalls()...) -// } -// for _, v := range toolCalls { -// if !v.Finished { -// return true -// } -// } -// return false -// } - -func (m *messagesComponent) help() string { - t := theme.CurrentTheme() - baseStyle := styles.BaseStyle() - - text := "" - - if m.app.IsBusy() { - text += lipgloss.JoinHorizontal( - lipgloss.Left, - baseStyle.Foreground(t.TextMuted()).Bold(true).Render("press "), - baseStyle.Foreground(t.Text()).Bold(true).Render("esc"), - baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" to interrupt"), - ) - } else { - text += lipgloss.JoinHorizontal( - lipgloss.Left, - baseStyle.Foreground(t.Text()).Bold(true).Render("enter"), - baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" to send,"), - baseStyle.Foreground(t.Text()).Bold(true).Render(" \\"), - baseStyle.Foreground(t.TextMuted()).Bold(true).Render("+"), - baseStyle.Foreground(t.Text()).Bold(true).Render("enter"), - baseStyle.Foreground(t.TextMuted()).Bold(true).Render(" for newline"), - ) - } - return baseStyle. - Width(m.width). - Render(text) -} - func (m *messagesComponent) home() string { - t := theme.CurrentTheme() + // t := theme.CurrentTheme() baseStyle := styles.BaseStyle() base := baseStyle.Render muted := styles.Muted().Render @@ -398,16 +333,13 @@ func (m *messagesComponent) home() string { lines = append(lines, "") } - return styles.ForceReplaceBackgroundWithLipgloss( - lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, - baseStyle.Width(lipgloss.Width(logoAndVersion)).Render( - lipgloss.JoinVertical( - lipgloss.Top, - lines..., - ), - )), - t.Background(), - ) + return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, + baseStyle.Width(lipgloss.Width(logoAndVersion)).Render( + lipgloss.JoinVertical( + lipgloss.Top, + lines..., + ), + )) } func (m *messagesComponent) SetSize(width, height int) tea.Cmd { @@ -420,10 +352,10 @@ func (m *messagesComponent) SetSize(width, height int) tea.Cmd { } m.width = width m.height = height - m.viewport.Width = width - m.viewport.Height = height - lipgloss.Height(m.header()) - m.attachments.Width = width + 40 - m.attachments.Height = 3 + m.viewport.SetWidth(width) + m.viewport.SetHeight(height - lipgloss.Height(m.header())) + m.attachments.SetWidth(width + 40) + m.attachments.SetHeight(3) m.renderView() return nil } @@ -449,15 +381,15 @@ func (m *messagesComponent) BindingKeys() []key.Binding { } } -func NewMessagesComponent(app *app.App) tea.Model { +func NewMessagesComponent(app *app.App) layout.ModelWithView { customSpinner := spinner.Spinner{ Frames: []string{" ", "┃", "┃"}, FPS: time.Second / 3, } s := spinner.New(spinner.WithSpinner(customSpinner)) - vp := viewport.New(0, 0) - attachments := viewport.New(0, 0) + vp := viewport.New() //(0, 0) + attachments := viewport.New() //(0, 0) vp.KeyMap.PageUp = messageKeys.PageUp vp.KeyMap.PageDown = messageKeys.PageDown vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp diff --git a/packages/tui/internal/components/core/status.go b/packages/tui/internal/components/core/status.go index 10a123f02..2b9929222 100644 --- a/packages/tui/internal/components/core/status.go +++ b/packages/tui/internal/components/core/status.go @@ -5,20 +5,21 @@ import ( "strings" "time" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/app" + "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/pubsub" "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" ) -type StatusCmp interface { - tea.Model +type StatusComponent interface { + layout.ModelWithView } -type statusCmp struct { +type statusComponent struct { app *app.App queue []status.StatusMessage width int @@ -27,7 +28,7 @@ type statusCmp struct { } // clearMessageCmd is a command that clears status messages after a timeout -func (m statusCmp) clearMessageCmd() tea.Cmd { +func (m statusComponent) clearMessageCmd() tea.Cmd { return tea.Tick(time.Second, func(t time.Time) tea.Msg { return statusCleanupMsg{time: t} }) @@ -38,11 +39,11 @@ type statusCleanupMsg struct { time time.Time } -func (m statusCmp) Init() tea.Cmd { +func (m statusComponent) Init() tea.Cmd { return m.clearMessageCmd() } -func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: m.width = msg.Width @@ -104,10 +105,9 @@ func logo() string { open := styles.Muted().Render("open") code := styles.BaseStyle().Bold(true).Render("code") version := styles.Muted().Render(app.Info.Version) - return styles.ForceReplaceBackgroundWithLipgloss( - styles.Padded().Render(mark+open+code+" "+version), - t.BackgroundElement(), - ) + return styles.Padded(). + Background(t.BackgroundElement()). + Render(mark + open + code + " " + version) } func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) string { @@ -137,7 +137,7 @@ func formatTokensAndCost(tokens float32, contextWindow float32, cost float32) st return fmt.Sprintf("Tokens: %s (%d%%), Cost: %s", formattedTokens, int(percentage), formattedCost) } -func (m statusCmp) View() string { +func (m statusComponent) View() string { if m.app.Session.Id == "" { return styles.BaseStyle(). Width(m.width). @@ -250,107 +250,8 @@ func (m statusCmp) View() string { // } } -func (m *statusCmp) projectDiagnostics() string { - t := theme.CurrentTheme() - - // Check if any LSP server is still initializing - initializing := false - // for _, client := range m.app.LSPClients { - // if client.GetServerState() == lsp.StateStarting { - // initializing = true - // break - // } - // } - - // If any server is initializing, show that status - if initializing { - return lipgloss.NewStyle(). - Foreground(t.Warning()). - Render(fmt.Sprintf("%s Initializing LSP...", styles.SpinnerIcon)) - } - - // errorDiagnostics := []protocol.Diagnostic{} - // warnDiagnostics := []protocol.Diagnostic{} - // hintDiagnostics := []protocol.Diagnostic{} - // infoDiagnostics := []protocol.Diagnostic{} - // for _, client := range m.app.LSPClients { - // for _, d := range client.GetDiagnostics() { - // for _, diag := range d { - // switch diag.Severity { - // case protocol.SeverityError: - // errorDiagnostics = append(errorDiagnostics, diag) - // case protocol.SeverityWarning: - // warnDiagnostics = append(warnDiagnostics, diag) - // case protocol.SeverityHint: - // hintDiagnostics = append(hintDiagnostics, diag) - // case protocol.SeverityInformation: - // infoDiagnostics = append(infoDiagnostics, diag) - // } - // } - // } - // } - return styles.ForceReplaceBackgroundWithLipgloss( - styles.Padded().Render("No diagnostics"), - t.BackgroundElement(), - ) - - // if len(errorDiagnostics) == 0 && - // len(warnDiagnostics) == 0 && - // len(infoDiagnostics) == 0 && - // len(hintDiagnostics) == 0 { - // return styles.ForceReplaceBackgroundWithLipgloss( - // styles.Padded().Render("No diagnostics"), - // t.BackgroundDarker(), - // ) - // } - - // diagnostics := []string{} - // - // errStr := lipgloss.NewStyle(). - // Background(t.BackgroundDarker()). - // Foreground(t.Error()). - // Render(fmt.Sprintf("%s %d", styles.ErrorIcon, len(errorDiagnostics))) - // diagnostics = append(diagnostics, errStr) - // - // warnStr := lipgloss.NewStyle(). - // Background(t.BackgroundDarker()). - // Foreground(t.Warning()). - // Render(fmt.Sprintf("%s %d", styles.WarningIcon, len(warnDiagnostics))) - // diagnostics = append(diagnostics, warnStr) - // - // infoStr := lipgloss.NewStyle(). - // Background(t.BackgroundDarker()). - // Foreground(t.Info()). - // Render(fmt.Sprintf("%s %d", styles.InfoIcon, len(infoDiagnostics))) - // diagnostics = append(diagnostics, infoStr) - // - // hintStr := lipgloss.NewStyle(). - // Background(t.BackgroundDarker()). - // Foreground(t.Text()). - // Render(fmt.Sprintf("%s %d", styles.HintIcon, len(hintDiagnostics))) - // diagnostics = append(diagnostics, hintStr) - // - // return styles.ForceReplaceBackgroundWithLipgloss( - // styles.Padded().Render(strings.Join(diagnostics, " ")), - // t.BackgroundDarker(), - // ) -} - -func (m statusCmp) model() string { - t := theme.CurrentTheme() - model := "None" - if m.app.Model != nil { - model = *m.app.Model.Name - } - - return styles.Padded(). - Background(t.Secondary()). - Foreground(t.Background()). - Render(model) -} - -func NewStatusCmp(app *app.App) StatusCmp { - statusComponent := &statusCmp{ +func NewStatusCmp(app *app.App) StatusComponent { + statusComponent := &statusComponent{ app: app, queue: []status.StatusMessage{}, messageTTL: 4 * time.Second, diff --git a/packages/tui/internal/components/dialog/arguments.go b/packages/tui/internal/components/dialog/arguments.go index 2faa36070..4ef21fcf4 100644 --- a/packages/tui/internal/components/dialog/arguments.go +++ b/packages/tui/internal/components/dialog/arguments.go @@ -2,10 +2,10 @@ package dialog import ( "fmt" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textinput" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -70,17 +70,24 @@ func NewMultiArgumentsDialogCmp(commandID, content string, argNames []string) Mu for i, name := range argNames { ti := textinput.New() ti.Placeholder = fmt.Sprintf("Enter value for %s...", name) - ti.Width = 40 + ti.SetWidth(40) ti.Prompt = "" - ti.PlaceholderStyle = ti.PlaceholderStyle.Background(t.Background()) - ti.PromptStyle = ti.PromptStyle.Background(t.Background()) - ti.TextStyle = ti.TextStyle.Background(t.Background()) + ti.Styles.Blurred.Placeholder = ti.Styles.Blurred.Placeholder.Background(t.Background()) + ti.Styles.Blurred.Text = ti.Styles.Blurred.Text.Background(t.Background()) + ti.Styles.Blurred.Prompt = ti.Styles.Blurred.Prompt.Foreground(t.Primary()) + + ti.Styles.Focused.Placeholder = ti.Styles.Focused.Placeholder.Background(t.Background()) + ti.Styles.Focused.Text = ti.Styles.Focused.Text.Background(t.Background()) + ti.Styles.Focused.Prompt = ti.Styles.Focused.Prompt.Foreground(t.Primary()) + + // ti.PromptStyle = ti.PromptStyle.Background(t.Background()) + // ti.TextStyle = ti.TextStyle.Background(t.Background()) // Only focus the first input initially if i == 0 { ti.Focus() - ti.PromptStyle = ti.PromptStyle.Foreground(t.Primary()) - ti.TextStyle = ti.TextStyle.Foreground(t.Primary()) + // ti.PromptStyle = ti.PromptStyle.Foreground(t.Primary()) + // ti.TextStyle = ti.TextStyle.Foreground(t.Primary()) } else { ti.Blur() } @@ -115,7 +122,7 @@ func (m MultiArgumentsDialogCmp) Init() tea.Cmd { // Update implements tea.Model. func (m MultiArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd - t := theme.CurrentTheme() + // t := theme.CurrentTheme() switch msg := msg.(type) { case tea.KeyMsg: @@ -145,22 +152,22 @@ func (m MultiArgumentsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.inputs[m.focusIndex].Blur() m.focusIndex++ m.inputs[m.focusIndex].Focus() - m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) - m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) case key.Matches(msg, key.NewBinding(key.WithKeys("tab"))): // Move to the next input m.inputs[m.focusIndex].Blur() m.focusIndex = (m.focusIndex + 1) % len(m.inputs) m.inputs[m.focusIndex].Focus() - m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) - m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) case key.Matches(msg, key.NewBinding(key.WithKeys("shift+tab"))): // Move to the previous input m.inputs[m.focusIndex].Blur() m.focusIndex = (m.focusIndex - 1 + len(m.inputs)) % len(m.inputs) m.inputs[m.focusIndex].Focus() - m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) - m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].PromptStyle = m.inputs[m.focusIndex].PromptStyle.Foreground(t.Primary()) + // m.inputs[m.focusIndex].TextStyle = m.inputs[m.focusIndex].TextStyle.Foreground(t.Primary()) } case tea.WindowSizeMsg: m.width = msg.Width diff --git a/packages/tui/internal/components/dialog/commands.go b/packages/tui/internal/components/dialog/commands.go index 1e99c09e0..39fe793d7 100644 --- a/packages/tui/internal/components/dialog/commands.go +++ b/packages/tui/internal/components/dialog/commands.go @@ -1,9 +1,9 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "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" @@ -56,12 +56,12 @@ type CloseCommandDialogMsg struct{} // CommandDialog interface for the command selection dialog type CommandDialog interface { - tea.Model + layout.ModelWithView layout.Bindings SetCommands(commands []Command) } -type commandDialogCmp struct { +type commandDialogComponent struct { listView utilComponents.SimpleList[Command] width int height int @@ -83,11 +83,11 @@ var commandKeys = commandKeyMap{ ), } -func (c *commandDialogCmp) Init() tea.Cmd { +func (c *commandDialogComponent) Init() tea.Cmd { return c.listView.Init() } -func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (c *commandDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { case tea.KeyMsg: @@ -114,7 +114,7 @@ func (c *commandDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return c, tea.Batch(cmds...) } -func (c *commandDialogCmp) View() string { +func (c *commandDialogComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -158,11 +158,11 @@ func (c *commandDialogCmp) View() string { Render(content) } -func (c *commandDialogCmp) BindingKeys() []key.Binding { +func (c *commandDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(commandKeys) } -func (c *commandDialogCmp) SetCommands(commands []Command) { +func (c *commandDialogComponent) SetCommands(commands []Command) { c.listView.SetItems(commands) } @@ -174,7 +174,7 @@ func NewCommandDialogCmp() CommandDialog { "No commands available", true, ) - return &commandDialogCmp{ + return &commandDialogComponent{ listView: listView, } } diff --git a/packages/tui/internal/components/dialog/complete.go b/packages/tui/internal/components/dialog/complete.go index ca209014e..91ac8f7b8 100644 --- a/packages/tui/internal/components/dialog/complete.go +++ b/packages/tui/internal/components/dialog/complete.go @@ -1,13 +1,13 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textarea" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" - "github.com/sst/opencode/internal/status" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textarea" + 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/status" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" "github.com/sst/opencode/internal/util" @@ -77,7 +77,7 @@ type CompletionDialogCompleteItemMsg struct { type CompletionDialogCloseMsg struct{} type CompletionDialog interface { - tea.Model + layout.ModelWithView layout.Bindings SetWidth(width int) } diff --git a/packages/tui/internal/components/dialog/custom_commands.go b/packages/tui/internal/components/dialog/custom_commands.go index c0115ad7a..5e0671b80 100644 --- a/packages/tui/internal/components/dialog/custom_commands.go +++ b/packages/tui/internal/components/dialog/custom_commands.go @@ -7,7 +7,7 @@ import ( "regexp" "strings" - tea "github.com/charmbracelet/bubbletea" + tea "github.com/charmbracelet/bubbletea/v2" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/util" ) diff --git a/packages/tui/internal/components/dialog/filepicker.go b/packages/tui/internal/components/dialog/filepicker.go index 3fad90e3f..408b9cc00 100644 --- a/packages/tui/internal/components/dialog/filepicker.go +++ b/packages/tui/internal/components/dialog/filepicker.go @@ -12,13 +12,14 @@ import ( "log/slog" "github.com/atotto/clipboard" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/textinput" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/textinput" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/image" + "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -82,7 +83,7 @@ var filePickerKeyMap = FilePrickerKeyMap{ ), } -type filepickerCmp struct { +type filepickerComponent struct { basePath string width int height int @@ -118,18 +119,18 @@ type AttachmentAddedMsg struct { Attachment app.Attachment } -func (f *filepickerCmp) Init() tea.Cmd { +func (f *filepickerComponent) Init() tea.Cmd { return nil } -func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (f *filepickerComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmd tea.Cmd switch msg := msg.(type) { case tea.WindowSizeMsg: f.width = 60 f.height = 20 - f.viewport.Width = 80 - f.viewport.Height = 22 + f.viewport.SetWidth(80) + f.viewport.SetHeight(22) f.cursor = 0 f.getCurrentFileBelowCursor() case tea.KeyMsg: @@ -236,7 +237,7 @@ func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return f, cmd } -func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) { +func (f *filepickerComponent) addAttachmentToMessage() (tea.Model, tea.Cmd) { // modeInfo := GetSelectedModel(config.Get()) // if !modeInfo.SupportsAttachments { // status.Error(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name)) @@ -273,7 +274,7 @@ func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) { return f, util.CmdHandler(AttachmentAddedMsg{attachment}) } -func (f *filepickerCmp) View() string { +func (f *filepickerComponent) View() string { t := theme.CurrentTheme() const maxVisibleDirs = 20 const maxWidth = 80 @@ -333,7 +334,7 @@ func (f *filepickerCmp) View() string { Render(f.cwd.View()) viewportstyle := lipgloss.NewStyle(). - Width(f.viewport.Width). + Width(f.viewport.Width()). Background(t.Background()). Border(lipgloss.RoundedBorder()). BorderForeground(t.TextMuted()). @@ -366,21 +367,21 @@ func (f *filepickerCmp) View() string { return lipgloss.JoinHorizontal(lipgloss.Center, contentStyle.Render(content), viewportstyle) } -type FilepickerCmp interface { - tea.Model +type FilepickerComponent interface { + layout.ModelWithView ToggleFilepicker(showFilepicker bool) IsCWDFocused() bool } -func (f *filepickerCmp) ToggleFilepicker(showFilepicker bool) { +func (f *filepickerComponent) ToggleFilepicker(showFilepicker bool) { f.ShowFilePicker = showFilepicker } -func (f *filepickerCmp) IsCWDFocused() bool { +func (f *filepickerComponent) IsCWDFocused() bool { return f.cwd.Focused() } -func NewFilepickerCmp(app *app.App) FilepickerCmp { +func NewFilepickerCmp(app *app.App) FilepickerComponent { homepath, err := os.UserHomeDir() if err != nil { slog.Error("error loading user files") @@ -388,16 +389,16 @@ func NewFilepickerCmp(app *app.App) FilepickerCmp { } baseDir := DirNode{parent: nil, directory: homepath} dirs := readDir(homepath, false) - viewport := viewport.New(0, 0) + viewport := viewport.New() // viewport.New(0, 0) currentDirectory := textinput.New() currentDirectory.CharLimit = 200 - currentDirectory.Width = 44 - currentDirectory.Cursor.Blink = true + currentDirectory.SetWidth(44) + // currentDirectory.Cursor.Blink = true currentDirectory.SetValue(baseDir.directory) - return &filepickerCmp{cwdDetails: &baseDir, dirs: dirs, cursorChain: make(stack, 0), viewport: viewport, cwd: currentDirectory, app: app} + return &filepickerComponent{cwdDetails: &baseDir, dirs: dirs, cursorChain: make(stack, 0), viewport: viewport, cwd: currentDirectory, app: app} } -func (f *filepickerCmp) getCurrentFileBelowCursor() { +func (f *filepickerComponent) getCurrentFileBelowCursor() { if len(f.dirs) == 0 || f.cursor < 0 || f.cursor >= len(f.dirs) { slog.Error(fmt.Sprintf("Invalid cursor position. Dirs length: %d, Cursor: %d", len(f.dirs), f.cursor)) f.viewport.SetContent("Preview unavailable") @@ -410,7 +411,7 @@ func (f *filepickerCmp) getCurrentFileBelowCursor() { fullPath := f.cwdDetails.directory + "/" + dir.Name() go func() { - imageString, err := image.ImagePreview(f.viewport.Width-4, fullPath) + imageString, err := image.ImagePreview(f.viewport.Width()-4, fullPath) if err != nil { slog.Error(err.Error()) f.viewport.SetContent("Preview unavailable") diff --git a/packages/tui/internal/components/dialog/help.go b/packages/tui/internal/components/dialog/help.go index a55e39e72..6ceeb40b2 100644 --- a/packages/tui/internal/components/dialog/help.go +++ b/packages/tui/internal/components/dialog/help.go @@ -3,28 +3,29 @@ package dialog import ( "strings" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" + "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" ) -type helpCmp struct { +type helpComponent struct { width int height int keys []key.Binding } -func (h *helpCmp) Init() tea.Cmd { +func (h *helpComponent) Init() tea.Cmd { return nil } -func (h *helpCmp) SetBindings(k []key.Binding) { +func (h *helpComponent) SetBindings(k []key.Binding) { h.keys = k } -func (h *helpCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (h *helpComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: h.width = 90 @@ -53,7 +54,7 @@ func removeDuplicateBindings(bindings []key.Binding) []key.Binding { return result } -func (h *helpCmp) render() string { +func (h *helpComponent) render() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -134,7 +135,7 @@ func (h *helpCmp) render() string { pairs = append(pairs, pair) } - // https://github.com/charmbracelet/lipgloss/issues/209 + // https://github.com/charmbracelet/lipgloss/v2/issues/209 if len(pairs) > 1 { prefix := pairs[:len(pairs)-1] lastPair := pairs[len(pairs)-1] @@ -144,7 +145,7 @@ func (h *helpCmp) render() string { lipgloss.Left, // x lipgloss.Top, // y lastPair, // content - lipgloss.WithWhitespaceBackground(t.Background()), + // lipgloss.WithWhitespaceBackground(t.Background()), )) content := baseStyle.Width(h.width).Render( lipgloss.JoinHorizontal( @@ -165,7 +166,7 @@ func (h *helpCmp) render() string { return content } -func (h *helpCmp) View() string { +func (h *helpComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -190,11 +191,11 @@ func (h *helpCmp) View() string { ) } -type HelpCmp interface { - tea.Model +type HelpComponent interface { + layout.ModelWithView SetBindings([]key.Binding) } -func NewHelpCmp() HelpCmp { - return &helpCmp{} +func NewHelpCmp() HelpComponent { + return &helpComponent{} } diff --git a/packages/tui/internal/components/dialog/init.go b/packages/tui/internal/components/dialog/init.go index 01bdbe577..d939427c1 100644 --- a/packages/tui/internal/components/dialog/init.go +++ b/packages/tui/internal/components/dialog/init.go @@ -1,9 +1,9 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" diff --git a/packages/tui/internal/components/dialog/models.go b/packages/tui/internal/components/dialog/models.go index 5d0ee772b..50d1791cd 100644 --- a/packages/tui/internal/components/dialog/models.go +++ b/packages/tui/internal/components/dialog/models.go @@ -7,9 +7,9 @@ import ( "slices" "strings" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/app" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" @@ -31,13 +31,13 @@ type CloseModelDialogMsg struct { // ModelDialog interface for the model selection dialog type ModelDialog interface { - tea.Model + layout.ModelWithView layout.Bindings SetProviders(providers []client.ProviderInfo) } -type modelDialogCmp struct { +type modelDialogComponent struct { app *app.App availableProviders []client.ProviderInfo provider client.ProviderInfo @@ -106,7 +106,7 @@ var modelKeys = modelKeyMap{ ), } -func (m *modelDialogCmp) Init() tea.Cmd { +func (m *modelDialogComponent) Init() tea.Cmd { // cfg := config.Get() // modelInfo := GetSelectedModel(cfg) // m.availableProviders = getEnabledProviders(cfg) @@ -125,11 +125,11 @@ func (m *modelDialogCmp) Init() tea.Cmd { return nil } -func (m *modelDialogCmp) SetProviders(providers []client.ProviderInfo) { +func (m *modelDialogComponent) SetProviders(providers []client.ProviderInfo) { m.availableProviders = providers } -func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *modelDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -159,7 +159,7 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, nil } -func (m *modelDialogCmp) models() []client.ProviderModel { +func (m *modelDialogComponent) models() []client.ProviderModel { models := slices.SortedFunc(maps.Values(m.provider.Models), func(a, b client.ProviderModel) int { return strings.Compare(*a.Name, *b.Name) }) @@ -167,7 +167,7 @@ func (m *modelDialogCmp) models() []client.ProviderModel { } // moveSelectionUp moves the selection up or wraps to bottom -func (m *modelDialogCmp) moveSelectionUp() { +func (m *modelDialogComponent) moveSelectionUp() { if m.selectedIdx > 0 { m.selectedIdx-- } else { @@ -182,7 +182,7 @@ func (m *modelDialogCmp) moveSelectionUp() { } // moveSelectionDown moves the selection down or wraps to top -func (m *modelDialogCmp) moveSelectionDown() { +func (m *modelDialogComponent) moveSelectionDown() { if m.selectedIdx < len(m.provider.Models)-1 { m.selectedIdx++ } else { @@ -196,7 +196,7 @@ func (m *modelDialogCmp) moveSelectionDown() { } } -func (m *modelDialogCmp) switchProvider(offset int) { +func (m *modelDialogComponent) switchProvider(offset int) { newOffset := m.hScrollOffset + offset // Ensure we stay within bounds @@ -212,7 +212,7 @@ func (m *modelDialogCmp) switchProvider(offset int) { m.setupModelsForProvider(m.provider.Id) } -func (m *modelDialogCmp) View() string { +func (m *modelDialogComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -255,7 +255,7 @@ func (m *modelDialogCmp) View() string { Render(content) } -func (m *modelDialogCmp) getScrollIndicators(maxWidth int) string { +func (m *modelDialogComponent) getScrollIndicators(maxWidth int) string { var indicator string if len(m.provider.Models) > numVisibleModels { @@ -291,7 +291,7 @@ func (m *modelDialogCmp) getScrollIndicators(maxWidth int) string { Render(indicator) } -func (m *modelDialogCmp) BindingKeys() []key.Binding { +func (m *modelDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(modelKeys) } @@ -305,7 +305,7 @@ func (m *modelDialogCmp) BindingKeys() []key.Binding { // return -1 // } -func (m *modelDialogCmp) setupModelsForProvider(_ string) { +func (m *modelDialogComponent) setupModelsForProvider(_ string) { m.selectedIdx = 0 m.scrollOffset = 0 @@ -332,7 +332,7 @@ func (m *modelDialogCmp) setupModelsForProvider(_ string) { } func NewModelDialogCmp(app *app.App) ModelDialog { - return &modelDialogCmp{ + return &modelDialogComponent{ app: app, } } diff --git a/packages/tui/internal/components/dialog/permission.go b/packages/tui/internal/components/dialog/permission.go index bb71fc35d..526e03483 100644 --- a/packages/tui/internal/components/dialog/permission.go +++ b/packages/tui/internal/components/dialog/permission.go @@ -2,10 +2,10 @@ package dialog import ( "fmt" - "github.com/charmbracelet/bubbles/key" - "github.com/charmbracelet/bubbles/viewport" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + "github.com/charmbracelet/bubbles/v2/viewport" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -28,9 +28,9 @@ type PermissionResponseMsg struct { Action PermissionAction } -// PermissionDialogCmp interface for permission dialog component -type PermissionDialogCmp interface { - tea.Model +// PermissionDialogComponent interface for permission dialog component +type PermissionDialogComponent interface { + layout.ModelWithView layout.Bindings // SetPermissions(permission permission.PermissionRequest) tea.Cmd } @@ -76,8 +76,8 @@ var permissionsKeys = permissionsMapping{ ), } -// permissionDialogCmp is the implementation of PermissionDialog -type permissionDialogCmp struct { +// permissionDialogComponent is the implementation of PermissionDialog +type permissionDialogComponent struct { width int height int // permission permission.PermissionRequest @@ -89,11 +89,11 @@ type permissionDialogCmp struct { markdownCache map[string]string } -func (p *permissionDialogCmp) Init() tea.Cmd { +func (p *permissionDialogComponent) Init() tea.Cmd { return p.contentViewPort.Init() } -func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (p *permissionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cmds []tea.Cmd switch msg := msg.(type) { @@ -129,7 +129,7 @@ func (p *permissionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return p, tea.Batch(cmds...) } -func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd { +func (p *permissionDialogComponent) selectCurrentOption() tea.Cmd { var action PermissionAction switch p.selectedOption { @@ -144,7 +144,7 @@ func (p *permissionDialogCmp) selectCurrentOption() tea.Cmd { return util.CmdHandler(PermissionResponseMsg{Action: action}) // , Permission: p.permission}) } -func (p *permissionDialogCmp) renderButtons() string { +func (p *permissionDialogComponent) renderButtons() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -190,7 +190,7 @@ func (p *permissionDialogCmp) renderButtons() string { return content } -func (p *permissionDialogCmp) renderHeader() string { +func (p *permissionDialogComponent) renderHeader() string { return "NOT IMPLEMENTED" // t := theme.CurrentTheme() // baseStyle := styles.BaseStyle() @@ -246,7 +246,7 @@ func (p *permissionDialogCmp) renderHeader() string { // return lipgloss.NewStyle().Background(t.Background()).Render(lipgloss.JoinVertical(lipgloss.Left, headerParts...)) } -func (p *permissionDialogCmp) renderBashContent() string { +func (p *permissionDialogComponent) renderBashContent() string { // t := theme.CurrentTheme() // baseStyle := styles.BaseStyle() // @@ -269,7 +269,7 @@ func (p *permissionDialogCmp) renderBashContent() string { return "" } -func (p *permissionDialogCmp) renderEditContent() string { +func (p *permissionDialogComponent) renderEditContent() string { // if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok { // diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) { // return diff.FormatDiff(pr.Diff, diff.WithTotalWidth(p.contentViewPort.Width)) @@ -281,7 +281,7 @@ func (p *permissionDialogCmp) renderEditContent() string { return "" } -func (p *permissionDialogCmp) renderPatchContent() string { +func (p *permissionDialogComponent) renderPatchContent() string { // if pr, ok := p.permission.Params.(tools.EditPermissionsParams); ok { // diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) { // return diff.FormatDiff(pr.Diff, diff.WithTotalWidth(p.contentViewPort.Width)) @@ -293,7 +293,7 @@ func (p *permissionDialogCmp) renderPatchContent() string { return "" } -func (p *permissionDialogCmp) renderWriteContent() string { +func (p *permissionDialogComponent) renderWriteContent() string { // if pr, ok := p.permission.Params.(tools.WritePermissionsParams); ok { // // Use the cache for diff rendering // diff := p.GetOrSetDiff(p.permission.ID, func() (string, error) { @@ -306,7 +306,7 @@ func (p *permissionDialogCmp) renderWriteContent() string { return "" } -func (p *permissionDialogCmp) renderFetchContent() string { +func (p *permissionDialogComponent) renderFetchContent() string { // t := theme.CurrentTheme() // baseStyle := styles.BaseStyle() // @@ -329,7 +329,7 @@ func (p *permissionDialogCmp) renderFetchContent() string { return "" } -func (p *permissionDialogCmp) renderDefaultContent() string { +func (p *permissionDialogComponent) renderDefaultContent() string { // t := theme.CurrentTheme() // baseStyle := styles.BaseStyle() // @@ -354,7 +354,7 @@ func (p *permissionDialogCmp) renderDefaultContent() string { return p.styleViewport() } -func (p *permissionDialogCmp) styleViewport() string { +func (p *permissionDialogComponent) styleViewport() string { t := theme.CurrentTheme() contentStyle := lipgloss.NewStyle(). Background(t.Background()) @@ -362,7 +362,7 @@ func (p *permissionDialogCmp) styleViewport() string { return contentStyle.Render(p.contentViewPort.View()) } -func (p *permissionDialogCmp) render() string { +func (p *permissionDialogComponent) render() string { return "NOT IMPLEMENTED" // t := theme.CurrentTheme() // baseStyle := styles.BaseStyle() @@ -420,15 +420,15 @@ func (p *permissionDialogCmp) render() string { // ) } -func (p *permissionDialogCmp) View() string { +func (p *permissionDialogComponent) View() string { return p.render() } -func (p *permissionDialogCmp) BindingKeys() []key.Binding { +func (p *permissionDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(permissionsKeys) } -func (p *permissionDialogCmp) SetSize() tea.Cmd { +func (p *permissionDialogComponent) SetSize() tea.Cmd { // if p.permission.ID == "" { // return nil // } @@ -458,7 +458,7 @@ func (p *permissionDialogCmp) SetSize() tea.Cmd { // } // Helper to get or set cached diff content -func (c *permissionDialogCmp) GetOrSetDiff(key string, generator func() (string, error)) string { +func (c *permissionDialogComponent) GetOrSetDiff(key string, generator func() (string, error)) string { if cached, ok := c.diffCache[key]; ok { return cached } @@ -474,7 +474,7 @@ func (c *permissionDialogCmp) GetOrSetDiff(key string, generator func() (string, } // Helper to get or set cached markdown content -func (c *permissionDialogCmp) GetOrSetMarkdown(key string, generator func() (string, error)) string { +func (c *permissionDialogComponent) GetOrSetMarkdown(key string, generator func() (string, error)) string { if cached, ok := c.markdownCache[key]; ok { return cached } @@ -489,11 +489,11 @@ func (c *permissionDialogCmp) GetOrSetMarkdown(key string, generator func() (str return content } -func NewPermissionDialogCmp() PermissionDialogCmp { +func NewPermissionDialogCmp() PermissionDialogComponent { // Create viewport for content - contentViewport := viewport.New(0, 0) + contentViewport := viewport.New() // (0, 0) - return &permissionDialogCmp{ + return &permissionDialogComponent{ contentViewPort: contentViewport, selectedOption: 0, // Default to "Allow" diffCache: make(map[string]string), diff --git a/packages/tui/internal/components/dialog/quit.go b/packages/tui/internal/components/dialog/quit.go index 78034c714..8362e7672 100644 --- a/packages/tui/internal/components/dialog/quit.go +++ b/packages/tui/internal/components/dialog/quit.go @@ -3,9 +3,9 @@ package dialog import ( "strings" - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -17,11 +17,11 @@ const question = "Are you sure you want to quit?" type CloseQuitMsg struct{} type QuitDialog interface { - tea.Model + layout.ModelWithView layout.Bindings } -type quitDialogCmp struct { +type quitDialogComponent struct { selectedNo bool } @@ -56,11 +56,11 @@ var helpKeys = helpMapping{ ), } -func (q *quitDialogCmp) Init() tea.Cmd { +func (q *quitDialogComponent) Init() tea.Cmd { return nil } -func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (q *quitDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -81,7 +81,7 @@ func (q *quitDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return q, nil } -func (q *quitDialogCmp) View() string { +func (q *quitDialogComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -125,12 +125,12 @@ func (q *quitDialogCmp) View() string { Render(content) } -func (q *quitDialogCmp) BindingKeys() []key.Binding { +func (q *quitDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(helpKeys) } func NewQuitCmp() QuitDialog { - return &quitDialogCmp{ + return &quitDialogComponent{ selectedNo: true, } } diff --git a/packages/tui/internal/components/dialog/session.go b/packages/tui/internal/components/dialog/session.go index 2d234529e..8ef9a701f 100644 --- a/packages/tui/internal/components/dialog/session.go +++ b/packages/tui/internal/components/dialog/session.go @@ -1,9 +1,9 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -18,13 +18,13 @@ type CloseSessionDialogMsg struct { // SessionDialog interface for the session switching dialog type SessionDialog interface { - tea.Model + layout.ModelWithView layout.Bindings SetSessions(sessions []client.SessionInfo) SetSelectedSession(sessionID string) } -type sessionDialogCmp struct { +type sessionDialogComponent struct { sessions []client.SessionInfo selectedIdx int width int @@ -68,11 +68,11 @@ var sessionKeys = sessionKeyMap{ ), } -func (s *sessionDialogCmp) Init() tea.Cmd { +func (s *sessionDialogComponent) Init() tea.Cmd { return nil } -func (s *sessionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (s *sessionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.WindowSizeMsg: s.width = msg.Width @@ -105,7 +105,7 @@ func (s *sessionDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return s, nil } -func (s *sessionDialogCmp) View() string { +func (s *sessionDialogComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -185,11 +185,11 @@ func (s *sessionDialogCmp) View() string { Render(content) } -func (s *sessionDialogCmp) BindingKeys() []key.Binding { +func (s *sessionDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(sessionKeys) } -func (s *sessionDialogCmp) SetSessions(sessions []client.SessionInfo) { +func (s *sessionDialogComponent) SetSessions(sessions []client.SessionInfo) { s.sessions = sessions // If we have a selected session ID, find its index @@ -206,7 +206,7 @@ func (s *sessionDialogCmp) SetSessions(sessions []client.SessionInfo) { s.selectedIdx = 0 } -func (s *sessionDialogCmp) SetSelectedSession(sessionID string) { +func (s *sessionDialogComponent) SetSelectedSession(sessionID string) { s.selectedSessionID = sessionID // Update the selected index if sessions are already loaded @@ -222,7 +222,7 @@ func (s *sessionDialogCmp) SetSelectedSession(sessionID string) { // NewSessionDialogCmp creates a new session switching dialog func NewSessionDialogCmp() SessionDialog { - return &sessionDialogCmp{ + return &sessionDialogComponent{ sessions: []client.SessionInfo{}, selectedIdx: 0, selectedSessionID: "", diff --git a/packages/tui/internal/components/dialog/theme.go b/packages/tui/internal/components/dialog/theme.go index 42c777c41..4704cb42e 100644 --- a/packages/tui/internal/components/dialog/theme.go +++ b/packages/tui/internal/components/dialog/theme.go @@ -1,9 +1,9 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/status" "github.com/sst/opencode/internal/styles" @@ -21,11 +21,11 @@ type CloseThemeDialogMsg struct{} // ThemeDialog interface for the theme switching dialog type ThemeDialog interface { - tea.Model + layout.ModelWithView layout.Bindings } -type themeDialogCmp struct { +type themeDialogComponent struct { themes []string selectedIdx int width int @@ -69,7 +69,7 @@ var themeKeys = themeKeyMap{ ), } -func (t *themeDialogCmp) Init() tea.Cmd { +func (t *themeDialogComponent) Init() tea.Cmd { // Load available themes and update selectedIdx based on current theme t.themes = theme.AvailableThemes() t.currentTheme = theme.CurrentThemeName() @@ -85,7 +85,7 @@ func (t *themeDialogCmp) Init() tea.Cmd { return nil } -func (t *themeDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (t *themeDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -124,7 +124,7 @@ func (t *themeDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return t, nil } -func (t *themeDialogCmp) View() string { +func (t *themeDialogComponent) View() string { currentTheme := theme.CurrentTheme() baseStyle := styles.BaseStyle() @@ -185,13 +185,13 @@ func (t *themeDialogCmp) View() string { Render(content) } -func (t *themeDialogCmp) BindingKeys() []key.Binding { +func (t *themeDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(themeKeys) } // NewThemeDialogCmp creates a new theme switching dialog func NewThemeDialogCmp() ThemeDialog { - return &themeDialogCmp{ + return &themeDialogComponent{ themes: []string{}, selectedIdx: 0, currentTheme: "", diff --git a/packages/tui/internal/components/dialog/tools.go b/packages/tui/internal/components/dialog/tools.go index a01d1911b..c91dfef9a 100644 --- a/packages/tui/internal/components/dialog/tools.go +++ b/packages/tui/internal/components/dialog/tools.go @@ -1,9 +1,9 @@ package dialog import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "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" @@ -17,7 +17,7 @@ const ( // ToolsDialog interface for the tools list dialog type ToolsDialog interface { - tea.Model + layout.ModelWithView layout.Bindings SetTools(tools []string) } @@ -39,7 +39,7 @@ func (t toolItem) Render(selected bool, width int) string { baseStyle := styles.BaseStyle(). Width(width). Background(th.Background()) - + if selected { baseStyle = baseStyle. Background(th.Primary()). @@ -49,15 +49,15 @@ func (t toolItem) Render(selected bool, width int) string { baseStyle = baseStyle. Foreground(th.Text()) } - + return baseStyle.Render(t.name) } -type toolsDialogCmp struct { - tools []toolItem - width int - height int - list utilComponents.SimpleList[toolItem] +type toolsDialogComponent struct { + tools []toolItem + width int + height int + list utilComponents.SimpleList[toolItem] } type toolsKeyMap struct { @@ -91,21 +91,21 @@ var toolsKeys = toolsKeyMap{ ), } -func (m *toolsDialogCmp) Init() tea.Cmd { +func (m *toolsDialogComponent) Init() tea.Cmd { return nil } -func (m *toolsDialogCmp) SetTools(tools []string) { +func (m *toolsDialogComponent) SetTools(tools []string) { var toolItems []toolItem for _, name := range tools { toolItems = append(toolItems, toolItem{name: name}) } - + m.tools = toolItems m.list.SetItems(toolItems) } -func (m *toolsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *toolsDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { case tea.KeyMsg: switch { @@ -130,7 +130,7 @@ func (m *toolsDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, cmd } -func (m *toolsDialogCmp) View() string { +func (m *toolsDialogComponent) View() string { t := theme.CurrentTheme() baseStyle := styles.BaseStyle().Background(t.Background()) @@ -144,7 +144,7 @@ func (m *toolsDialogCmp) View() string { // Calculate dialog width based on content dialogWidth := min(maxToolsDialogWidth, m.width/2) m.list.SetMaxWidth(dialogWidth) - + content := lipgloss.JoinVertical( lipgloss.Left, title, @@ -160,7 +160,7 @@ func (m *toolsDialogCmp) View() string { Render(content) } -func (m *toolsDialogCmp) BindingKeys() []key.Binding { +func (m *toolsDialogComponent) BindingKeys() []key.Binding { return layout.KeyMapToSlice(toolsKeys) } @@ -171,8 +171,8 @@ func NewToolsDialogCmp() ToolsDialog { "No tools available", true, ) - - return &toolsDialogCmp{ + + return &toolsDialogComponent{ list: list, } } diff --git a/packages/tui/internal/components/diff/diff.go b/packages/tui/internal/components/diff/diff.go index 65874ebb4..4551cf736 100644 --- a/packages/tui/internal/components/diff/diff.go +++ b/packages/tui/internal/components/diff/diff.go @@ -3,6 +3,7 @@ package diff import ( "bytes" "fmt" + "image/color" "io" "regexp" "strconv" @@ -12,9 +13,11 @@ import ( "github.com/alecthomas/chroma/v2/formatters" "github.com/alecthomas/chroma/v2/lexers" "github.com/alecthomas/chroma/v2/styles" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" + "github.com/charmbracelet/lipgloss/v2/compat" "github.com/charmbracelet/x/ansi" "github.com/sergi/go-diff/diffmatchpatch" + stylesi "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" ) @@ -300,7 +303,7 @@ func pairLines(lines []DiffLine) []linePair { // ------------------------------------------------------------------------- // SyntaxHighlight applies syntax highlighting to text based on file extension -func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg lipgloss.TerminalColor) error { +func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg color.Color) error { t := theme.CurrentTheme() // Determine the language lexer to use @@ -509,15 +512,12 @@ func SyntaxHighlight(w io.Writer, source, fileName, formatter string, bg lipglos } // getColor returns the appropriate hex color string based on terminal background -func getColor(adaptiveColor lipgloss.AdaptiveColor) string { - if lipgloss.HasDarkBackground() { - return adaptiveColor.Dark - } - return adaptiveColor.Light +func getColor(adaptiveColor compat.AdaptiveColor) string { + return stylesi.AdaptiveColorToString(adaptiveColor) } // highlightLine applies syntax highlighting to a single line -func highlightLine(fileName string, line string, bg lipgloss.TerminalColor) string { +func highlightLine(fileName string, line string, bg color.Color) string { var buf bytes.Buffer err := SyntaxHighlight(&buf, line, fileName, "terminal16m", bg) if err != nil { @@ -540,7 +540,7 @@ func createStyles(t theme.Theme) (removedLineStyle, addedLineStyle, contextLineS // ------------------------------------------------------------------------- // applyHighlighting applies intra-line highlighting to a piece of text -func applyHighlighting(content string, segments []Segment, segmentType LineType, highlightBg lipgloss.AdaptiveColor) string { +func applyHighlighting(content string, segments []Segment, segmentType LineType, highlightBg compat.AdaptiveColor) string { // Find all ANSI sequences in the content ansiRegex := regexp.MustCompile(`\x1b(?:[@-Z\\-_]|\[[0-9?]*(?:;[0-9?]*)*[@-~])`) ansiMatches := ansiRegex.FindAllStringIndex(content, -1) @@ -662,7 +662,7 @@ func renderDiffColumnLine( var bgStyle lipgloss.Style var lineNum string var highlightType LineType - var highlightColor lipgloss.AdaptiveColor + var highlightColor compat.AdaptiveColor if isLeftColumn { // Left column logic diff --git a/packages/tui/internal/components/qr/qr.go b/packages/tui/internal/components/qr/qr.go index df51a644d..82d597a3f 100644 --- a/packages/tui/internal/components/qr/qr.go +++ b/packages/tui/internal/components/qr/qr.go @@ -3,7 +3,7 @@ package qr import ( "strings" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/theme" "rsc.io/qr" ) diff --git a/packages/tui/internal/components/util/simple-list.go b/packages/tui/internal/components/util/simple-list.go index 0c0bb6556..d6e27db14 100644 --- a/packages/tui/internal/components/util/simple-list.go +++ b/packages/tui/internal/components/util/simple-list.go @@ -1,9 +1,9 @@ package utilComponents import ( - "github.com/charmbracelet/bubbles/key" - tea "github.com/charmbracelet/bubbletea" - "github.com/charmbracelet/lipgloss" + "github.com/charmbracelet/bubbles/v2/key" + tea "github.com/charmbracelet/bubbletea/v2" + "github.com/charmbracelet/lipgloss/v2" "github.com/sst/opencode/internal/layout" "github.com/sst/opencode/internal/styles" "github.com/sst/opencode/internal/theme" @@ -14,7 +14,7 @@ type SimpleListItem interface { } type SimpleList[T SimpleListItem] interface { - tea.Model + layout.ModelWithView layout.Bindings SetMaxWidth(maxWidth int) GetSelectedItem() (item T, idx int) |
