summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdotdevin <[email protected]>2025-07-15 13:56:14 -0500
committeradamdotdevin <[email protected]>2025-07-16 06:09:26 -0500
commitd41aa2bc72c895fb63c0bd68e1535370e3c52127 (patch)
tree417ff46519870769e9ec5a238f1910e98dba630e
parentf45deb37f06d6c8989faa0275b89a25695a6a216 (diff)
downloadopencode-d41aa2bc72c895fb63c0bd68e1535370e3c52127.tar.gz
opencode-d41aa2bc72c895fb63c0bd68e1535370e3c52127.zip
chore(tui): simplify messages component, remove navigate, add copy last message
-rw-r--r--packages/tui/internal/components/chat/message.go60
-rw-r--r--packages/tui/internal/components/chat/messages.go121
-rw-r--r--packages/tui/internal/tui/tui.go22
3 files changed, 47 insertions, 156 deletions
diff --git a/packages/tui/internal/components/chat/message.go b/packages/tui/internal/components/chat/message.go
index 8b7a33416..ab4e7d014 100644
--- a/packages/tui/internal/components/chat/message.go
+++ b/packages/tui/internal/components/chat/message.go
@@ -11,9 +11,7 @@ import (
"github.com/charmbracelet/lipgloss/v2/compat"
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode/internal/app"
- "github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/components/diff"
- "github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
@@ -109,7 +107,6 @@ func WithPaddingBottom(padding int) renderingOption {
func renderContentBlock(
app *app.App,
content string,
- highlight bool,
width int,
options ...renderingOption,
) string {
@@ -158,18 +155,6 @@ func renderContentBlock(
BorderRightBackground(t.Background())
}
- if highlight {
- style = style.
- BorderLeftForeground(borderColor).
- BorderRightForeground(borderColor)
- }
- }
-
- if highlight {
- style = style.
- Foreground(t.Text()).
- Background(t.BackgroundElement()).
- Bold(true)
}
content = style.Render(content)
@@ -184,32 +169,6 @@ func renderContentBlock(
}
}
- if highlight {
- copy := app.Key(commands.MessagesCopyCommand)
- // revert := app.Key(commands.MessagesRevertCommand)
-
- background := t.Background()
- header := layout.Render(
- layout.FlexOptions{
- Background: &background,
- Direction: layout.Row,
- Justify: layout.JustifyCenter,
- Align: layout.AlignStretch,
- Width: width - 2,
- Gap: 5,
- },
- layout.FlexItem{
- View: copy,
- },
- // layout.FlexItem{
- // View: revert,
- // },
- )
- header = styles.NewStyle().Background(t.Background()).Padding(0, 1).Render(header)
-
- content = "\n\n\n" + header + "\n\n" + content + "\n\n\n"
- }
-
return content
}
@@ -219,7 +178,6 @@ func renderText(
text string,
author string,
showToolDetails bool,
- highlight bool,
width int,
extra string,
toolCalls ...opencode.ToolPart,
@@ -228,9 +186,6 @@ func renderText(
var ts time.Time
backgroundColor := t.BackgroundPanel()
- if highlight {
- backgroundColor = t.BackgroundElement()
- }
var content string
switch casted := message.(type) {
case opencode.AssistantMessage:
@@ -277,7 +232,6 @@ func renderText(
return renderContentBlock(
app,
content,
- highlight,
width,
WithTextColor(t.Text()),
WithBorderColorRight(t.Secondary()),
@@ -286,7 +240,6 @@ func renderText(
return renderContentBlock(
app,
content,
- highlight,
width,
WithBorderColor(t.Accent()),
)
@@ -297,7 +250,6 @@ func renderText(
func renderToolDetails(
app *app.App,
toolCall opencode.ToolPart,
- highlight bool,
width int,
) string {
ignoredTools := []string{"todoread"}
@@ -307,7 +259,7 @@ func renderToolDetails(
if toolCall.State.Status == opencode.ToolPartStateStatusPending {
title := renderToolTitle(toolCall, width)
- return renderContentBlock(app, title, highlight, width)
+ return renderContentBlock(app, title, width)
}
var result *string
@@ -332,10 +284,6 @@ func renderToolDetails(
t := theme.CurrentTheme()
backgroundColor := t.BackgroundPanel()
borderColor := t.BackgroundPanel()
- if highlight {
- backgroundColor = t.BackgroundElement()
- borderColor = t.BorderActive()
- }
if toolCall.State.Metadata != nil {
metadata := toolCall.State.Metadata.(map[string]any)
@@ -370,9 +318,6 @@ func renderToolDetails(
Foreground(t.TextMuted()).
Padding(1, 2).
Width(width - 4)
- if highlight {
- style = style.Foreground(t.Text()).Bold(true)
- }
if diagnostics := renderDiagnostics(metadata, filename); diagnostics != "" {
diagnostics = style.Render(diagnostics)
@@ -385,7 +330,6 @@ func renderToolDetails(
content = renderContentBlock(
app,
content,
- highlight,
width,
WithPadding(0),
WithBorderColor(borderColor),
@@ -486,7 +430,7 @@ func renderToolDetails(
title := renderToolTitle(toolCall, width)
content := title + "\n\n" + body
- return renderContentBlock(app, content, highlight, width, WithBorderColor(borderColor))
+ return renderContentBlock(app, content, width, WithBorderColor(borderColor))
}
func renderToolName(name string) string {
diff --git a/packages/tui/internal/components/chat/messages.go b/packages/tui/internal/components/chat/messages.go
index 938ef683e..9ee0a348a 100644
--- a/packages/tui/internal/components/chat/messages.go
+++ b/packages/tui/internal/components/chat/messages.go
@@ -10,6 +10,7 @@ import (
"github.com/sst/opencode-sdk-go"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/components/dialog"
+ "github.com/sst/opencode/internal/components/toast"
"github.com/sst/opencode/internal/layout"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
@@ -24,12 +25,10 @@ type MessagesComponent interface {
PageDown() (tea.Model, tea.Cmd)
HalfPageUp() (tea.Model, tea.Cmd)
HalfPageDown() (tea.Model, tea.Cmd)
- First() (tea.Model, tea.Cmd)
- Last() (tea.Model, tea.Cmd)
- Previous() (tea.Model, tea.Cmd)
- Next() (tea.Model, tea.Cmd)
ToolDetailsVisible() bool
- Selected() string
+ GotoTop() (tea.Model, tea.Cmd)
+ GotoBottom() (tea.Model, tea.Cmd)
+ CopyLastMessage() (tea.Model, tea.Cmd)
}
type messagesComponent struct {
@@ -42,13 +41,8 @@ type messagesComponent struct {
tail bool
partCount int
lineCount int
- selectedPart int
- selectedText string
}
type renderFinishedMsg struct{}
-type selectedMessagePartChangedMsg struct {
- part int
-}
type ToggleToolDetailsMsg struct{}
@@ -56,17 +50,12 @@ func (m *messagesComponent) Init() tea.Cmd {
return tea.Batch(m.viewport.Init())
}
-func (m *messagesComponent) Selected() string {
- return m.selectedText
-}
-
func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd
switch msg := msg.(type) {
case app.SendMsg:
m.viewport.GotoBottom()
m.tail = true
- m.selectedPart = -1
return m, nil
case app.OptimisticMessageAddedMsg:
m.tail = true
@@ -90,8 +79,7 @@ func (m *messagesComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if m.tail {
m.viewport.GotoBottom()
}
- case selectedMessagePartChangedMsg:
- return m, m.Reload()
+
case opencode.EventListResponseEventSessionUpdated:
if msg.Properties.Info.ID == m.app.Session.ID {
m.renderView(m.width)
@@ -183,7 +171,7 @@ func (m *messagesComponent) renderView(width int) {
flexItems...,
)
- key := m.cache.GenerateKey(casted.ID, part.Text, width, m.selectedPart == m.partCount, files)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, files)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
@@ -192,14 +180,14 @@ func (m *messagesComponent) renderView(width int) {
part.Text,
m.app.Config.Username,
m.showToolDetails,
- m.partCount == m.selectedPart,
width,
files,
)
m.cache.Set(key, content)
}
if content != "" {
- m = m.updateSelected(content, part.Text)
+ m.partCount++
+ m.lineCount += lipgloss.Height(content) + 1
blocks = append(blocks, content)
}
// Only render the first text part
@@ -236,7 +224,7 @@ func (m *messagesComponent) renderView(width int) {
remaining = false
case opencode.ToolPart:
toolCallParts = append(toolCallParts, part)
- if part.State.Status != opencode.ToolPartStateStatusCompleted || part.State.Status != opencode.ToolPartStateStatusError {
+ if part.State.Status != opencode.ToolPartStateStatusCompleted && part.State.Status != opencode.ToolPartStateStatusError {
// i don't think there's a case where a tool call isn't in result state
// and the message time is 0, but just in case
finished = false
@@ -245,7 +233,7 @@ func (m *messagesComponent) renderView(width int) {
}
if finished {
- key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails, m.selectedPart == m.partCount)
+ key := m.cache.GenerateKey(casted.ID, part.Text, width, m.showToolDetails)
content, cached = m.cache.Get(key)
if !cached {
content = renderText(
@@ -254,7 +242,6 @@ func (m *messagesComponent) renderView(width int) {
part.Text,
casted.ModelID,
m.showToolDetails,
- m.partCount == m.selectedPart,
width,
"",
toolCallParts...,
@@ -268,14 +255,14 @@ func (m *messagesComponent) renderView(width int) {
part.Text,
casted.ModelID,
m.showToolDetails,
- m.partCount == m.selectedPart,
width,
"",
toolCallParts...,
)
}
if content != "" {
- m = m.updateSelected(content, part.Text)
+ m.partCount++
+ m.lineCount += lipgloss.Height(content) + 1
blocks = append(blocks, content)
}
case opencode.ToolPart:
@@ -291,14 +278,12 @@ func (m *messagesComponent) renderView(width int) {
part.ID,
m.showToolDetails,
width,
- m.partCount == m.selectedPart,
)
content, cached = m.cache.Get(key)
if !cached {
content = renderToolDetails(
m.app,
part,
- m.partCount == m.selectedPart,
width,
)
m.cache.Set(key, content)
@@ -308,12 +293,12 @@ func (m *messagesComponent) renderView(width int) {
content = renderToolDetails(
m.app,
part,
- m.partCount == m.selectedPart,
width,
)
}
if content != "" {
- m = m.updateSelected(content, "")
+ m.partCount++
+ m.lineCount += lipgloss.Height(content) + 1
blocks = append(blocks, content)
}
}
@@ -340,7 +325,6 @@ func (m *messagesComponent) renderView(width int) {
error = renderContentBlock(
m.app,
error,
- false,
width,
WithBorderColor(t.Error()),
)
@@ -350,22 +334,9 @@ func (m *messagesComponent) renderView(width int) {
}
m.viewport.SetContent("\n" + strings.Join(blocks, "\n\n"))
- if m.selectedPart == m.partCount {
- m.viewport.GotoBottom()
- }
}
-func (m *messagesComponent) updateSelected(content string, selectedText string) *messagesComponent {
- if m.selectedPart == m.partCount {
- m.viewport.SetYOffset(m.lineCount - (m.viewport.Height() / 2) + 4)
- m.selectedText = selectedText
- }
- m.partCount++
- m.lineCount += lipgloss.Height(content) + 1
- return m
-}
-
func (m *messagesComponent) header(width int) string {
if m.app.Session.ID == "" {
return ""
@@ -561,49 +532,38 @@ func (m *messagesComponent) HalfPageDown() (tea.Model, tea.Cmd) {
return m, nil
}
-func (m *messagesComponent) Previous() (tea.Model, tea.Cmd) {
- m.tail = false
- if m.selectedPart < 0 {
- m.selectedPart = m.partCount
- }
- m.selectedPart--
- if m.selectedPart < 0 {
- m.selectedPart = 0
- }
- return m, util.CmdHandler(selectedMessagePartChangedMsg{
- part: m.selectedPart,
- })
-}
-
-func (m *messagesComponent) Next() (tea.Model, tea.Cmd) {
- m.tail = false
- m.selectedPart++
- if m.selectedPart >= m.partCount {
- m.selectedPart = m.partCount
- }
- return m, util.CmdHandler(selectedMessagePartChangedMsg{
- part: m.selectedPart,
- })
+func (m *messagesComponent) ToolDetailsVisible() bool {
+ return m.showToolDetails
}
-func (m *messagesComponent) First() (tea.Model, tea.Cmd) {
- m.selectedPart = 0
- m.tail = false
- return m, util.CmdHandler(selectedMessagePartChangedMsg{
- part: m.selectedPart,
- })
+func (m *messagesComponent) GotoTop() (tea.Model, tea.Cmd) {
+ m.viewport.GotoTop()
+ return m, nil
}
-func (m *messagesComponent) Last() (tea.Model, tea.Cmd) {
- m.selectedPart = m.partCount - 1
- m.tail = true
- return m, util.CmdHandler(selectedMessagePartChangedMsg{
- part: m.selectedPart,
- })
+func (m *messagesComponent) GotoBottom() (tea.Model, tea.Cmd) {
+ m.viewport.GotoBottom()
+ return m, nil
}
-func (m *messagesComponent) ToolDetailsVisible() bool {
- return m.showToolDetails
+func (m *messagesComponent) CopyLastMessage() (tea.Model, tea.Cmd) {
+ if len(m.app.Messages) == 0 {
+ return m, nil
+ }
+ lastMessage := m.app.Messages[len(m.app.Messages)-1]
+ var lastTextPart *opencode.TextPart
+ for _, part := range lastMessage.Parts {
+ if p, ok := part.(opencode.TextPart); ok {
+ lastTextPart = &p
+ }
+ }
+ if lastTextPart == nil {
+ return m, nil
+ }
+ var cmds []tea.Cmd
+ cmds = append(cmds, m.app.SetClipboard(lastTextPart.Text))
+ cmds = append(cmds, toast.NewSuccessToast("Message copied to clipboard"))
+ return m, tea.Batch(cmds...)
}
func NewMessagesComponent(app *app.App) MessagesComponent {
@@ -616,6 +576,5 @@ func NewMessagesComponent(app *app.App) MessagesComponent {
showToolDetails: true,
cache: NewMessageCache(),
tail: true,
- selectedPart: -1,
}
}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 500c57f6f..de1c6df2d 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -1001,11 +1001,11 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
a.editor = updated.(chat.EditorComponent)
cmds = append(cmds, cmd)
case commands.MessagesFirstCommand:
- updated, cmd := a.messages.First()
+ updated, cmd := a.messages.GotoTop()
a.messages = updated.(chat.MessagesComponent)
cmds = append(cmds, cmd)
case commands.MessagesLastCommand:
- updated, cmd := a.messages.Last()
+ updated, cmd := a.messages.GotoBottom()
a.messages = updated.(chat.MessagesComponent)
cmds = append(cmds, cmd)
case commands.MessagesPageUpCommand:
@@ -1044,26 +1044,14 @@ func (a appModel) executeCommand(command commands.Command) (tea.Model, tea.Cmd)
a.messages = updated.(chat.MessagesComponent)
cmds = append(cmds, cmd)
}
- case commands.MessagesPreviousCommand:
- updated, cmd := a.messages.Previous()
- a.messages = updated.(chat.MessagesComponent)
- cmds = append(cmds, cmd)
- case commands.MessagesNextCommand:
- updated, cmd := a.messages.Next()
- a.messages = updated.(chat.MessagesComponent)
- cmds = append(cmds, cmd)
case commands.MessagesLayoutToggleCommand:
a.messagesRight = !a.messagesRight
a.app.State.MessagesRight = a.messagesRight
a.app.SaveState()
case commands.MessagesCopyCommand:
- selected := a.messages.Selected()
- if selected != "" {
- cmd = a.app.SetClipboard(selected)
- cmds = append(cmds, cmd)
- cmd = toast.NewSuccessToast("Message copied to clipboard")
- cmds = append(cmds, cmd)
- }
+ updated, cmd := a.messages.CopyLastMessage()
+ a.messages = updated.(chat.MessagesComponent)
+ cmds = append(cmds, cmd)
case commands.MessagesRevertCommand:
case commands.AppExitCommand:
return a, tea.Quit