summaryrefslogtreecommitdiff
path: root/internal/lsp
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-03 15:55:34 +0300
committerPaul Buetow <paul@buetow.org>2025-09-03 15:55:34 +0300
commit71f0d04bd558433cebf1b05845c9fa0e2957eba8 (patch)
tree911a3cab02ecb212cc4221bdd24b46bdbea21fb8 /internal/lsp
parentb089ce13904d225a77ed2a4825fa88366a57442c (diff)
Phase 1: remove single in-flight LLM gate\n\n- Drop llmBusy state and busy item\n- Remove concurrency guard in completion paths\n- Allow manual invoke (TriggerKind=1) even after whitespace\n- Delete llm_busy_test; update TODO\n\nAll unit tests pass.
Diffstat (limited to 'internal/lsp')
-rw-r--r--internal/lsp/handlers.go49
-rw-r--r--internal/lsp/handlers_completion.go35
-rw-r--r--internal/lsp/llm_busy_test.go32
-rw-r--r--internal/lsp/server.go7
4 files changed, 21 insertions, 102 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go
index 531b454..547be67 100644
--- a/internal/lsp/handlers.go
+++ b/internal/lsp/handlers.go
@@ -184,38 +184,7 @@ func (s *Server) reply(id json.RawMessage, result any, err *RespError) {
// busyCompletionItem builds a visible, non-inserting completion item indicating
// that an LLM request is already in flight.
-func (s *Server) busyCompletionItem() CompletionItem {
- prov := ""
- model := ""
- if s.llmClient != nil {
- prov = s.llmClient.Name()
- model = s.llmClient.DefaultModel()
- }
- label := "Hexai: LLM busy"
- if prov != "" && model != "" {
- label += " (" + prov + ":" + model + ")"
- }
- return CompletionItem{
- Label: label,
- Detail: "Another request is running; only one is allowed concurrently",
- InsertText: "",
- FilterText: "",
- SortText: "~~~~~busy", // float to top
- Documentation: "Hexai is processing a previous request. Please retry shortly.",
- }
-}
-
-func (s *Server) isLLMBusy() bool {
- s.mu.Lock()
- defer s.mu.Unlock()
- return s.llmBusy
-}
-
-func (s *Server) setLLMBusy(v bool) {
- s.mu.Lock()
- s.llmBusy = v
- s.mu.Unlock()
-}
+// removed: previous single in-flight LLM busy gate and busy item
// --- small completion cache (last ~10 entries) ---
@@ -329,14 +298,14 @@ func (s *Server) isTriggerEvent(p CompletionParams, current string) bool {
b, _ := json.Marshal(p.Context)
_ = json.Unmarshal(b, &ctx)
}
- // If the line contains a bare ';;' (no ';;text;'), do not treat as a trigger source.
- if strings.Contains(current, ";;") && !hasDoubleSemicolonTrigger(current) {
- return false
- }
- // TriggerKind 1 = Invoked (manual) — always allow (unless bare ';;' above)
- if ctx.TriggerKind == 1 {
- return true
- }
+ // If the line contains a bare ';;' (no ';;text;'), do not treat as a trigger source.
+ if strings.Contains(current, ";;") && !hasDoubleSemicolonTrigger(current) {
+ return false
+ }
+ // TriggerKind 1 = Invoked (manual). Always allow manual invoke.
+ if ctx.TriggerKind == 1 {
+ return true
+ }
// TriggerKind 2 is TriggerCharacter per LSP spec
if ctx.TriggerKind == 2 {
if ctx.TriggerCharacter != "" {
diff --git a/internal/lsp/handlers_completion.go b/internal/lsp/handlers_completion.go
index 73c903f..1c77024 100644
--- a/internal/lsp/handlers_completion.go
+++ b/internal/lsp/handlers_completion.go
@@ -70,9 +70,8 @@ func (s *Server) logCompletionContext(p CompletionParams, above, current, below,
}
func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, funcCtx, docStr string, hasExtra bool, extraText string) ([]CompletionItem, bool) {
- ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
- defer cancel()
- locked := false // track if we've taken the LLM busy lock
+ ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second)
+ defer cancel()
inlinePrompt := lineHasInlinePrompt(current)
if !inlinePrompt && !s.isTriggerEvent(p, current) {
@@ -104,10 +103,10 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
return []CompletionItem{}, true
}
- // Provider-native path
- if items, ok := s.tryProviderNativeCompletion(current, p, above, below, funcCtx, docStr, hasExtra, extraText, inParams); ok {
- return items, true
- }
+ // Provider-native path
+ if items, ok := s.tryProviderNativeCompletion(current, p, above, below, funcCtx, docStr, hasExtra, extraText, inParams); ok {
+ return items, true
+ }
// Chat path
messages := s.buildCompletionMessages(inlinePrompt, hasExtra, extraText, inParams, p, above, current, below, funcCtx)
@@ -121,16 +120,7 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
if s.codingTemperature != nil {
opts = append(opts, llm.WithTemperature(*s.codingTemperature))
}
- logging.Logf("lsp ", "completion llm=requesting model=%s", s.llmClient.DefaultModel())
-
- // Concurrency guard for chat path as well
- if !locked {
- if s.isLLMBusy() {
- return []CompletionItem{s.busyCompletionItem()}, true
- }
- s.setLLMBusy(true)
- defer s.setLLMBusy(false)
- }
+ logging.Logf("lsp ", "completion llm=requesting model=%s", s.llmClient.DefaultModel())
text, err := s.llmClient.Chat(ctx, messages, opts...)
if err != nil {
@@ -233,15 +223,10 @@ func (s *Server) tryProviderNativeCompletion(current string, p CompletionParams,
prov = s.llmClient.Name()
}
logging.Logf("lsp ", "completion path=codex provider=%s uri=%s", prov, path)
- ctx2, cancel2 := context.WithTimeout(context.Background(), 8*time.Second)
- defer cancel2()
- if s.isLLMBusy() {
- return []CompletionItem{s.busyCompletionItem()}, true
- }
- s.setLLMBusy(true)
- defer s.setLLMBusy(false)
+ ctx2, cancel2 := context.WithTimeout(context.Background(), 8*time.Second)
+ defer cancel2()
- suggestions, err := cc.CodeCompletion(ctx2, prompt, after, 1, lang, temp)
+ suggestions, err := cc.CodeCompletion(ctx2, prompt, after, 1, lang, temp)
if err == nil && len(suggestions) > 0 {
cleaned := strings.TrimSpace(suggestions[0])
if cleaned != "" {
diff --git a/internal/lsp/llm_busy_test.go b/internal/lsp/llm_busy_test.go
deleted file mode 100644
index c7cc716..0000000
--- a/internal/lsp/llm_busy_test.go
+++ /dev/null
@@ -1,32 +0,0 @@
-package lsp
-
-import (
- "encoding/json"
- "testing"
-)
-
-// Ensure a visible busy item is returned when a prior LLM request is in flight.
-func TestLLMBusy_YieldsBusyCompletionItem(t *testing.T) {
- s := &Server{maxTokens: 32, triggerChars: []string{"."}, compCache: make(map[string]string)}
- s.llmClient = &countingLLM{}
- // Mark busy
- s.setLLMBusy(true)
- t.Cleanup(func() { s.setLLMBusy(false) })
- line := "obj."
- p := CompletionParams{Position: Position{Line: 0, Character: len(line)}, TextDocument: TextDocumentIdentifier{URI: "file://busy.go"}}
- // Simulate manual invoke to bypass min-prefix
- p.Context = json.RawMessage([]byte(`{"triggerKind":1}`))
- items, ok := s.tryLLMCompletion(p, "", line, "", "", "", false, "")
- if !ok {
- t.Fatalf("expected ok=true")
- }
- if len(items) != 1 {
- t.Fatalf("expected one busy item, got %d", len(items))
- }
- if items[0].InsertText != "" {
- t.Fatalf("busy item should not insert text")
- }
- if items[0].Label == "" {
- t.Fatalf("busy item should have a label")
- }
-}
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 4b72717..2f834ba 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -44,11 +44,8 @@ type Server struct {
// Minimum identifier chars required for manual invoke to bypass prefix checks
manualInvokeMinPrefix int
- // LLM concurrency guard: allow at most one in-flight request
- llmBusy bool
-
- // Dispatch table for JSON-RPC methods → handler functions
- handlers map[string]func(Request)
+ // Dispatch table for JSON-RPC methods → handler functions
+ handlers map[string]func(Request)
}
// ServerOptions collects configuration for NewServer to avoid long parameter lists.