summaryrefslogtreecommitdiffhomepage
path: root/internal
diff options
context:
space:
mode:
authorKujtim Hoxha <[email protected]>2025-04-21 13:33:51 +0200
committerKujtim Hoxha <[email protected]>2025-04-21 13:42:29 +0200
commite7bb99baab5e6968ce0351d6ad219ed21ceec4df (patch)
tree01bee64956837d810a61938bef0f70af006ca4f1 /internal
parent1da298e7554bab0f7a631a44fed12692d668c024 (diff)
downloadopencode-e7bb99baab5e6968ce0351d6ad219ed21ceec4df.tar.gz
opencode-e7bb99baab5e6968ce0351d6ad219ed21ceec4df.zip
fix the memory bug
Diffstat (limited to 'internal')
-rw-r--r--internal/pubsub/broker.go72
-rw-r--r--internal/tui/components/chat/list.go1
-rw-r--r--internal/tui/components/core/status.go15
-rw-r--r--internal/tui/tui.go63
4 files changed, 111 insertions, 40 deletions
diff --git a/internal/pubsub/broker.go b/internal/pubsub/broker.go
index d73accffb..0de1be063 100644
--- a/internal/pubsub/broker.go
+++ b/internal/pubsub/broker.go
@@ -5,47 +5,53 @@ import (
"sync"
)
-const bufferSize = 1024
+const bufferSize = 64
-// Broker allows clients to publish events and subscribe to events
type Broker[T any] struct {
- subs map[chan Event[T]]struct{} // subscriptions
- mu sync.Mutex // sync access to map
- done chan struct{} // close when broker is shutting down
+ subs map[chan Event[T]]struct{}
+ mu sync.RWMutex
+ done chan struct{}
+ subCount int
+ maxEvents int
}
-// NewBroker constructs a pub/sub broker.
func NewBroker[T any]() *Broker[T] {
+ return NewBrokerWithOptions[T](bufferSize, 1000)
+}
+
+func NewBrokerWithOptions[T any](channelBufferSize, maxEvents int) *Broker[T] {
b := &Broker[T]{
- subs: make(map[chan Event[T]]struct{}),
- done: make(chan struct{}),
+ subs: make(map[chan Event[T]]struct{}),
+ done: make(chan struct{}),
+ subCount: 0,
+ maxEvents: maxEvents,
}
return b
}
-// Shutdown the broker, terminating any subscriptions.
func (b *Broker[T]) Shutdown() {
- close(b.done)
+ select {
+ case <-b.done: // Already closed
+ return
+ default:
+ close(b.done)
+ }
b.mu.Lock()
defer b.mu.Unlock()
- // Remove each subscriber entry, so Publish() cannot send any further
- // messages, and close each subscriber's channel, so the subscriber cannot
- // consume any more messages.
for ch := range b.subs {
delete(b.subs, ch)
close(ch)
}
+
+ b.subCount = 0
}
-// Subscribe subscribes the caller to a stream of events. The returned channel
-// is closed when the broker is shutdown.
func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
b.mu.Lock()
defer b.mu.Unlock()
- // Check if broker has shutdown and if so return closed channel
select {
case <-b.done:
ch := make(chan Event[T])
@@ -54,18 +60,16 @@ func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
default:
}
- // Subscribe
sub := make(chan Event[T], bufferSize)
b.subs[sub] = struct{}{}
+ b.subCount++
- // Unsubscribe when context is done.
go func() {
<-ctx.Done()
b.mu.Lock()
defer b.mu.Unlock()
- // Check if broker has shutdown and if so do nothing
select {
case <-b.done:
return
@@ -74,21 +78,39 @@ func (b *Broker[T]) Subscribe(ctx context.Context) <-chan Event[T] {
delete(b.subs, sub)
close(sub)
+ b.subCount--
}()
return sub
}
-// Publish an event to subscribers.
+func (b *Broker[T]) GetSubscriberCount() int {
+ b.mu.RLock()
+ defer b.mu.RUnlock()
+ return b.subCount
+}
+
func (b *Broker[T]) Publish(t EventType, payload T) {
- b.mu.Lock()
- defer b.mu.Unlock()
+ b.mu.RLock()
+ select {
+ case <-b.done:
+ b.mu.RUnlock()
+ return
+ default:
+ }
+ subscribers := make([]chan Event[T], 0, len(b.subs))
for sub := range b.subs {
+ subscribers = append(subscribers, sub)
+ }
+ b.mu.RUnlock()
+
+ event := Event[T]{Type: t, Payload: payload}
+
+ for _, sub := range subscribers {
select {
- case sub <- Event[T]{Type: t, Payload: payload}:
- case <-b.done:
- return
+ case sub <- event:
+ default:
}
}
}
diff --git a/internal/tui/components/chat/list.go b/internal/tui/components/chat/list.go
index b09cc4495..03a50541e 100644
--- a/internal/tui/components/chat/list.go
+++ b/internal/tui/components/chat/list.go
@@ -370,6 +370,7 @@ func (m *messagesCmp) SetSize(width, height int) tea.Cmd {
delete(m.cachedContent, msg.ID)
}
m.uiMessages = make([]uiMessage, 0)
+ m.renderView()
return nil
}
diff --git a/internal/tui/components/core/status.go b/internal/tui/components/core/status.go
index 5a2114e83..8bf3e5166 100644
--- a/internal/tui/components/core/status.go
+++ b/internal/tui/components/core/status.go
@@ -18,6 +18,11 @@ import (
"github.com/kujtimiihoxha/opencode/internal/tui/util"
)
+type StatusCmp interface {
+ tea.Model
+ SetHelpMsg(string)
+}
+
type statusCmp struct {
info util.InfoMsg
width int
@@ -146,7 +151,7 @@ func (m *statusCmp) projectDiagnostics() string {
break
}
}
-
+
// If any server is initializing, show that status
if initializing {
return lipgloss.NewStyle().
@@ -154,7 +159,7 @@ func (m *statusCmp) projectDiagnostics() string {
Foreground(styles.Peach).
Render(fmt.Sprintf("%s Initializing LSP...", styles.SpinnerIcon))
}
-
+
errorDiagnostics := []protocol.Diagnostic{}
warnDiagnostics := []protocol.Diagnostic{}
hintDiagnostics := []protocol.Diagnostic{}
@@ -235,7 +240,11 @@ func (m statusCmp) model() string {
return styles.Padded.Background(styles.Grey).Foreground(styles.Text).Render(model.Name)
}
-func NewStatusCmp(lspClients map[string]*lsp.Client) tea.Model {
+func (m statusCmp) SetHelpMsg(s string) {
+ helpWidget = styles.Padded.Background(styles.Forground).Foreground(styles.BackgroundDarker).Bold(true).Render(s)
+}
+
+func NewStatusCmp(lspClients map[string]*lsp.Client) StatusCmp {
return &statusCmp{
messageTTL: 10 * time.Second,
lspClients: lspClients,
diff --git a/internal/tui/tui.go b/internal/tui/tui.go
index 2a9ed0d70..dec43f7c0 100644
--- a/internal/tui/tui.go
+++ b/internal/tui/tui.go
@@ -39,12 +39,18 @@ var keys = keyMap{
key.WithKeys("ctrl+_"),
key.WithHelp("ctrl+?", "toggle help"),
),
+
SwitchSession: key.NewBinding(
key.WithKeys("ctrl+a"),
key.WithHelp("ctrl+a", "switch session"),
),
}
+var helpEsc = key.NewBinding(
+ key.WithKeys("?"),
+ key.WithHelp("?", "toggle help"),
+)
+
var returnKey = key.NewBinding(
key.WithKeys("esc"),
key.WithHelp("esc", "close"),
@@ -61,7 +67,7 @@ type appModel struct {
previousPage page.PageID
pages map[page.PageID]tea.Model
loadedPages map[page.PageID]bool
- status tea.Model
+ status core.StatusCmp
app *app.App
showPermissions bool
@@ -75,6 +81,8 @@ type appModel struct {
showSessionDialog bool
sessionDialog dialog.SessionDialog
+
+ editingMode bool
}
func (a appModel) Init() tea.Cmd {
@@ -101,7 +109,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
msg.Height -= 1 // Make space for the status bar
a.width, a.height = msg.Width, msg.Height
- a.status, _ = a.status.Update(msg)
+ s, _ := a.status.Update(msg)
+ a.status = s.(core.StatusCmp)
a.pages[a.currentPage], cmd = a.pages[a.currentPage].Update(msg)
cmds = append(cmds, cmd)
@@ -118,45 +127,56 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
cmds = append(cmds, sessionCmd)
return a, tea.Batch(cmds...)
-
+ case chat.EditorFocusMsg:
+ a.editingMode = bool(msg)
// Status
case util.InfoMsg:
- a.status, cmd = a.status.Update(msg)
+ s, cmd := a.status.Update(msg)
+ a.status = s.(core.StatusCmp)
cmds = append(cmds, cmd)
return a, tea.Batch(cmds...)
case pubsub.Event[logging.LogMessage]:
if msg.Payload.Persist {
switch msg.Payload.Level {
case "error":
- a.status, cmd = a.status.Update(util.InfoMsg{
+ s, cmd := a.status.Update(util.InfoMsg{
Type: util.InfoTypeError,
Msg: msg.Payload.Message,
TTL: msg.Payload.PersistTime,
})
+ a.status = s.(core.StatusCmp)
+ cmds = append(cmds, cmd)
case "info":
- a.status, cmd = a.status.Update(util.InfoMsg{
+ s, cmd := a.status.Update(util.InfoMsg{
Type: util.InfoTypeInfo,
Msg: msg.Payload.Message,
TTL: msg.Payload.PersistTime,
})
+ a.status = s.(core.StatusCmp)
+ cmds = append(cmds, cmd)
+
case "warn":
- a.status, cmd = a.status.Update(util.InfoMsg{
+ s, cmd := a.status.Update(util.InfoMsg{
Type: util.InfoTypeWarn,
Msg: msg.Payload.Message,
TTL: msg.Payload.PersistTime,
})
+ a.status = s.(core.StatusCmp)
+ cmds = append(cmds, cmd)
default:
- a.status, cmd = a.status.Update(util.InfoMsg{
+ s, cmd := a.status.Update(util.InfoMsg{
Type: util.InfoTypeInfo,
Msg: msg.Payload.Message,
TTL: msg.Payload.PersistTime,
})
+ a.status = s.(core.StatusCmp)
+ cmds = append(cmds, cmd)
}
- cmds = append(cmds, cmd)
}
case util.ClearStatusMsg:
- a.status, _ = a.status.Update(msg)
+ s, _ := a.status.Update(msg)
+ a.status = s.(core.StatusCmp)
// Permission
case pubsub.Event[permission.PermissionRequest]:
@@ -243,7 +263,16 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
a.showHelp = !a.showHelp
return a, nil
+ case key.Matches(msg, helpEsc):
+ if !a.editingMode {
+ if a.showQuit {
+ return a, nil
+ }
+ a.showHelp = !a.showHelp
+ return a, nil
+ }
}
+
}
if a.showQuit {
@@ -275,7 +304,8 @@ func (a appModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
}
}
- a.status, _ = a.status.Update(msg)
+ s, _ := a.status.Update(msg)
+ a.status = s.(core.StatusCmp)
a.pages[a.currentPage], cmd = a.pages[a.currentPage].Update(msg)
cmds = append(cmds, cmd)
return a, tea.Batch(cmds...)
@@ -326,6 +356,12 @@ func (a appModel) View() string {
)
}
+ if a.editingMode {
+ a.status.SetHelpMsg("ctrl+? help")
+ } else {
+ a.status.SetHelpMsg("? help")
+ }
+
if a.showHelp {
bindings := layout.KeyMapToSlice(keys)
if p, ok := a.pages[a.currentPage].(layout.Bindings); ok {
@@ -337,7 +373,9 @@ func (a appModel) View() string {
if a.currentPage == page.LogsPage {
bindings = append(bindings, logsKeyReturnKey)
}
-
+ if !a.editingMode {
+ bindings = append(bindings, helpEsc)
+ }
a.help.SetBindings(bindings)
overlay := a.help.View()
@@ -398,6 +436,7 @@ func New(app *app.App) tea.Model {
sessionDialog: dialog.NewSessionDialogCmp(),
permissions: dialog.NewPermissionDialogCmp(),
app: app,
+ editingMode: true,
pages: map[page.PageID]tea.Model{
page.ChatPage: page.NewChatPage(app),
page.LogsPage: page.NewLogsPage(),