diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-19 22:11:22 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-19 22:11:22 +0300 |
| commit | 8fa3c76907c3e99e75c5828d9b5642646c81205c (patch) | |
| tree | 75cf350ab4c01000603f7843c6427bc10710c68c /internal/lsp/handlers.go | |
| parent | 9f59e7acd647f9adc0903e9c9655c04495f13a53 (diff) | |
lsp: include space in trigger characters and allow space-triggered completions\n\n- Defaults now include space (" ") in trigger list\n- Prefix heuristic treats space as structural trigger (no min-prefix required)\n- README and config example updated\n- Tests: add coverage for space trigger
Diffstat (limited to 'internal/lsp/handlers.go')
| -rw-r--r-- | internal/lsp/handlers.go | 58 |
1 files changed, 55 insertions, 3 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index b9922f9..5d2201f 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -448,8 +448,14 @@ 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() + ctx, cancel := context.WithTimeout(context.Background(), 6*time.Second) + defer cancel() + + // Only invoke LLM when triggered by one of our trigger characters. + if !s.isTriggerEvent(p, current) { + logging.Logf("lsp ", "completion skip=no-trigger line=%d char=%d current=%q", p.Position.Line, p.Position.Character, trimLen(current)) + return []CompletionItem{}, true + } inParams := inParamList(current, p.Position.Character) // Heuristic 1: Require a minimal typed identifier prefix to avoid early triggers, @@ -464,7 +470,7 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun allowNoPrefix := false if idx > 0 { ch := current[idx-1] - if ch == '.' || ch == ':' || ch == '/' || ch == '_' { + if ch == '.' || ch == ':' || ch == '/' || ch == '_' || ch == ' ' { allowNoPrefix = true } } @@ -541,6 +547,52 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun return s.makeCompletionItems(cleaned, inParams, current, p, docStr), true } +// isTriggerEvent returns true when the completion request appears to be caused +// by typing one of our configured trigger characters. It checks the LSP +// CompletionContext if provided and also falls back to inspecting the character +// immediately to the left of the cursor. +func (s *Server) isTriggerEvent(p CompletionParams, current string) bool { + // 1) Inspect LSP completion context if present + if p.Context != nil { + var ctx struct{ + TriggerKind int `json:"triggerKind"` + TriggerCharacter string `json:"triggerCharacter,omitempty"` + } + if raw, ok := p.Context.(json.RawMessage); ok { + _ = json.Unmarshal(raw, &ctx) + } else { + b, _ := json.Marshal(p.Context) + _ = json.Unmarshal(b, &ctx) + } + // TriggerKind 2 is TriggerCharacter per LSP spec + if ctx.TriggerKind == 2 { + if ctx.TriggerCharacter != "" { + for _, c := range s.triggerChars { + if c == ctx.TriggerCharacter { + return true + } + } + return false + } + // No character provided but reported as TriggerCharacter; be conservative + return false + } + // For Invoked (1) or TriggerForIncomplete (3), require manual char check below + } + // 2) Fallback: check the character immediately prior to cursor + idx := p.Position.Character + if idx <= 0 || idx > len(current) { + return false + } + ch := string(current[idx-1]) + for _, c := range s.triggerChars { + if c == ch { + return true + } + } + return false +} + func (s *Server) makeCompletionItems(cleaned string, inParams bool, current string, p CompletionParams, docStr string) []CompletionItem { te, filter := computeTextEditAndFilter(cleaned, inParams, current, p) rm := s.collectPromptRemovalEdits(p.TextDocument.URI) |
