summaryrefslogtreecommitdiffhomepage
path: root/internal/tui/components
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-05-08 12:03:59 -0500
committeradamdottv <[email protected]>2025-05-08 12:03:59 -0500
commitf41b7bbd0a0cc731fd7c471b7ee8b26f14a21755 (patch)
treebd34e77a07516735a220c210d4930fbe2132a63b /internal/tui/components
parente35ea2d448d1a3c9cf0a6fba1318e522fc61d1eb (diff)
downloadopencode-f41b7bbd0a0cc731fd7c471b7ee8b26f14a21755.tar.gz
opencode-f41b7bbd0a0cc731fd7c471b7ee8b26f14a21755.zip
chore: refactoring status updates
Diffstat (limited to 'internal/tui/components')
-rw-r--r--internal/tui/components/chat/editor.go22
-rw-r--r--internal/tui/components/chat/list.go37
-rw-r--r--internal/tui/components/core/status.go90
-rw-r--r--internal/tui/components/dialog/filepicker.go21
-rw-r--r--internal/tui/components/dialog/models.go3
-rw-r--r--internal/tui/components/dialog/theme.go5
6 files changed, 110 insertions, 68 deletions
diff --git a/internal/tui/components/chat/editor.go b/internal/tui/components/chat/editor.go
index 982415182..99991cdf7 100644
--- a/internal/tui/components/chat/editor.go
+++ b/internal/tui/components/chat/editor.go
@@ -12,9 +12,9 @@ import (
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/opencode-ai/opencode/internal/app"
- "github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/message"
"github.com/opencode-ai/opencode/internal/session"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/components/dialog"
"github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/opencode-ai/opencode/internal/tui/styles"
@@ -87,7 +87,8 @@ func (m *editorCmp) openEditor(value string) tea.Cmd {
tmpfile, err := os.CreateTemp("", "msg_*.md")
tmpfile.WriteString(value)
if err != nil {
- return util.ReportError(err)
+ status.Error(err.Error())
+ return nil
}
tmpfile.Close()
c := exec.Command(editor, tmpfile.Name()) //nolint:gosec
@@ -96,14 +97,17 @@ func (m *editorCmp) openEditor(value string) tea.Cmd {
c.Stderr = os.Stderr
return tea.ExecProcess(c, func(err error) tea.Msg {
if err != nil {
- return util.ReportError(err)
+ status.Error(err.Error())
+ return nil
}
content, err := os.ReadFile(tmpfile.Name())
if err != nil {
- return util.ReportError(err)
+ status.Error(err.Error())
+ return nil
}
if len(content) == 0 {
- return util.ReportWarn("Message is empty")
+ status.Warn("Message is empty")
+ return nil
}
os.Remove(tmpfile.Name())
attachments := m.attachments
@@ -121,7 +125,8 @@ func (m *editorCmp) Init() tea.Cmd {
func (m *editorCmp) send() tea.Cmd {
if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
- return util.ReportWarn("Agent is working, please wait...")
+ status.Warn("Agent is working, please wait...")
+ return nil
}
value := m.textarea.Value()
@@ -153,7 +158,7 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return m, nil
case dialog.AttachmentAddedMsg:
if len(m.attachments) >= maxAttachments {
- logging.ErrorPersist(fmt.Sprintf("cannot add more than %d images", maxAttachments))
+ status.Error(fmt.Sprintf("cannot add more than %d images", maxAttachments))
return m, cmd
}
m.attachments = append(m.attachments, msg.Attachment)
@@ -185,7 +190,8 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
if key.Matches(msg, editorMaps.OpenEditor) {
if m.app.CoderAgent.IsSessionBusy(m.session.ID) {
- return m, util.ReportWarn("Agent is working, please wait...")
+ status.Warn("Agent is working, please wait...")
+ return m, nil
}
value := m.textarea.Value()
m.textarea.Reset()
diff --git a/internal/tui/components/chat/list.go b/internal/tui/components/chat/list.go
index 83538eb3b..55f76c342 100644
--- a/internal/tui/components/chat/list.go
+++ b/internal/tui/components/chat/list.go
@@ -15,10 +15,10 @@ import (
"github.com/opencode-ai/opencode/internal/message"
"github.com/opencode-ai/opencode/internal/pubsub"
"github.com/opencode-ai/opencode/internal/session"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/components/dialog"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
- "github.com/opencode-ai/opencode/internal/tui/util"
)
type cacheItem struct {
@@ -26,17 +26,17 @@ type cacheItem struct {
content []uiMessage
}
type messagesCmp struct {
- app *app.App
- width, height int
- viewport viewport.Model
- session session.Session
- messages []message.Message
- uiMessages []uiMessage
- currentMsgID string
- cachedContent map[string]cacheItem
- spinner spinner.Model
- rendering bool
- attachments viewport.Model
+ app *app.App
+ width, height int
+ viewport viewport.Model
+ session session.Session
+ messages []message.Message
+ uiMessages []uiMessage
+ currentMsgID string
+ cachedContent map[string]cacheItem
+ spinner spinner.Model
+ rendering bool
+ attachments viewport.Model
showToolMessages bool
}
type renderFinishedMsg struct{}
@@ -447,7 +447,8 @@ func (m *messagesCmp) SetSession(session session.Session) tea.Cmd {
m.session = session
messages, err := m.app.Messages.List(context.Background(), session.ID)
if err != nil {
- return util.ReportError(err)
+ status.Error(err.Error())
+ return nil
}
m.messages = messages
if len(m.messages) > 0 {
@@ -483,11 +484,11 @@ func NewMessagesCmp(app *app.App) tea.Model {
vp.KeyMap.HalfPageUp = messageKeys.HalfPageUp
vp.KeyMap.HalfPageDown = messageKeys.HalfPageDown
return &messagesCmp{
- app: app,
- cachedContent: make(map[string]cacheItem),
- viewport: vp,
- spinner: s,
- attachments: attachmets,
+ app: app,
+ cachedContent: make(map[string]cacheItem),
+ viewport: vp,
+ spinner: s,
+ attachments: attachmets,
showToolMessages: true,
}
}
diff --git a/internal/tui/components/core/status.go b/internal/tui/components/core/status.go
index 9460e7f98..84e0be42b 100644
--- a/internal/tui/components/core/status.go
+++ b/internal/tui/components/core/status.go
@@ -13,10 +13,10 @@ import (
"github.com/opencode-ai/opencode/internal/lsp/protocol"
"github.com/opencode-ai/opencode/internal/pubsub"
"github.com/opencode-ai/opencode/internal/session"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/components/chat"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
- "github.com/opencode-ai/opencode/internal/tui/util"
)
type StatusCmp interface {
@@ -25,22 +25,34 @@ type StatusCmp interface {
}
type statusCmp struct {
- info util.InfoMsg
- width int
- messageTTL time.Duration
- lspClients map[string]*lsp.Client
- session session.Session
+ statusMessages []statusMessage
+ width int
+ messageTTL time.Duration
+ lspClients map[string]*lsp.Client
+ session session.Session
+}
+
+type statusMessage struct {
+ Level status.Level
+ Message string
+ Timestamp time.Time
+ ExpiresAt time.Time
}
// clearMessageCmd is a command that clears status messages after a timeout
-func (m statusCmp) clearMessageCmd(ttl time.Duration) tea.Cmd {
- return tea.Tick(ttl, func(time.Time) tea.Msg {
- return util.ClearStatusMsg{}
+func (m statusCmp) clearMessageCmd() tea.Cmd {
+ return tea.Tick(time.Second, func(t time.Time) tea.Msg {
+ return statusCleanupMsg{time: t}
})
}
+// statusCleanupMsg is a message that triggers cleanup of expired status messages
+type statusCleanupMsg struct {
+ time time.Time
+}
+
func (m statusCmp) Init() tea.Cmd {
- return nil
+ return m.clearMessageCmd()
}
func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -58,15 +70,26 @@ func (m statusCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.session = msg.Payload
}
}
- case util.InfoMsg:
- m.info = msg
- ttl := msg.TTL
- if ttl == 0 {
- ttl = m.messageTTL
+ case pubsub.Event[status.StatusMessage]:
+ if msg.Type == pubsub.CreatedEvent {
+ statusMsg := statusMessage{
+ Level: msg.Payload.Level,
+ Message: msg.Payload.Message,
+ Timestamp: msg.Payload.Timestamp,
+ ExpiresAt: msg.Payload.Timestamp.Add(m.messageTTL),
+ }
+ m.statusMessages = append(m.statusMessages, statusMsg)
}
- return m, m.clearMessageCmd(ttl)
- case util.ClearStatusMsg:
- m.info = util.InfoMsg{}
+ case statusCleanupMsg:
+ // Remove expired messages
+ var activeMessages []statusMessage
+ for _, sm := range m.statusMessages {
+ if sm.ExpiresAt.After(msg.time) {
+ activeMessages = append(activeMessages, sm)
+ }
+ }
+ m.statusMessages = activeMessages
+ return m, m.clearMessageCmd()
}
return m, nil
}
@@ -128,8 +151,7 @@ func (m statusCmp) View() string {
status += tokensStyle
}
- diagnostics :=
- styles.Padded().Background(t.BackgroundDarker()).Render(m.projectDiagnostics())
+ diagnostics := styles.Padded().Background(t.BackgroundDarker()).Render(m.projectDiagnostics())
model := m.model()
@@ -141,25 +163,31 @@ func (m statusCmp) View() string {
lipgloss.Width(diagnostics),
)
- if m.info.Msg != "" {
+ // Display the first status message if available
+ if len(m.statusMessages) > 0 {
+ sm := m.statusMessages[0]
infoStyle := styles.Padded().
Foreground(t.Background()).
Width(statusWidth)
- switch m.info.Type {
- case util.InfoTypeInfo:
+
+ switch sm.Level {
+ case "info":
infoStyle = infoStyle.Background(t.Info())
- case util.InfoTypeWarn:
+ case "warn":
infoStyle = infoStyle.Background(t.Warning())
- case util.InfoTypeError:
+ case "error":
infoStyle = infoStyle.Background(t.Error())
+ case "debug":
+ infoStyle = infoStyle.Background(t.TextMuted())
}
// Truncate message if it's longer than available width
- msg := m.info.Msg
+ msg := sm.Message
availWidth := statusWidth - 10
if len(msg) > availWidth && availWidth > 0 {
msg = msg[:availWidth] + "..."
}
+
status += infoStyle.Render(msg)
} else {
status += styles.Padded().
@@ -272,8 +300,12 @@ func NewStatusCmp(lspClients map[string]*lsp.Client) StatusCmp {
// Initialize the help widget with default text
helpWidget = getHelpWidget("")
- return &statusCmp{
- messageTTL: 10 * time.Second,
- lspClients: lspClients,
+ statusComponent := &statusCmp{
+ statusMessages: []statusMessage{},
+ messageTTL: 4 * time.Second,
+ lspClients: lspClients,
}
+
+ return statusComponent
}
+
diff --git a/internal/tui/components/dialog/filepicker.go b/internal/tui/components/dialog/filepicker.go
index a61c8ef54..f747dc9af 100644
--- a/internal/tui/components/dialog/filepicker.go
+++ b/internal/tui/components/dialog/filepicker.go
@@ -18,6 +18,7 @@ import (
"github.com/opencode-ai/opencode/internal/config"
"github.com/opencode-ai/opencode/internal/logging"
"github.com/opencode-ai/opencode/internal/message"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/image"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
@@ -156,7 +157,7 @@ func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
path = f.cwd.Value()
fileInfo, err := os.Stat(path)
if err != nil {
- logging.ErrorPersist("Invalid path")
+ status.Error("Invalid path")
return f, cmd
}
isPathDir = fileInfo.IsDir()
@@ -225,7 +226,7 @@ func (f *filepickerCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
modeInfo := GetSelectedModel(config.Get())
if !modeInfo.SupportsAttachments {
- logging.ErrorPersist(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name))
+ status.Error(fmt.Sprintf("Model %s doesn't support attachments", modeInfo.Name))
return f, nil
}
if isExtSupported(f.dirs[f.cursor].Name()) {
@@ -233,17 +234,17 @@ func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
selectedFilePath := filepath.Join(f.cwdDetails.directory, "/", f.selectedFile)
isFileLarge, err := image.ValidateFileSize(selectedFilePath, maxAttachmentSize)
if err != nil {
- logging.ErrorPersist("unable to read the image")
+ status.Error("unable to read the image")
return f, nil
}
if isFileLarge {
- logging.ErrorPersist("file too large, max 5MB")
+ status.Error("file too large, max 5MB")
return f, nil
}
content, err := os.ReadFile(selectedFilePath)
if err != nil {
- logging.ErrorPersist("Unable read selected file")
+ status.Error("Unable read selected file")
return f, nil
}
@@ -255,7 +256,7 @@ func (f *filepickerCmp) addAttachmentToMessage() (tea.Model, tea.Cmd) {
return f, util.CmdHandler(AttachmentAddedMsg{attachment})
}
if !isExtSupported(f.selectedFile) {
- logging.ErrorPersist("Unsupported file")
+ status.Error("Unsupported file")
return f, nil
}
return f, nil
@@ -425,7 +426,7 @@ func readDir(path string, showHidden bool) []os.DirEntry {
go func() {
dirEntries, err := os.ReadDir(path)
if err != nil {
- logging.ErrorPersist(err.Error())
+ status.Error(err.Error())
errChan <- err
return
}
@@ -457,12 +458,12 @@ func readDir(path string, showHidden bool) []os.DirEntry {
return sanitizedDirEntries
- case err := <-errChan:
- logging.ErrorPersist(fmt.Sprintf("Error reading directory %s", path), err)
+ case <-errChan:
+ status.Error(fmt.Sprintf("Error reading directory %s", path))
return []os.DirEntry{}
case <-time.After(5 * time.Second):
- logging.ErrorPersist(fmt.Sprintf("Timeout reading directory %s", path), nil)
+ status.Error(fmt.Sprintf("Timeout reading directory %s", path))
return []os.DirEntry{}
}
}
diff --git a/internal/tui/components/dialog/models.go b/internal/tui/components/dialog/models.go
index 77c2a02ac..9dc2fdffe 100644
--- a/internal/tui/components/dialog/models.go
+++ b/internal/tui/components/dialog/models.go
@@ -10,6 +10,7 @@ import (
"github.com/charmbracelet/lipgloss"
"github.com/opencode-ai/opencode/internal/config"
"github.com/opencode-ai/opencode/internal/llm/models"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
@@ -126,7 +127,7 @@ func (m *modelDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.switchProvider(1)
}
case key.Matches(msg, modelKeys.Enter):
- util.ReportInfo(fmt.Sprintf("selected model: %s", m.models[m.selectedIdx].Name))
+ status.Info(fmt.Sprintf("selected model: %s", m.models[m.selectedIdx].Name))
return m, util.CmdHandler(ModelSelectedMsg{Model: m.models[m.selectedIdx]})
case key.Matches(msg, modelKeys.Escape):
return m, util.CmdHandler(CloseModelDialogMsg{})
diff --git a/internal/tui/components/dialog/theme.go b/internal/tui/components/dialog/theme.go
index d35d3e2b6..1794bd631 100644
--- a/internal/tui/components/dialog/theme.go
+++ b/internal/tui/components/dialog/theme.go
@@ -4,6 +4,7 @@ import (
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
+ "github.com/opencode-ai/opencode/internal/status"
"github.com/opencode-ai/opencode/internal/tui/layout"
"github.com/opencode-ai/opencode/internal/tui/styles"
"github.com/opencode-ai/opencode/internal/tui/theme"
@@ -106,7 +107,8 @@ func (t *themeDialogCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return t, util.CmdHandler(CloseThemeDialogMsg{})
}
if err := theme.SetTheme(selectedTheme); err != nil {
- return t, util.ReportError(err)
+ status.Error(err.Error())
+ return t, nil
}
return t, util.CmdHandler(ThemeChangedMsg{
ThemeName: selectedTheme,
@@ -195,4 +197,3 @@ func NewThemeDialogCmp() ThemeDialog {
currentTheme: "",
}
}
-