summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-02 14:43:42 +0200
committerPaul Buetow <paul@buetow.org>2026-03-02 14:43:42 +0200
commitbd1e20279725c03c0d7244d1bb4425fe07dac787 (patch)
tree29c5ca759542c9ec8a74792e8a9ffe4d52669d31
parent36c332b4a3deef68c93232416c68ec2b74f108fb (diff)
lsp: inject StatusSink to decouple core from tmux package (task 407)
-rw-r--r--internal/hexailsp/run.go13
-rw-r--r--internal/lsp/handlers_init.go3
-rw-r--r--internal/lsp/handlers_utils.go4
-rw-r--r--internal/lsp/server.go23
4 files changed, 38 insertions, 5 deletions
diff --git a/internal/hexailsp/run.go b/internal/hexailsp/run.go
index 09889ec..ca24016 100644
--- a/internal/hexailsp/run.go
+++ b/internal/hexailsp/run.go
@@ -18,6 +18,7 @@ import (
"codeberg.org/snonux/hexai/internal/lsp"
"codeberg.org/snonux/hexai/internal/runtimeconfig"
"codeberg.org/snonux/hexai/internal/stats"
+ tmx "codeberg.org/snonux/hexai/internal/tmux"
)
// ServerRunner is the minimal interface satisfied by lsp.Server.
@@ -29,6 +30,17 @@ type ConfigurableServerRunner interface {
ApplyOptions(lsp.ServerOptions)
}
+type tmuxStatusSink struct{}
+
+func (tmuxStatusSink) SetLLMStart(provider, model string) error {
+ return tmx.SetStatus(tmx.FormatLLMStartStatus(provider, model))
+}
+
+func (tmuxStatusSink) SetGlobal(reqs int64, rpm float64, sent int64, recv int64, provider, model string, scopeRPM float64, scopeReqs int64, window time.Duration) error {
+ status := tmx.FormatGlobalStatusColored(reqs, rpm, sent, recv, provider, model, scopeRPM, scopeReqs, window)
+ return tmx.SetStatus(status)
+}
+
// ServerFactory creates a ServerRunner. Default uses lsp.NewServer.
type ServerFactory func(r io.Reader, w io.Writer, logger *log.Logger, opts lsp.ServerOptions) ServerRunner
@@ -150,5 +162,6 @@ func makeServerOptions(cfg appconfig.App, logContext bool, client llm.Client, lo
Config: &cfg,
Client: client,
IgnoreChecker: ignoreChecker,
+ StatusSink: tmuxStatusSink{},
}
}
diff --git a/internal/lsp/handlers_init.go b/internal/lsp/handlers_init.go
index 0cecc6c..24789f7 100644
--- a/internal/lsp/handlers_init.go
+++ b/internal/lsp/handlers_init.go
@@ -4,7 +4,6 @@ package lsp
import (
"codeberg.org/snonux/hexai/internal"
"codeberg.org/snonux/hexai/internal/logging"
- tmx "codeberg.org/snonux/hexai/internal/tmux"
)
func (s *Server) handleInitialize(req Request) {
@@ -31,7 +30,7 @@ func (s *Server) handleInitialized() {
logging.Logf("lsp ", "client initialized")
// Emit an initial tmux heartbeat with provider/model
if client := s.currentLLMClient(); client != nil {
- _ = tmx.SetStatus(tmx.FormatLLMStartStatus(client.Name(), client.DefaultModel()))
+ s.emitLLMStartStatus(client.Name(), client.DefaultModel())
}
}
diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go
index adc3b7e..4a5bf4a 100644
--- a/internal/lsp/handlers_utils.go
+++ b/internal/lsp/handlers_utils.go
@@ -13,7 +13,6 @@ import (
"codeberg.org/snonux/hexai/internal/logging"
"codeberg.org/snonux/hexai/internal/stats"
"codeberg.org/snonux/hexai/internal/textutil"
- tmx "codeberg.org/snonux/hexai/internal/tmux"
)
type surfaceKind string
@@ -198,8 +197,7 @@ func (s *Server) logLLMStats(model string) {
minsWin = 0.001
}
scopeRPM := float64(scopeReqs) / minsWin
- status := tmx.FormatGlobalStatusColored(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, provider, modelName, scopeRPM, scopeReqs, snap.Window)
- _ = tmx.SetStatus(status)
+ s.emitGlobalStatus(snap.Global.Reqs, snap.RPM, snap.Global.Sent, snap.Global.Recv, provider, modelName, scopeRPM, scopeReqs, snap.Window)
}
}
}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 9516e37..b33147c 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -29,6 +29,7 @@ type Server struct {
logger *log.Logger
serverCtx context.Context
serverCancel context.CancelFunc
+ statusSink StatusSink
exited atomic.Bool
mu sync.RWMutex
docs map[string]*document
@@ -74,6 +75,12 @@ type codeActionSubsystem struct {
altClients map[string]llm.Client
}
+// StatusSink receives status updates from the LSP server.
+type StatusSink interface {
+ SetLLMStart(provider, model string) error
+ SetGlobal(reqs int64, rpm float64, sent int64, recv int64, provider, model string, scopeRPM float64, scopeReqs int64, window time.Duration) error
+}
+
// ServerOptions collects configuration for NewServer to avoid long parameter lists.
type ServerOptions struct {
LogContext bool
@@ -84,6 +91,7 @@ type ServerOptions struct {
Client llm.Client
// Gitignore-aware file checker (optional)
IgnoreChecker *ignore.Checker
+ StatusSink StatusSink
}
func NewServer(r io.Reader, w io.Writer, logger *log.Logger, opts ServerOptions) *Server {
@@ -144,6 +152,9 @@ func (s *Server) applyOptions(opts ServerOptions) {
if opts.IgnoreChecker != nil {
s.ignoreChecker = opts.IgnoreChecker
}
+ if opts.StatusSink != nil {
+ s.statusSink = opts.StatusSink
+ }
}
// ApplyOptions updates the server's configuration at runtime.
@@ -412,6 +423,18 @@ func (s *Server) cancelRequests() {
}
}
+func (s *Server) emitLLMStartStatus(provider, model string) {
+ if s.statusSink != nil {
+ _ = s.statusSink.SetLLMStart(provider, model)
+ }
+}
+
+func (s *Server) emitGlobalStatus(reqs int64, rpm float64, sent int64, recv int64, provider, model string, scopeRPM float64, scopeReqs int64, window time.Duration) {
+ if s.statusSink != nil {
+ _ = s.statusSink.SetGlobal(reqs, rpm, sent, recv, provider, model, scopeRPM, scopeReqs, window)
+ }
+}
+
func (s *Server) Run() error {
defer s.cancelRequests()
for {