summaryrefslogtreecommitdiff
path: root/internal/lsp/handlers_completion.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-23 08:33:07 +0200
committerPaul Buetow <paul@buetow.org>2026-03-23 08:33:07 +0200
commit56c2c4f64ba1712f7cab28a8dc92a3c14b20eb1d (patch)
tree4088f6ceddbfdea3877cd7c7343397a8b0e78d36 /internal/lsp/handlers_completion.go
parent3ea11bc5d671d962d01b57fa0fba0bda611025fe (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_completion.go')
-rw-r--r--internal/lsp/handlers_completion.go67
1 files changed, 41 insertions, 26 deletions
diff --git a/internal/lsp/handlers_completion.go b/internal/lsp/handlers_completion.go
index 527d020..d6529de 100644
--- a/internal/lsp/handlers_completion.go
+++ b/internal/lsp/handlers_completion.go
@@ -321,7 +321,6 @@ func (s *Server) executeChatCompletion(ctx context.Context, plan completionPlan,
text, err := client.Chat(ctx, messages, spec.options...)
if err != nil {
logging.Logf("lsp ", "llm completion error: %v", err)
- s.logLLMStats("")
return nil, false
}
s.incRecvCounters(len(text))
@@ -426,6 +425,45 @@ func (s *Server) prefixHeuristicAllows(inlinePrompt bool, current string, p Comp
return j-start >= min
}
+// buildNativeCompletionCacheKey constructs the per-provider cache key for native completions.
+func buildNativeCompletionCacheKey(planCacheKey, provider, modelUsed string, clientName string) string {
+ providerKey := provider
+ if providerKey == "" {
+ providerKey = llmutils.CanonicalProvider(clientName)
+ }
+ return planCacheKey + "|" + providerKey + ":" + modelUsed
+}
+
+// postProcessNativeCompletion strips duplicates and applies indentation to the raw suggestion.
+// Returns the cleaned text, or an empty string when the suggestion should be discarded.
+func (s *Server) postProcessNativeCompletion(raw, current string, charOffset int) string {
+ cleaned := strings.TrimSpace(raw)
+ if cleaned == "" {
+ return ""
+ }
+ openStr, _, openChar, closeChar := s.inlineMarkers()
+ cByte := utf16OffsetToByteOffset(current, charOffset)
+ leftOfCursor := current[:cByte]
+ cleaned = stripDuplicateAssignmentPrefix(leftOfCursor, cleaned)
+ if cleaned == "" {
+ return ""
+ }
+ cleaned = stripDuplicateGeneralPrefix(leftOfCursor, cleaned)
+ if cleaned == "" {
+ return ""
+ }
+ if strings.TrimSpace(cleaned) != "" && hasDoubleOpenTrigger(current, openStr, openChar, closeChar) {
+ if indent := leadingIndent(current); indent != "" {
+ cleaned = applyIndent(indent, cleaned)
+ }
+ }
+ // Guard against all-whitespace result without stripping intentional indentation.
+ if strings.TrimSpace(cleaned) == "" {
+ return ""
+ }
+ return cleaned
+}
+
// tryProviderNativeCompletion attempts provider-native completion and returns items when successful.
func (s *Server) tryProviderNativeCompletion(ctx context.Context, plan completionPlan, spec requestSpec, client llm.Client, sortPrefix string) ([]CompletionItem, bool) {
cc, ok := client.(llm.CodeCompleter)
@@ -437,7 +475,6 @@ func (s *Server) tryProviderNativeCompletion(ctx context.Context, plan completio
before, after := s.docBeforeAfter(p.TextDocument.URI, p.Position)
path := strings.TrimPrefix(p.TextDocument.URI, "file://")
cfg := s.currentConfig()
- openStr, _, openChar, closeChar := s.inlineMarkers()
prompt := renderTemplate(cfg.PromptNativeCompletion, map[string]string{
"path": path,
"before": before,
@@ -466,34 +503,12 @@ func (s *Server) tryProviderNativeCompletion(ctx context.Context, plan completio
s.incRecvCounters(len(suggestions[0]))
_ = stats.Update(ctx2, client.Name(), modelUsed, sentBytes, len(suggestions[0]))
s.logLLMStats(modelUsed)
- cleaned := strings.TrimSpace(suggestions[0])
- if cleaned == "" {
- return nil, false
- }
- cByte := utf16OffsetToByteOffset(current, p.Position.Character)
- cleaned = stripDuplicateAssignmentPrefix(current[:cByte], cleaned)
+ cleaned := s.postProcessNativeCompletion(suggestions[0], current, p.Position.Character)
if cleaned == "" {
return nil, false
}
- cleaned = stripDuplicateGeneralPrefix(current[:cByte], cleaned)
- if cleaned == "" {
- return nil, false
- }
- if strings.TrimSpace(cleaned) != "" && hasDoubleOpenTrigger(current, openStr, openChar, closeChar) {
- indent := leadingIndent(current)
- if indent != "" {
- cleaned = applyIndent(indent, cleaned)
- }
- }
- if strings.TrimSpace(cleaned) == "" {
- return nil, false
- }
detail := fmt.Sprintf("Hexai %s:%s", client.Name(), modelUsed)
- providerKey := provider
- if providerKey == "" {
- providerKey = llmutils.CanonicalProvider(client.Name())
- }
- cacheKey := plan.cacheKey + "|" + providerKey + ":" + modelUsed
+ cacheKey := buildNativeCompletionCacheKey(plan.cacheKey, provider, modelUsed, client.Name())
s.completionCachePut(cacheKey, cleaned)
items := s.makeCompletionItems(cleaned, plan.inParams, current, p, plan.docStr, detail, sortPrefix)
return items, true