summaryrefslogtreecommitdiff
path: root/internal/lsp
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-08 09:50:38 +0300
committerPaul Buetow <paul@buetow.org>2025-09-08 09:50:38 +0300
commitcead3ebde8f3aee0ef8677158d37f4d04c6629dc (patch)
treeeadf4928c13e4f1fd782e8e0955116a24cef1d27 /internal/lsp
parent29b0da31acf02816ee9e8f1d5a1b9a0ad5993593 (diff)
tmux: colored LLM status with provider + stats; add start heartbeat for LSP/CLI/TUI; theme support via HEXAI_TMUX_STATUS_THEME and HEXAI_TMUX_STATUS_FG/BG; docs: update tmux options and add Helix+tmux quickstart
Diffstat (limited to 'internal/lsp')
-rw-r--r--internal/lsp/handlers_init.go5
-rw-r--r--internal/lsp/handlers_utils.go50
2 files changed, 44 insertions, 11 deletions
diff --git a/internal/lsp/handlers_init.go b/internal/lsp/handlers_init.go
index ac1d566..ba00333 100644
--- a/internal/lsp/handlers_init.go
+++ b/internal/lsp/handlers_init.go
@@ -6,6 +6,7 @@ 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) {
@@ -29,6 +30,10 @@ func (s *Server) handleInitialize(req Request) {
func (s *Server) handleInitialized() {
logging.Logf("lsp ", "client initialized")
+ // Emit an initial tmux heartbeat with provider/model
+ if s.llmClient != nil {
+ _ = tmx.SetStatus(tmx.FormatLLMStartStatus(s.llmClient.Name(), s.llmClient.DefaultModel()))
+ }
}
func (s *Server) handleShutdown(req Request) {
diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go
index 7f116cd..15f0174 100644
--- a/internal/lsp/handlers_utils.go
+++ b/internal/lsp/handlers_utils.go
@@ -2,13 +2,14 @@
package lsp
import (
- "strings"
- "time"
+ "context"
+ "strings"
+ "time"
- "codeberg.org/snonux/hexai/internal/llm"
- "codeberg.org/snonux/hexai/internal/logging"
- "codeberg.org/snonux/hexai/internal/textutil"
- tmx "codeberg.org/snonux/hexai/internal/tmux"
+ "codeberg.org/snonux/hexai/internal/llm"
+ "codeberg.org/snonux/hexai/internal/logging"
+ "codeberg.org/snonux/hexai/internal/textutil"
+ tmx "codeberg.org/snonux/hexai/internal/tmux"
)
// Configurable inline trigger characters (default to '>') used by free helpers below.
@@ -61,11 +62,14 @@ func (s *Server) logLLMStats() {
rpm := float64(reqs) / mins
sentPerMin := float64(sentTot) / mins
recvPerMin := float64(recvTot) / mins
- logging.Logf("lsp ", "llm stats reqs=%d avg_sent=%d avg_recv=%d sent_total=%d recv_total=%d rpm=%.2f sent_per_min=%.0f recv_per_min=%.0f", reqs, avgSent, avgRecv, sentTot, recvTot, rpm, sentPerMin, recvPerMin)
- // Best-effort tmux status update
- if s.llmClient != nil {
- _ = tmx.SetStatus("LLM:" + s.llmClient.DefaultModel())
- }
+ logging.Logf("lsp ", "llm stats reqs=%d avg_sent=%d avg_recv=%d sent_total=%d recv_total=%d rpm=%.2f sent_per_min=%.0f recv_per_min=%.0f", reqs, avgSent, avgRecv, sentTot, recvTot, rpm, sentPerMin, recvPerMin)
+ // Best-effort tmux status update with a compact stats heartbeat
+ if s.llmClient != nil {
+ model := s.llmClient.DefaultModel()
+ provider := s.llmClient.Name()
+ status := tmx.FormatLLMStatsStatusColored(provider, model, reqs, rpm, sentTot, recvTot)
+ _ = tmx.SetStatus(status)
+ }
}
// Completion prompt builders and filters
@@ -127,6 +131,30 @@ func isIdentChar(ch byte) bool {
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch == '_'
}
+// chatWithStats wraps llmClient.Chat to increment counters and emit a tmux heartbeat.
+func (s *Server) chatWithStats(ctx context.Context, msgs []llm.Message, opts ...llm.RequestOption) (string, error) {
+ // Count bytes sent
+ sent := 0
+ for _, m := range msgs {
+ sent += len(m.Content)
+ }
+ s.incSentCounters(sent)
+ // Debounce/throttle if configured (reuse completion gates)
+ s.waitForDebounce(ctx)
+ if !s.waitForThrottle(ctx) {
+ return "", context.Canceled
+ }
+ // Perform request
+ txt, err := s.llmClient.Chat(ctx, msgs, opts...)
+ if err != nil {
+ s.logLLMStats()
+ return "", err
+ }
+ s.incRecvCounters(len(txt))
+ s.logLLMStats()
+ return txt, nil
+}
+
// Inline prompt utilities
func lineHasInlinePrompt(line string) bool {
if _, _, _, ok := findStrictInlineTag(line); ok {