summaryrefslogtreecommitdiff
path: root/internal/lsp/handlers.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/handlers.go')
-rw-r--r--internal/lsp/handlers.go52
1 files changed, 35 insertions, 17 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go
index 95656df..b9922f9 100644
--- a/internal/lsp/handlers.go
+++ b/internal/lsp/handlers.go
@@ -452,26 +452,42 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
defer cancel()
inParams := inParamList(current, p.Position.Character)
- // Heuristic 1: Require a minimal typed identifier prefix to avoid early triggers
+ // Heuristic 1: Require a minimal typed identifier prefix to avoid early triggers,
+ // but allow immediate completion after structural trigger chars like '.', ':', '/'.
if !inParams {
- start := computeWordStart(current, p.Position.Character)
- if p.Position.Character-start < 2 { // fewer than 2 identifier chars
- return []CompletionItem{}, true
- }
- }
- // Heuristic 2: Throttle LLM calls to avoid rapid-fire requests
- if s.minCompletionInterval > 0 {
- s.mu.Lock()
- tooSoon := time.Since(s.lastLLMCompletion) < s.minCompletionInterval
- // Preemptively update timestamp to coalesce bursts
- if !tooSoon {
- s.lastLLMCompletion = time.Now()
+ // Determine the effective cursor index within current line, clamped, and
+ // skip over trailing spaces/tabs to support cases like "type Matrix| "
+ // where the cursor is after a space following an identifier.
+ idx := p.Position.Character
+ if idx > len(current) { idx = len(current) }
+ // Structural triggers allow no prefix
+ allowNoPrefix := false
+ if idx > 0 {
+ ch := current[idx-1]
+ if ch == '.' || ch == ':' || ch == '/' || ch == '_' {
+ allowNoPrefix = true
+ }
}
- s.mu.Unlock()
- if tooSoon {
- return []CompletionItem{}, true
+ if !allowNoPrefix {
+ // Walk left over whitespace
+ j := idx
+ for j > 0 {
+ c := current[j-1]
+ if c == ' ' || c == '\t' { j--; continue }
+ break
+ }
+ start := computeWordStart(current, j)
+ if j-start < 1 { // require at least 1 identifier char
+ logging.Logf("lsp ", "completion skip=short-prefix line=%d char=%d current=%q", p.Position.Line, p.Position.Character, trimLen(current))
+ return []CompletionItem{}, true
+ }
}
}
+ // Concurrency guard: if another LLM request is running, skip this one.
+ if !s.tryStartLLM() {
+ logging.Logf("lsp ", "completion skip=busy another LLM request in flight")
+ return []CompletionItem{}, true
+ }
sysPrompt, userPrompt := buildPrompts(inParams, p, above, current, below, funcCtx)
messages := []llm.Message{
{Role: "system", Content: sysPrompt},
@@ -492,7 +508,9 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun
if s.codingTemperature != nil {
opts = append(opts, llm.WithTemperature(*s.codingTemperature))
}
- text, err := s.llmClient.Chat(ctx, messages, opts...)
+ logging.Logf("lsp ", "completion llm=requesting model=%s", s.llmClient.DefaultModel())
+ text, err := s.llmClient.Chat(ctx, messages, opts...)
+ defer s.endLLM()
if err != nil {
logging.Logf("lsp ", "llm completion error: %v", err)
// Log updated averages after this request (even if failed)