summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authoradamdottv <[email protected]>2025-06-16 10:45:13 -0500
committeradamdottv <[email protected]>2025-06-16 10:45:19 -0500
commit3c94d265701c710e3f5ad13f2212cb8476c6a674 (patch)
tree2f2e6970b49b608fcd01155506a28de5fdc6fb92
parent1a553e525f70419ac893bec1a7fc3ea839b89b2c (diff)
downloadopencode-3c94d265701c710e3f5ad13f2212cb8476c6a674.tar.gz
opencode-3c94d265701c710e3f5ad13f2212cb8476c6a674.zip
chore: remove status service
-rw-r--r--packages/tui/cmd/opencode/main.go166
-rw-r--r--packages/tui/go.mod5
-rw-r--r--packages/tui/go.sum11
-rw-r--r--packages/tui/internal/app/app.go25
-rw-r--r--packages/tui/internal/components/chat/editor.go39
-rw-r--r--packages/tui/internal/components/core/status.go137
-rw-r--r--packages/tui/internal/components/dialog/complete.go9
-rw-r--r--packages/tui/internal/components/dialog/theme.go3
-rw-r--r--packages/tui/internal/pubsub/broker.go113
-rw-r--r--packages/tui/internal/pubsub/events.go24
-rw-r--r--packages/tui/internal/status/status.go142
-rw-r--r--packages/tui/internal/tui/tui.go18
12 files changed, 42 insertions, 650 deletions
diff --git a/packages/tui/cmd/opencode/main.go b/packages/tui/cmd/opencode/main.go
index aa52f583c..8595cb6f7 100644
--- a/packages/tui/cmd/opencode/main.go
+++ b/packages/tui/cmd/opencode/main.go
@@ -6,13 +6,9 @@ import (
"os"
"path/filepath"
"strings"
- "sync"
- "time"
tea "github.com/charmbracelet/bubbletea/v2"
- zone "github.com/lrstanley/bubblezone"
"github.com/sst/opencode/internal/app"
- "github.com/sst/opencode/internal/pubsub"
"github.com/sst/opencode/internal/tui"
"github.com/sst/opencode/pkg/client"
)
@@ -26,27 +22,6 @@ func main() {
slog.Error("Failed to create client", "error", err)
os.Exit(1)
}
- paths, err := httpClient.PostPathGetWithResponse(context.Background())
- if err != nil {
- panic(err)
- }
- logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
-
- if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
- err := os.MkdirAll(filepath.Dir(logfile), 0755)
- if err != nil {
- slog.Error("Failed to create log directory", "error", err)
- os.Exit(1)
- }
- }
- file, err := os.Create(logfile)
- if err != nil {
- slog.Error("Failed to create log file", "error", err)
- os.Exit(1)
- }
- defer file.Close()
- logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
- slog.SetDefault(logger)
// Create main context for the application
ctx, cancel := context.WithCancel(context.Background())
@@ -61,13 +36,11 @@ func main() {
panic(err)
}
- // Set up the TUI
- zone.NewGlobal()
program := tea.NewProgram(
tui.NewModel(app_),
- // tea.WithMouseCellMotion(),
- tea.WithKeyboardEnhancements(),
tea.WithAltScreen(),
+ tea.WithKeyboardEnhancements(),
+ // tea.WithMouseCellMotion(),
)
eventClient, err := client.NewClient(url)
@@ -88,54 +61,30 @@ func main() {
}
}()
- // Setup the subscriptions, this will send services events to the TUI
- ch, cancelSubs := setupSubscriptions(app_, ctx)
-
- // Create a context for the TUI message handler
- tuiCtx, tuiCancel := context.WithCancel(ctx)
- var tuiWg sync.WaitGroup
- tuiWg.Add(1)
-
- // Set up message handling for the TUI
- go func() {
- defer tuiWg.Done()
- // defer logging.RecoverPanic("TUI-message-handler", func() {
- // attemptTUIRecovery(program)
- // })
+ paths, err := httpClient.PostPathGetWithResponse(context.Background())
+ if err != nil {
+ panic(err)
+ }
+ logfile := filepath.Join(paths.JSON200.Data, "log", "tui.log")
- for {
- select {
- case <-tuiCtx.Done():
- slog.Info("TUI message handler shutting down")
- return
- case msg, ok := <-ch:
- if !ok {
- slog.Info("TUI message channel closed")
- return
- }
- program.Send(msg)
- }
+ if _, err := os.Stat(filepath.Dir(logfile)); os.IsNotExist(err) {
+ err := os.MkdirAll(filepath.Dir(logfile), 0755)
+ if err != nil {
+ slog.Error("Failed to create log directory", "error", err)
+ os.Exit(1)
}
- }()
-
- // Cleanup function for when the program exits
- cleanup := func() {
- // Cancel subscriptions first
- cancelSubs()
-
- // Then cancel TUI message handler
- tuiCancel()
-
- // Wait for TUI message handler to finish
- tuiWg.Wait()
-
- slog.Info("All goroutines cleaned up")
}
+ file, err := os.Create(logfile)
+ if err != nil {
+ slog.Error("Failed to create log file", "error", err)
+ os.Exit(1)
+ }
+ defer file.Close()
+ logger := slog.New(slog.NewTextHandler(file, &slog.HandlerOptions{Level: slog.LevelDebug}))
+ slog.SetDefault(logger)
// Run the TUI
result, err := program.Run()
- cleanup()
-
if err != nil {
slog.Error("TUI error", "error", err)
// return fmt.Errorf("TUI error: %v", err)
@@ -143,78 +92,3 @@ func main() {
slog.Info("TUI exited", "result", result)
}
-
-func setupSubscriber[T any](
- ctx context.Context,
- wg *sync.WaitGroup,
- name string,
- subscriber func(context.Context) <-chan pubsub.Event[T],
- outputCh chan<- tea.Msg,
-) {
- wg.Add(1)
- go func() {
- defer wg.Done()
- // defer logging.RecoverPanic(fmt.Sprintf("subscription-%s", name), nil)
-
- subCh := subscriber(ctx)
- if subCh == nil {
- slog.Warn("subscription channel is nil", "name", name)
- return
- }
-
- for {
- select {
- case event, ok := <-subCh:
- if !ok {
- slog.Info("subscription channel closed", "name", name)
- return
- }
-
- var msg tea.Msg = event
-
- select {
- case outputCh <- msg:
- case <-time.After(2 * time.Second):
- slog.Warn("message dropped due to slow consumer", "name", name)
- case <-ctx.Done():
- slog.Info("subscription cancelled", "name", name)
- return
- }
- case <-ctx.Done():
- slog.Info("subscription cancelled", "name", name)
- return
- }
- }
- }()
-}
-
-func setupSubscriptions(app *app.App, parentCtx context.Context) (chan tea.Msg, func()) {
- ch := make(chan tea.Msg, 100)
-
- wg := sync.WaitGroup{}
- ctx, cancel := context.WithCancel(parentCtx) // Inherit from parent context
-
- setupSubscriber(ctx, &wg, "status", app.Status.Subscribe, ch)
-
- cleanupFunc := func() {
- slog.Info("Cancelling all subscriptions")
- cancel() // Signal all goroutines to stop
-
- waitCh := make(chan struct{})
- go func() {
- // defer logging.RecoverPanic("subscription-cleanup", nil)
- wg.Wait()
- close(waitCh)
- }()
-
- select {
- case <-waitCh:
- slog.Info("All subscription goroutines completed successfully")
- close(ch) // Only close after all writers are confirmed done
- case <-time.After(5 * time.Second):
- slog.Warn("Timed out waiting for some subscription goroutines to complete")
- close(ch)
- }
- }
- return ch, cleanupFunc
-}
diff --git a/packages/tui/go.mod b/packages/tui/go.mod
index 6dddd8094..38409c3b6 100644
--- a/packages/tui/go.mod
+++ b/packages/tui/go.mod
@@ -5,14 +5,12 @@ go 1.24.0
require (
github.com/BurntSushi/toml v1.5.0
github.com/alecthomas/chroma/v2 v2.18.0
- github.com/bmatcuk/doublestar/v4 v4.8.1
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3
github.com/charmbracelet/glamour v0.10.0
github.com/charmbracelet/lipgloss/v2 v2.0.0-beta1
github.com/charmbracelet/x/ansi v0.8.0
github.com/lithammer/fuzzysearch v1.1.8
- github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.16.0
@@ -27,7 +25,6 @@ require (
dario.cat/mergo v1.0.2 // indirect
github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect
github.com/atombender/go-jsonschema v0.20.0 // indirect
- github.com/charmbracelet/bubbletea v1.3.4 // indirect
github.com/charmbracelet/lipgloss v1.1.1-0.20250404203927-76690c660834 // indirect
github.com/charmbracelet/x/exp/slice v0.0.0-20250327172914-2fdc97757edf // indirect
github.com/charmbracelet/x/input v0.3.5-0.20250424101541-abb4d9a9b197 // indirect
@@ -66,13 +63,11 @@ require (
github.com/charmbracelet/x/term v0.2.1 // indirect
github.com/disintegration/imaging v1.6.2
github.com/dlclark/regexp2 v1.11.5 // indirect
- github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/gorilla/css v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0
github.com/mattn/go-isatty v0.0.20 // indirect
- github.com/mattn/go-localereader v0.0.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/muesli/cancelreader v0.2.2 // indirect
diff --git a/packages/tui/go.sum b/packages/tui/go.sum
index 4021e9bf4..2a8c8cd46 100644
--- a/packages/tui/go.sum
+++ b/packages/tui/go.sum
@@ -24,12 +24,8 @@ github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/
github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w=
-github.com/bmatcuk/doublestar/v4 v4.8.1 h1:54Bopc5c2cAvhLRAzqOGCYHYyhcDHsFF4wWIR5wKP38=
-github.com/bmatcuk/doublestar/v4 v4.8.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1 h1:swACzss0FjnyPz1enfX56GKkLiuKg5FlyVmOLIlU2kE=
github.com/charmbracelet/bubbles/v2 v2.0.0-beta.1/go.mod h1:6HamsBKWqEC/FVHuQMHgQL+knPyvHH55HwJDHl/adMw=
-github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
-github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3 h1:5A2e3myxXMpCES+kjEWgGsaf9VgZXjZbLi5iMTH7j40=
github.com/charmbracelet/bubbletea/v2 v2.0.0-beta.3/go.mod h1:ZFDg5oPjyRYrPAa3iFrtP1DO8xy+LUQxd9JFHEcuwJY=
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
@@ -69,8 +65,6 @@ github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cn
github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w=
github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
-github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M=
@@ -126,16 +120,12 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4=
github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4=
-github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231 h1:9rjt7AfnrXKNSZhp36A3/4QAZAwGGCGD/p8Bse26zms=
-github.com/lrstanley/bubblezone v0.0.0-20250315020633-c249a3fe1231/go.mod h1:S5etECMx+sZnW0Gm100Ma9J1PgVCTgNyFaqGu2b08b4=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
-github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
-github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
@@ -267,7 +257,6 @@ golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
diff --git a/packages/tui/internal/app/app.go b/packages/tui/internal/app/app.go
index 70f655c62..5bc8eb656 100644
--- a/packages/tui/internal/app/app.go
+++ b/packages/tui/internal/app/app.go
@@ -12,7 +12,6 @@ import (
"github.com/sst/opencode/internal/commands"
"github.com/sst/opencode/internal/config"
"github.com/sst/opencode/internal/state"
- "github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
"github.com/sst/opencode/pkg/client"
@@ -26,7 +25,6 @@ type App struct {
Model *client.ModelInfo
Session *client.SessionInfo
Messages []client.MessageInfo
- Status status.Service
Commands commands.Registry
}
@@ -38,12 +36,6 @@ type AppInfo struct {
var Info AppInfo
func New(ctx context.Context, version string, httpClient *client.ClientWithResponses) (*App, error) {
- err := status.InitService()
- if err != nil {
- slog.Error("Failed to initialize status service", "error", err)
- return nil, err
- }
-
appInfoResponse, _ := httpClient.PostAppInfoWithResponse(ctx)
appInfo := appInfoResponse.JSON200
Info = AppInfo{Version: version}
@@ -114,7 +106,6 @@ func New(ctx context.Context, version string, httpClient *client.ClientWithRespo
Model: currentModel,
Session: &client.SessionInfo{},
Messages: []client.MessageInfo{},
- Status: status.GetService(),
Commands: commands.NewCommandRegistry(),
}
@@ -160,7 +151,7 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
session, err := a.CreateSession(ctx)
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return nil
}
@@ -175,10 +166,10 @@ func (a *App) InitializeProject(ctx context.Context) tea.Cmd {
ModelID: a.Model.Id,
})
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
}
if response != nil && response.StatusCode != 200 {
- status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
+ // status.Error(fmt.Sprintf("failed to initialize project: %d", response.StatusCode))
}
}()
@@ -214,7 +205,7 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
if a.Session.Id == "" {
session, err := a.CreateSession(ctx)
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return nil
}
a.Session = session
@@ -243,11 +234,11 @@ func (a *App) SendChatMessage(ctx context.Context, text string, attachments []At
})
if err != nil {
slog.Error("Failed to send message", "error", err)
- status.Error(err.Error())
+ // status.Error(err.Error())
}
if response != nil && response.StatusCode != 200 {
slog.Error("Failed to send message", "error", fmt.Sprintf("failed to send message: %d", response.StatusCode))
- status.Error(fmt.Sprintf("failed to send message: %d", response.StatusCode))
+ // status.Error(fmt.Sprintf("failed to send message: %d", response.StatusCode))
}
}()
@@ -262,12 +253,12 @@ func (a *App) Cancel(ctx context.Context, sessionID string) error {
})
if err != nil {
slog.Error("Failed to cancel session", "error", err)
- status.Error(err.Error())
+ // status.Error(err.Error())
return err
}
if response != nil && response.StatusCode != 200 {
slog.Error("Failed to cancel session", "error", fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
- status.Error(fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
+ // status.Error(fmt.Sprintf("failed to cancel session: %d", response.StatusCode))
return fmt.Errorf("failed to cancel session: %d", response.StatusCode)
}
return nil
diff --git a/packages/tui/internal/components/chat/editor.go b/packages/tui/internal/components/chat/editor.go
index 13bcf60ee..3c005a32e 100644
--- a/packages/tui/internal/components/chat/editor.go
+++ b/packages/tui/internal/components/chat/editor.go
@@ -17,7 +17,6 @@ import (
"github.com/sst/opencode/internal/components/dialog"
"github.com/sst/opencode/internal/image"
"github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
@@ -157,7 +156,7 @@ func (m *editorComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
if key.Matches(msg, editorMaps.OpenEditor) {
if m.app.IsBusy() {
- status.Warn("Agent is working, please wait...")
+ // status.Warn("Agent is working, please wait...")
return m, nil
}
value := m.textarea.Value()
@@ -323,7 +322,7 @@ func (m *editorComponent) openEditor(value string) tea.Cmd {
tmpfile, err := os.CreateTemp("", "msg_*.md")
tmpfile.WriteString(value)
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return nil
}
tmpfile.Close()
@@ -333,16 +332,16 @@ func (m *editorComponent) openEditor(value string) tea.Cmd {
c.Stderr = os.Stderr
return tea.ExecProcess(c, func(err error) tea.Msg {
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return nil
}
content, err := os.ReadFile(tmpfile.Name())
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return nil
}
if len(content) == 0 {
- status.Warn("Message is empty")
+ // status.Warn("Message is empty")
return nil
}
os.Remove(tmpfile.Name())
@@ -381,7 +380,6 @@ func (m *editorComponent) send() tea.Cmd {
// return util.CmdHandler(commands.ExecuteCommandMsg{Name: commandName})
// }
// }
- slog.Info("Send message", "value", value)
return tea.Batch(
util.CmdHandler(SendMsg{
@@ -391,33 +389,6 @@ func (m *editorComponent) send() tea.Cmd {
)
}
-func (m *editorComponent) attachmentsContent() string {
- if len(m.attachments) == 0 {
- return ""
- }
-
- t := theme.CurrentTheme()
- var styledAttachments []string
- attachmentStyles := styles.BaseStyle().
- MarginLeft(1).
- Background(t.TextMuted()).
- Foreground(t.Text())
- for i, attachment := range m.attachments {
- var filename string
- if len(attachment.FileName) > 10 {
- filename = fmt.Sprintf(" %s %s...", styles.DocumentIcon, attachment.FileName[0:7])
- } else {
- filename = fmt.Sprintf(" %s %s", styles.DocumentIcon, attachment.FileName)
- }
- if m.deleteMode {
- filename = fmt.Sprintf("%d%s", i, filename)
- }
- styledAttachments = append(styledAttachments, attachmentStyles.Render(filename))
- }
- content := lipgloss.JoinHorizontal(lipgloss.Left, styledAttachments...)
- return content
-}
-
func createTextArea(existing *textarea.Model) textarea.Model {
t := theme.CurrentTheme()
bgColor := t.BackgroundElement()
diff --git a/packages/tui/internal/components/core/status.go b/packages/tui/internal/components/core/status.go
index aa3febf9b..d014d6fa3 100644
--- a/packages/tui/internal/components/core/status.go
+++ b/packages/tui/internal/components/core/status.go
@@ -3,14 +3,11 @@ package core
import (
"fmt"
"strings"
- "time"
tea "github.com/charmbracelet/bubbletea/v2"
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/app"
"github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/pubsub"
- "github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
)
@@ -20,27 +17,12 @@ type StatusComponent interface {
}
type statusComponent struct {
- app *app.App
- queue []status.StatusMessage
- width int
- messageTTL time.Duration
- activeUntil time.Time
-}
-
-// clearMessageCmd is a command that clears status messages after a timeout
-func (m statusComponent) 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
+ app *app.App
+ width int
}
func (m statusComponent) Init() tea.Cmd {
- return m.clearMessageCmd()
+ return nil
}
func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
@@ -48,53 +30,6 @@ func (m statusComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case tea.WindowSizeMsg:
m.width = msg.Width
return m, nil
- case pubsub.Event[status.StatusMessage]:
- if msg.Type == status.EventStatusPublished {
- // If this is a critical message, move it to the front of the queue
- if msg.Payload.Critical {
- // Insert at the front of the queue
- m.queue = append([]status.StatusMessage{msg.Payload}, m.queue...)
-
- // Reset active time to show critical message immediately
- m.activeUntil = time.Time{}
- } else {
- // Otherwise, just add it to the queue
- m.queue = append(m.queue, msg.Payload)
-
- // If this is the first message and nothing is active, activate it immediately
- if len(m.queue) == 1 && m.activeUntil.IsZero() {
- now := time.Now()
- duration := m.messageTTL
- if msg.Payload.Duration > 0 {
- duration = msg.Payload.Duration
- }
- m.activeUntil = now.Add(duration)
- }
- }
- }
- case statusCleanupMsg:
- now := msg.time
-
- // If the active message has expired, remove it and activate the next one
- if !m.activeUntil.IsZero() && m.activeUntil.Before(now) {
- // Current message expired, remove it if we have one
- if len(m.queue) > 0 {
- m.queue = m.queue[1:]
- }
- m.activeUntil = time.Time{}
- }
-
- // If we have messages in queue but none are active, activate the first one
- if len(m.queue) > 0 && m.activeUntil.IsZero() {
- // Use custom duration if specified, otherwise use default
- duration := m.messageTTL
- if m.queue[0].Duration > 0 {
- duration = m.queue[0].Duration
- }
- m.activeUntil = now.Add(duration)
- }
-
- return m, m.clearMessageCmd()
}
return m, nil
}
@@ -190,75 +125,11 @@ func (m statusComponent) View() string {
blank := styles.BaseStyle().Background(t.Background()).Width(m.width).Render("")
return blank + "\n" + status
-
- // Display the first status message if available
- // var statusMessage string
- // if len(m.queue) > 0 {
- // sm := m.queue[0]
- // infoStyle := styles.Padded().
- // Foreground(t.Background())
- //
- // switch sm.Level {
- // case "info":
- // infoStyle = infoStyle.Background(t.Info())
- // case "warn":
- // infoStyle = infoStyle.Background(t.Warning())
- // case "error":
- // infoStyle = infoStyle.Background(t.Error())
- // case "debug":
- // infoStyle = infoStyle.Background(t.TextMuted())
- // }
- //
- // // Truncate message if it's longer than available width
- // msg := sm.Message
- // availWidth := statusWidth - 10
- //
- // // If we have enough space, show inline
- // if availWidth >= minInlineWidth {
- // if len(msg) > availWidth && availWidth > 0 {
- // msg = msg[:availWidth] + "..."
- // }
- // status += infoStyle.Width(statusWidth).Render(msg)
- // } else {
- // // Otherwise, prepare a full-width message to show above
- // if len(msg) > m.width-10 && m.width > 10 {
- // msg = msg[:m.width-10] + "..."
- // }
- // statusMessage = infoStyle.Width(m.width).Render(msg)
- //
- // // Add empty space in the status bar
- // status += styles.Padded().
- // Foreground(t.Text()).
- // Background(t.BackgroundSubtle()).
- // Width(statusWidth).
- // Render("")
- // }
- // } else {
- // status += styles.Padded().
- // Foreground(t.Text()).
- // Background(t.BackgroundSubtle()).
- // Width(statusWidth).
- // Render("")
- // }
-
- // status += diagnostics
- // status += modelName
-
- // If we have a separate status message, prepend it
- // if statusMessage != "" {
- // return statusMessage + "\n" + status
- // } else {
- // blank := styles.BaseStyle().Background(t.Background()).Width(m.width).Render("")
- // return blank + "\n" + status
- // }
}
func NewStatusCmp(app *app.App) StatusComponent {
statusComponent := &statusComponent{
- app: app,
- queue: []status.StatusMessage{},
- messageTTL: 4 * time.Second,
- activeUntil: time.Time{},
+ app: app,
}
return statusComponent
diff --git a/packages/tui/internal/components/dialog/complete.go b/packages/tui/internal/components/dialog/complete.go
index 74501f08a..9bde1fd04 100644
--- a/packages/tui/internal/components/dialog/complete.go
+++ b/packages/tui/internal/components/dialog/complete.go
@@ -7,7 +7,6 @@ import (
"github.com/charmbracelet/lipgloss/v2"
"github.com/sst/opencode/internal/components/list"
"github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
@@ -158,7 +157,7 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
if query != c.query {
items, err := c.completionProvider.GetChildEntries(query)
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
}
c.list.SetItems(items)
@@ -189,7 +188,7 @@ func (c *completionDialogComponent) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
} else {
items, err := c.completionProvider.GetChildEntries("")
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
}
c.list.SetItems(items)
@@ -246,7 +245,7 @@ func (c *completionDialogComponent) SetProvider(provider CompletionProvider) {
c.completionProvider = provider
items, err := provider.GetChildEntries("")
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
}
c.list.SetItems(items)
}
@@ -257,7 +256,7 @@ func NewCompletionDialogComponent(completionProvider CompletionProvider) Complet
items, err := completionProvider.GetChildEntries("")
if err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
}
li := list.NewListComponent(
diff --git a/packages/tui/internal/components/dialog/theme.go b/packages/tui/internal/components/dialog/theme.go
index f045b1b09..50472428b 100644
--- a/packages/tui/internal/components/dialog/theme.go
+++ b/packages/tui/internal/components/dialog/theme.go
@@ -5,7 +5,6 @@ import (
list "github.com/sst/opencode/internal/components/list"
"github.com/sst/opencode/internal/components/modal"
"github.com/sst/opencode/internal/layout"
- "github.com/sst/opencode/internal/status"
"github.com/sst/opencode/internal/styles"
"github.com/sst/opencode/internal/theme"
"github.com/sst/opencode/internal/util"
@@ -71,7 +70,7 @@ func (t *themeDialog) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return t, util.CmdHandler(modal.CloseModalMsg{})
}
if err := theme.SetTheme(selectedTheme); err != nil {
- status.Error(err.Error())
+ // status.Error(err.Error())
return t, nil
}
return t, tea.Sequence(
diff --git a/packages/tui/internal/pubsub/broker.go b/packages/tui/internal/pubsub/broker.go
deleted file mode 100644
index 05a4476c8..000000000
--- a/packages/tui/internal/pubsub/broker.go
+++ /dev/null
@@ -1,113 +0,0 @@
-package pubsub
-
-import (
- "context"
- "fmt"
- "log/slog"
- "sync"
- "time"
-)
-
-const defaultChannelBufferSize = 100
-
-type Broker[T any] struct {
- subs map[chan Event[T]]context.CancelFunc
- mu sync.RWMutex
- isClosed bool
-}
-
-func NewBroker[T any]() *Broker[T] {
- return &Broker[T]{
- subs: make(map[chan Event[T]]context.CancelFunc),
- }
-}
-
-func (b *Broker[T]) Shutdown() {
- b.mu.Lock()
- if b.isClosed {
- b.mu.Unlock()
- return
- }
- b.isClosed = true
-
- for ch, cancel := range b.subs {
- cancel()
- close(ch)
- delete(b.subs, ch)
- }
- b.mu.Unlock()
- slog.Debug("PubSub broker shut down", "type", fmt.Sprintf("%T", *new(T)))
-}
-
-func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
- b.mu.Lock()
- defer b.mu.Unlock()
-
- if b.isClosed {
- closedCh := make(chan Event[T])
- close(closedCh)
- return closedCh
- }
-
- subCtx, subCancel := context.WithCancel(ctx)
- subscriberChannel := make(chan Event[T], defaultChannelBufferSize)
- b.subs[subscriberChannel] = subCancel
-
- go func() {
- <-subCtx.Done()
- b.mu.Lock()
- defer b.mu.Unlock()
- if _, ok := b.subs[subscriberChannel]; ok {
- close(subscriberChannel)
- delete(b.subs, subscriberChannel)
- }
- }()
-
- return subscriberChannel
-}
-
-func (b *Broker[T]) Publish(eventType EventType, payload T) {
- b.mu.RLock()
- defer b.mu.RUnlock()
-
- if b.isClosed {
- slog.Warn("Attempted to publish on a closed pubsub broker", "type", eventType, "payload_type", fmt.Sprintf("%T", payload))
- return
- }
-
- event := Event[T]{Type: eventType, Payload: payload}
-
- for ch := range b.subs {
- // Non-blocking send with a fallback to a goroutine to prevent slow subscribers
- // from blocking the publisher.
- select {
- case ch <- event:
- // Successfully sent
- default:
- // Subscriber channel is full or receiver is slow.
- // Send in a new goroutine to avoid blocking the publisher.
- // This might lead to out-of-order delivery for this specific slow subscriber.
- go func(sChan chan Event[T], ev Event[T]) {
- // Re-check if broker is closed before attempting send in goroutine
- b.mu.RLock()
- isBrokerClosed := b.isClosed
- b.mu.RUnlock()
- if isBrokerClosed {
- return
- }
-
- select {
- case sChan <- ev:
- case <-time.After(2 * time.Second): // Timeout for slow subscriber
- slog.Warn("PubSub: Dropped event for slow subscriber after timeout", "type", ev.Type)
- }
- }(ch, event)
- }
- }
-}
-
-func (b *Broker[T]) GetSubscriberCount() int {
- b.mu.RLock()
- defer b.mu.RUnlock()
- return len(b.subs)
-}
diff --git a/packages/tui/internal/pubsub/events.go b/packages/tui/internal/pubsub/events.go
deleted file mode 100644
index e3910f9f5..000000000
--- a/packages/tui/internal/pubsub/events.go
+++ /dev/null
@@ -1,24 +0,0 @@
-package pubsub
-
-import "context"
-
-type EventType string
-
-const (
- EventTypeCreated EventType = "created"
- EventTypeUpdated EventType = "updated"
- EventTypeDeleted EventType = "deleted"
-)
-
-type Event[T any] struct {
- Type EventType
- Payload T
-}
-
-type Subscriber[T any] interface {
- Subscribe(ctx context.Context) <-chan Event[T]
-}
-
-type Publisher[T any] interface {
- Publish(eventType EventType, payload T)
-}
diff --git a/packages/tui/internal/status/status.go b/packages/tui/internal/status/status.go
deleted file mode 100644
index 3648a64ae..000000000
--- a/packages/tui/internal/status/status.go
+++ /dev/null
@@ -1,142 +0,0 @@
-package status
-
-import (
- "context"
- "fmt"
- "log/slog"
- "sync"
- "time"
-
- "github.com/sst/opencode/internal/pubsub"
-)
-
-type Level string
-
-const (
- LevelInfo Level = "info"
- LevelWarn Level = "warn"
- LevelError Level = "error"
- LevelDebug Level = "debug"
-)
-
-type StatusMessage struct {
- Level Level `json:"level"`
- Message string `json:"message"`
- Timestamp time.Time `json:"timestamp"`
- Critical bool `json:"critical"`
- Duration time.Duration `json:"duration"`
-}
-
-// StatusOption is a function that configures a status message
-type StatusOption func(*StatusMessage)
-
-// WithCritical marks a status message as critical, causing it to be displayed immediately
-func WithCritical(critical bool) StatusOption {
- return func(msg *StatusMessage) {
- msg.Critical = critical
- }
-}
-
-// WithDuration sets a custom display duration for a status message
-func WithDuration(duration time.Duration) StatusOption {
- return func(msg *StatusMessage) {
- msg.Duration = duration
- }
-}
-
-const (
- EventStatusPublished pubsub.EventType = "status_published"
-)
-
-type Service interface {
- pubsub.Subscriber[StatusMessage]
-
- Info(message string, opts ...StatusOption)
- Warn(message string, opts ...StatusOption)
- Error(message string, opts ...StatusOption)
- Debug(message string, opts ...StatusOption)
-}
-
-type service struct {
- broker *pubsub.Broker[StatusMessage]
- mu sync.RWMutex
-}
-
-var globalStatusService *service
-
-func InitService() error {
- if globalStatusService != nil {
- return fmt.Errorf("status service already initialized")
- }
- broker := pubsub.NewBroker[StatusMessage]()
- globalStatusService = &service{
- broker: broker,
- }
- return nil
-}
-
-func GetService() Service {
- if globalStatusService == nil {
- panic("status service not initialized. Call status.InitService() at application startup.")
- }
- return globalStatusService
-}
-
-func (s *service) Info(message string, opts ...StatusOption) {
- s.publish(LevelInfo, message, opts...)
- slog.Info(message)
-}
-
-func (s *service) Warn(message string, opts ...StatusOption) {
- s.publish(LevelWarn, message, opts...)
- slog.Warn(message)
-}
-
-func (s *service) Error(message string, opts ...StatusOption) {
- s.publish(LevelError, message, opts...)
- slog.Error(message)
-}
-
-func (s *service) Debug(message string, opts ...StatusOption) {
- s.publish(LevelDebug, message, opts...)
- slog.Debug(message)
-}
-
-func (s *service) publish(level Level, messageText string, opts ...StatusOption) {
- statusMsg := StatusMessage{
- Level: level,
- Message: messageText,
- Timestamp: time.Now(),
- }
-
- // Apply all options
- for _, opt := range opts {
- opt(&statusMsg)
- }
-
- s.broker.Publish(EventStatusPublished, statusMsg)
-}
-
-func (s *service) Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
- return s.broker.Subscribe(ctx)
-}
-
-func Info(message string, opts ...StatusOption) {
- GetService().Info(message, opts...)
-}
-
-func Warn(message string, opts ...StatusOption) {
- GetService().Warn(message, opts...)
-}
-
-func Error(message string, opts ...StatusOption) {
- GetService().Error(message, opts...)
-}
-
-func Debug(message string, opts ...StatusOption) {
- GetService().Debug(message, opts...)
-}
-
-func Subscribe(ctx context.Context) <-chan pubsub.Event[StatusMessage] {
- return GetService().Subscribe(ctx)
-}
diff --git a/packages/tui/internal/tui/tui.go b/packages/tui/internal/tui/tui.go
index 426ebd326..2cc203b6a 100644
--- a/packages/tui/internal/tui/tui.go
+++ b/packages/tui/internal/tui/tui.go
@@ -224,24 +224,6 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
return a, tea.Batch(cmds...)
- // case pubsub.Event[permission.PermissionRequest]:
- // a.showPermissions = true
- // return a, a.permissions.SetPermissions(msg.Payload)
-
- case dialog.PermissionResponseMsg:
- // TODO: Permissions service not implemented in API yet
- // var cmd tea.Cmd
- // switch msg.Action {
- // case dialog.PermissionAllow:
- // a.app.Permissions.Grant(context.Background(), msg.Permission)
- // case dialog.PermissionAllowForSession:
- // a.app.Permissions.GrantPersistant(context.Background(), msg.Permission)
- // case dialog.PermissionDeny:
- // a.app.Permissions.Deny(context.Background(), msg.Permission)
- // }
- // a.showPermissions = false
- return a, nil
-
case page.PageChangeMsg:
return a, a.moveToPage(msg.ID)