diff options
Diffstat (limited to 'packages/tui/internal/components/chat')
| -rw-r--r-- | packages/tui/internal/components/chat/editor.go | 61 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/message.go | 49 | ||||
| -rw-r--r-- | packages/tui/internal/components/chat/messages.go | 116 |
3 files changed, 69 insertions, 157 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 |
