소스 검색

chore(tui): simplify messages component, remove navigate, add copy last message

adamdotdevin 7 달 전
부모
커밋
d41aa2bc72
3개의 변경된 파일47개의 추가작업 그리고 156개의 파일을 삭제
  1. 2 58
      packages/tui/internal/components/chat/message.go
  2. 40 81
      packages/tui/internal/components/chat/messages.go
  3. 5 17
      packages/tui/internal/tui/tui.go

+ 2 - 58
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 {

+ 40 - 81
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,
 	}
 }

+ 5 - 17
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