summaryrefslogtreecommitdiffhomepage
path: root/packages/tui/internal/components/chat
diff options
context:
space:
mode:
Diffstat (limited to 'packages/tui/internal/components/chat')
-rw-r--r--packages/tui/internal/components/chat/editor.go61
-rw-r--r--packages/tui/internal/components/chat/message.go49
-rw-r--r--packages/tui/internal/components/chat/messages.go116
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