diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-23 08:33:07 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-23 08:33:07 +0200 |
| commit | 56c2c4f64ba1712f7cab28a8dc92a3c14b20eb1d (patch) | |
| tree | 4088f6ceddbfdea3877cd7c7343397a8b0e78d36 /internal/lsp/handlers.go | |
| parent | 3ea11bc5d671d962d01b57fa0fba0bda611025fe (diff) | |
refactor: split oversized functions, fix double logging, add %w wrapping
- lsp/handlers_completion.go: extract buildNativeCompletionCacheKey and
postProcessNativeCompletion; track collectFirstCompletion in inflight;
remove redundant logLLMStats("") on error path
- lsp/handlers.go: extract checkTriggerFromContext and
checkTriggerFromCursorChar; isTriggerEvent reduced from 63→10 lines
- lsp/transport.go: use %w for error wrapping in Content-Length parse
- llm/ollama.go: extract parseOllamaStream; ChatStream reduced to ~35 lines
- appconfig/config_load.go: extract decodeModelEntryFromMap; rename 'any'
to 'found'; decodeModelEntry reduced to ~18 lines
- llm/provider.go: document why providerRegistry is package-level
- integrationtests/ask_test.go: add //go:build integration; move repoRoot
init from init() to TestMain with diagnostic message
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/lsp/handlers.go')
| -rw-r--r-- | internal/lsp/handlers.go | 106 |
1 files changed, 62 insertions, 44 deletions
diff --git a/internal/lsp/handlers.go b/internal/lsp/handlers.go index 3b3f8e0..0f98715 100644 --- a/internal/lsp/handlers.go +++ b/internal/lsp/handlers.go @@ -141,61 +141,63 @@ func (s *Server) completionCacheKey(p CompletionParams, above, current, below, f }, "\x1f") // use unit separator to avoid collisions } -// 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 { - open, _, openChar, closeChar := s.inlineMarkers() - doubleSeqs := doubleOpenSequences(open, openChar, closeChar) - triggerChars := s.triggerCharacters() - // 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 { - if err := json.Unmarshal(raw, &ctx); err != nil { - logging.Logf("lsp ", "handleCompletion: unmarshal raw context: %v", err) - } - } else { - b, _ := json.Marshal(p.Context) - if err := json.Unmarshal(b, &ctx); err != nil { - logging.Logf("lsp ", "handleCompletion: unmarshal context: %v", err) - } - } - // If configured and the line contains a bare double-open marker (e.g., '>>!' with no '>>!text>'), - // do not treat as a trigger source. - if containsAny(current, doubleSeqs) && !hasDoubleOpenTrigger(current, open, openChar, closeChar) { - return false +// checkTriggerFromContext inspects the LSP CompletionContext (if present) to decide if +// the completion was triggered by one of our configured trigger characters or by a manual +// invoke. Returns (result, decided): decided=true means the caller should use result +// directly; decided=false means the context was absent or inconclusive (TriggerKind 3). +func (s *Server) checkTriggerFromContext(p CompletionParams, current string, open string, openChar, closeChar byte, doubleSeqs, triggerChars []string) (result bool, decided bool) { + if p.Context == nil { + return false, false + } + var ctx struct { + TriggerKind int `json:"triggerKind"` + TriggerCharacter string `json:"triggerCharacter,omitempty"` + } + if raw, ok := p.Context.(json.RawMessage); ok { + if err := json.Unmarshal(raw, &ctx); err != nil { + logging.Logf("lsp ", "handleCompletion: unmarshal raw context: %v", err) } - // TriggerKind 1 = Invoked (manual). Always allow manual invoke. - if ctx.TriggerKind == 1 { - return true + } else { + b, _ := json.Marshal(p.Context) + if err := json.Unmarshal(b, &ctx); err != nil { + logging.Logf("lsp ", "handleCompletion: unmarshal context: %v", err) } - // TriggerKind 2 is TriggerCharacter per LSP spec - if ctx.TriggerKind == 2 { - if ctx.TriggerCharacter != "" { - for _, c := range triggerChars { - if c == ctx.TriggerCharacter { - return true - } + } + // Bare double-open markers must not be treated as a trigger source. + if containsAny(current, doubleSeqs) && !hasDoubleOpenTrigger(current, open, openChar, closeChar) { + return false, true + } + // TriggerKind 1 = Invoked (manual). Always allow. + if ctx.TriggerKind == 1 { + return true, true + } + // TriggerKind 2 = TriggerCharacter per LSP spec. + if ctx.TriggerKind == 2 { + if ctx.TriggerCharacter != "" { + for _, c := range triggerChars { + if c == ctx.TriggerCharacter { + return true, true } - return false } - // No character provided but reported as TriggerCharacter; be conservative - return false + return false, true } - // For TriggerForIncomplete (3), require manual char check below + // No character provided but reported as TriggerCharacter; be conservative. + return false, true } - // 2) Fallback: check the character immediately prior to cursor. + // TriggerKind 3 (TriggerForIncomplete): fall through to cursor-char check. + return false, false +} + +// checkTriggerFromCursorChar is the fallback check that looks at the character +// immediately to the left of the cursor position to decide whether it matches a +// configured trigger character. +func (s *Server) checkTriggerFromCursorChar(p CompletionParams, current string, open string, openChar, closeChar byte, doubleSeqs, triggerChars []string) bool { // Convert UTF-16 offset to byte offset for correct multi-byte handling. byteIdx := utf16OffsetToByteOffset(current, p.Position.Character) if byteIdx <= 0 || byteIdx > len(current) { return false } - // Bare double-open should not trigger via fallback char either (only when configured) + // Bare double-open should not trigger via fallback char check either. if containsAny(current, doubleSeqs) && !hasDoubleOpenTrigger(current, open, openChar, closeChar) { return false } @@ -209,6 +211,22 @@ func (s *Server) isTriggerEvent(p CompletionParams, current string) bool { return false } +// 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 { + open, _, openChar, closeChar := s.inlineMarkers() + doubleSeqs := doubleOpenSequences(open, openChar, closeChar) + triggerChars := s.triggerCharacters() + // 1) Inspect LSP completion context if present. + if result, decided := s.checkTriggerFromContext(p, current, open, openChar, closeChar, doubleSeqs, triggerChars); decided { + return result + } + // 2) Fallback: check the character immediately prior to cursor. + return s.checkTriggerFromCursorChar(p, current, open, openChar, closeChar, doubleSeqs, triggerChars) +} + func (s *Server) makeCompletionItems(cleaned string, inParams bool, current string, p CompletionParams, docStr string, detail string, sortPrefix string) []CompletionItem { te, filter := computeTextEditAndFilter(cleaned, inParams, current, p) rm := s.collectPromptRemovalEdits(p.TextDocument.URI) |
