diff options
| author | Paul Buetow <paul@buetow.org> | 2025-09-26 08:19:26 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-09-26 08:19:26 +0300 |
| commit | 9bcccbd80d36ae678d58cd8f83c4d0c790c16b48 (patch) | |
| tree | ccbfdec5119daf443332db020824bc5845bbcf78 /docs/coverage.html | |
| parent | 439ebb14fa6fb43bfda2e0ee6811c37f96b15ecc (diff) | |
Auto apply inline prompt completions
Diffstat (limited to 'docs/coverage.html')
| -rw-r--r-- | docs/coverage.html | 715 |
1 files changed, 380 insertions, 335 deletions
diff --git a/docs/coverage.html b/docs/coverage.html index a1db0c8..7cda0d1 100644 --- a/docs/coverage.html +++ b/docs/coverage.html @@ -111,13 +111,13 @@ <option value="file27">codeberg.org/snonux/hexai/internal/lsp/handlers_completion.go (88.8%)</option> - <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (89.4%)</option> + <option value="file28">codeberg.org/snonux/hexai/internal/lsp/handlers_document.go (77.7%)</option> <option value="file29">codeberg.org/snonux/hexai/internal/lsp/handlers_execute.go (75.0%)</option> <option value="file30">codeberg.org/snonux/hexai/internal/lsp/handlers_init.go (66.7%)</option> - <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (89.9%)</option> + <option value="file31">codeberg.org/snonux/hexai/internal/lsp/handlers_utils.go (90.2%)</option> <option value="file32">codeberg.org/snonux/hexai/internal/lsp/server.go (86.8%)</option> @@ -3593,7 +3593,7 @@ type RequestOption func(*Options) func WithModel(model string) RequestOption <span class="cov1" title="1">{ return func(o *Options) </span><span class="cov1" title="1">{ o.Model = model }</span> } func WithTemperature(t float64) RequestOption <span class="cov7" title="15">{ return func(o *Options) </span><span class="cov2" title="2">{ o.Temperature = t }</span> } -func WithMaxTokens(n int) RequestOption <span class="cov10" title="53">{ return func(o *Options) </span><span class="cov2" title="2">{ o.MaxTokens = n }</span> } +func WithMaxTokens(n int) RequestOption <span class="cov10" title="54">{ return func(o *Options) </span><span class="cov2" title="2">{ o.MaxTokens = n }</span> } func WithStop(stop ...string) RequestOption <span class="cov1" title="1">{ return func(o *Options) </span><span class="cov1" title="1">{ o.Stop = append([]string{}, stop...) }</span> } @@ -3773,11 +3773,11 @@ var std *log.Logger func Bind(l *log.Logger) <span class="cov2" title="3">{ std = l }</span> // Logf prints a formatted message with a module prefix and base ANSI style. -func Logf(prefix, format string, args ...any) <span class="cov10" title="199">{ +func Logf(prefix, format string, args ...any) <span class="cov10" title="202">{ if std == nil </span><span class="cov9" title="141">{ return }</span> - <span class="cov7" title="58">msg := fmt.Sprintf(format, args...) + <span class="cov7" title="61">msg := fmt.Sprintf(format, args...) std.Print(AnsiBase + prefix + msg + AnsiReset)</span> } @@ -3868,18 +3868,18 @@ import ( // - window: include a window of lines around the cursor // - file-on-new-func: include full file only when defining a new function // - always-full: always include the full file -func (s *Server) buildAdditionalContext(newFunc bool, uri string, pos Position) (string, bool) <span class="cov10" title="13">{ +func (s *Server) buildAdditionalContext(newFunc bool, uri string, pos Position) (string, bool) <span class="cov10" title="14">{ mode := s.contextMode() switch mode </span>{ case "minimal":<span class="cov3" title="2"> return "", false</span> case "window":<span class="cov1" title="1"> return s.windowContext(uri, pos), true</span> - case "file-on-new-func":<span class="cov8" title="8"> + case "file-on-new-func":<span class="cov8" title="9"> if newFunc </span><span class="cov3" title="2">{ return s.fullFileContext(uri), true }</span> - <span class="cov7" title="6">return "", false</span> + <span class="cov7" title="7">return "", false</span> case "always-full":<span class="cov3" title="2"> return s.fullFileContext(uri), true</span> default:<span class="cov0" title="0"> @@ -3953,7 +3953,7 @@ type document struct { lines []string } -func (s *Server) setDocument(uri, text string) <span class="cov8" title="40">{ +func (s *Server) setDocument(uri, text string) <span class="cov8" title="41">{ s.mu.Lock() defer s.mu.Unlock() s.docs[uri] = &document{uri: uri, text: text, lines: splitLines(text)} @@ -3971,76 +3971,76 @@ func (s *Server) markActivity() <span class="cov3" title="4">{ s.mu.Unlock() }</span> -func (s *Server) getDocument(uri string) *document <span class="cov10" title="87">{ +func (s *Server) getDocument(uri string) *document <span class="cov10" title="90">{ s.mu.RLock() defer s.mu.RUnlock() return s.docs[uri] }</span> // splitLines splits the input string into lines, normalizing line endings to '\n'. -func splitLines(sx string) []string <span class="cov8" title="52">{ +func splitLines(sx string) []string <span class="cov8" title="53">{ sx = strings.ReplaceAll(sx, "\r\n", "\n") return strings.Split(sx, "\n") }</span> -func (s *Server) lineContext(uri string, pos Position) (above, current, below, funcCtx string) <span class="cov4" title="7">{ +func (s *Server) lineContext(uri string, pos Position) (above, current, below, funcCtx string) <span class="cov5" title="8">{ d := s.getDocument(uri) if d == nil || len(d.lines) == 0 </span><span class="cov1" title="1">{ return "", "", "", "" }</span> - <span class="cov4" title="6">idx := pos.Line + <span class="cov4" title="7">idx := pos.Line if idx < 0 </span><span class="cov0" title="0">{ idx = 0 }</span> - <span class="cov4" title="6">if idx >= len(d.lines) </span><span class="cov0" title="0">{ + <span class="cov4" title="7">if idx >= len(d.lines) </span><span class="cov0" title="0">{ idx = len(d.lines) - 1 }</span> - <span class="cov4" title="6">current = d.lines[idx] + <span class="cov4" title="7">current = d.lines[idx] if idx-1 >= 0 </span><span class="cov4" title="6">{ above = d.lines[idx-1] }</span> - <span class="cov4" title="6">if idx+1 < len(d.lines) </span><span class="cov4" title="6">{ + <span class="cov4" title="7">if idx+1 < len(d.lines) </span><span class="cov4" title="6">{ below = d.lines[idx+1] }</span> - <span class="cov4" title="6">for i := idx; i >= 0; i-- </span><span class="cov5" title="8">{ + <span class="cov4" title="7">for i := idx; i >= 0; i-- </span><span class="cov5" title="9">{ line := strings.TrimSpace(d.lines[i]) if hasAny(line, []string{"func ", "def ", "class ", "fn ", "procedure ", "sub "}) </span><span class="cov4" title="6">{ funcCtx = line break</span> } } - <span class="cov4" title="6">return above, current, below, funcCtx</span> + <span class="cov4" title="7">return above, current, below, funcCtx</span> } // isDefiningNewFunction returns true when the cursor appears to be within // a function declaration/signature and before the opening '{' of the body. // Heuristic: find nearest preceding line containing "func "; ensure no '{' // appears before the cursor across those lines. -func (s *Server) isDefiningNewFunction(uri string, pos Position) bool <span class="cov5" title="11">{ +func (s *Server) isDefiningNewFunction(uri string, pos Position) bool <span class="cov5" title="12">{ d := s.getDocument(uri) if d == nil || len(d.lines) == 0 </span><span class="cov0" title="0">{ return false }</span> - <span class="cov5" title="11">idx := pos.Line + <span class="cov5" title="12">idx := pos.Line if idx < 0 </span><span class="cov0" title="0">{ idx = 0 }</span> - <span class="cov5" title="11">if idx >= len(d.lines) </span><span class="cov0" title="0">{ + <span class="cov5" title="12">if idx >= len(d.lines) </span><span class="cov0" title="0">{ idx = len(d.lines) - 1 }</span> // Find signature start - <span class="cov5" title="11">sigStart := -1 - for i := idx; i >= 0; i-- </span><span class="cov7" title="20">{ + <span class="cov5" title="12">sigStart := -1 + for i := idx; i >= 0; i-- </span><span class="cov7" title="21">{ if strings.Contains(d.lines[i], "func ") </span><span class="cov3" title="4">{ sigStart = i break</span> } // stop if we hit a closing brace which likely ends a previous block - <span class="cov6" title="16">if strings.Contains(d.lines[i], "}") </span><span class="cov0" title="0">{ + <span class="cov6" title="17">if strings.Contains(d.lines[i], "}") </span><span class="cov0" title="0">{ break</span> } } - <span class="cov5" title="11">if sigStart == -1 </span><span class="cov4" title="7">{ + <span class="cov5" title="12">if sigStart == -1 </span><span class="cov5" title="8">{ return false }</span> // Scan for '{' from sigStart up to cursor position; if found before or at cursor, we're in body @@ -4060,29 +4060,29 @@ func (s *Server) isDefiningNewFunction(uri string, pos Position) bool <span clas <span class="cov2" title="2">return true</span> } -func hasAny(s string, needles []string) bool <span class="cov5" title="8">{ - for _, n := range needles </span><span class="cov6" title="18">{ +func hasAny(s string, needles []string) bool <span class="cov5" title="9">{ + for _, n := range needles </span><span class="cov7" title="24">{ if strings.Contains(s, n) </span><span class="cov4" title="6">{ return true }</span> } - <span class="cov2" title="2">return false</span> + <span class="cov3" title="3">return false</span> } -func trimLen(s string) string <span class="cov8" title="42">{ +func trimLen(s string) string <span class="cov8" title="47">{ s = strings.TrimSpace(s) if len(s) > 200 </span><span class="cov1" title="1">{ return s[:200] + "…" }</span> - <span class="cov8" title="41">return s</span> + <span class="cov8" title="46">return s</span> } -func firstLine(s string) string <span class="cov7" title="26">{ +func firstLine(s string) string <span class="cov7" title="27">{ s = strings.ReplaceAll(s, "\r\n", "\n") if idx := strings.IndexByte(s, '\n'); idx >= 0 </span><span class="cov4" title="6">{ return s[:idx] }</span> - <span class="cov7" title="20">return s</span> + <span class="cov7" title="21">return s</span> } </pre> @@ -4203,7 +4203,7 @@ func (s *Server) findFirstInstructionInLine(line string) (instr string, cleaned // handleCompletion moved to handlers_completion.go -func (s *Server) reply(id json.RawMessage, result any, err *RespError) <span class="cov10" title="29">{ +func (s *Server) reply(id json.RawMessage, result any, err *RespError) <span class="cov10" title="30">{ resp := Response{JSONRPC: "2.0", ID: id, Result: result, Error: err} s.writeMessage(resp) }</span> @@ -4277,33 +4277,33 @@ func (s *Server) reply(id json.RawMessage, result any, err *RespError) <span cla // --- small completion cache (last ~10 entries) --- -func (s *Server) completionCacheKey(p CompletionParams, above, current, below, funcCtx string, inParams bool, hasExtra bool, extraText string) string <span class="cov8" title="14">{ +func (s *Server) completionCacheKey(p CompletionParams, above, current, below, funcCtx string, inParams bool, hasExtra bool, extraText string) string <span class="cov8" title="15">{ // Normalize left-of-cursor by trimming trailing spaces/tabs idx := p.Position.Character if idx > len(current) </span><span class="cov0" title="0">{ idx = len(current) }</span> - <span class="cov8" title="14">left := strings.TrimRight(current[:idx], " \t") + <span class="cov8" title="15">left := strings.TrimRight(current[:idx], " \t") right := "" if idx < len(current) </span><span class="cov1" title="1">{ right = current[idx:] }</span> - <span class="cov8" title="14">prov := "" + <span class="cov8" title="15">prov := "" model := "" - if client := s.currentLLMClient(); client != nil </span><span class="cov8" title="14">{ + if client := s.currentLLMClient(); client != nil </span><span class="cov8" title="15">{ prov = client.Name() model = client.DefaultModel() }</span> - <span class="cov8" title="14">temp := "" + <span class="cov8" title="15">temp := "" if tempPtr := s.codingTemperature(); tempPtr != nil </span><span class="cov0" title="0">{ temp = fmt.Sprintf("%.3f", *tempPtr) }</span> - <span class="cov8" title="14">extra := "" + <span class="cov8" title="15">extra := "" if hasExtra </span><span class="cov0" title="0">{ extra = strings.TrimSpace(extraText) }</span> // Compose a key from essential context parts - <span class="cov8" title="14">return strings.Join([]string{ + <span class="cov8" title="15">return strings.Join([]string{ "v1", // version for future-proofing prov, model, @@ -4320,11 +4320,11 @@ func (s *Server) completionCacheKey(p CompletionParams, above, current, below, f }, "\x1f")</span> // use unit separator to avoid collisions } -func (s *Server) completionCacheGet(key string) (string, bool) <span class="cov7" title="10">{ +func (s *Server) completionCacheGet(key string) (string, bool) <span class="cov7" title="11">{ s.mu.Lock() defer s.mu.Unlock() v, ok := s.compCache[key] - if !ok </span><span class="cov6" title="9">{ + if !ok </span><span class="cov7" title="10">{ return "", false }</span> // move to most-recent @@ -4332,13 +4332,13 @@ func (s *Server) completionCacheGet(key string) (string, bool) <span class="cov7 return v, true</span> } -func (s *Server) completionCachePut(key, value string) <span class="cov7" title="12">{ +func (s *Server) completionCachePut(key, value string) <span class="cov7" title="13">{ s.mu.Lock() defer s.mu.Unlock() if s.compCache == nil </span><span class="cov5" title="5">{ s.compCache = make(map[string]string) }</span> - <span class="cov7" title="12">if _, exists := s.compCache[key]; !exists </span><span class="cov7" title="12">{ + <span class="cov7" title="13">if _, exists := s.compCache[key]; !exists </span><span class="cov7" title="13">{ s.compCacheOrder = append(s.compCacheOrder, key) s.compCache[key] = value if len(s.compCacheOrder) > 10 </span><span class="cov0" title="0">{ @@ -4347,7 +4347,7 @@ func (s *Server) completionCachePut(key, value string) <span class="cov7" title= s.compCacheOrder = s.compCacheOrder[1:] delete(s.compCache, old) }</span> - <span class="cov7" title="12">return</span> + <span class="cov7" title="13">return</span> } // update existing and mark most-recent <span class="cov0" title="0">s.compCache[key] = value @@ -4431,15 +4431,15 @@ func (s *Server) isTriggerEvent(p CompletionParams, current string) bool <span c <span class="cov5" title="6">return false</span> } -func (s *Server) makeCompletionItems(cleaned string, inParams bool, current string, p CompletionParams, docStr string) []CompletionItem <span class="cov7" title="13">{ +func (s *Server) makeCompletionItems(cleaned string, inParams bool, current string, p CompletionParams, docStr string) []CompletionItem <span class="cov7" title="14">{ te, filter := computeTextEditAndFilter(cleaned, inParams, current, p) rm := s.collectPromptRemovalEdits(p.TextDocument.URI) label := labelForCompletion(cleaned, filter) detail := "Hexai LLM completion" - if client := s.currentLLMClient(); client != nil </span><span class="cov7" title="13">{ + if client := s.currentLLMClient(); client != nil </span><span class="cov7" title="14">{ detail = "Hexai " + client.Name() + ":" + client.DefaultModel() }</span> - <span class="cov7" title="13">return []CompletionItem{{ + <span class="cov7" title="14">return []CompletionItem{{ Label: label, Kind: 1, Detail: detail, @@ -5183,10 +5183,10 @@ type completionPlan struct { cacheKey string } -func (s *Server) handleCompletion(req Request) <span class="cov1" title="1">{ +func (s *Server) handleCompletion(req Request) <span class="cov2" title="2">{ var p CompletionParams var docStr string - if err := json.Unmarshal(req.Params, &p); err == nil </span><span class="cov1" title="1">{ + if err := json.Unmarshal(req.Params, &p); err == nil </span><span class="cov2" title="2">{ // Log trigger information for every completion request from client tk, tch := extractTriggerInfo(p) logging.Logf("lsp ", "completion trigger kind=%d char=%q uri=%s line=%d char=%d", @@ -5196,11 +5196,11 @@ func (s *Server) handleCompletion(req Request) <span class="cov1" title="1">{ if s.logContext </span><span class="cov0" title="0">{ s.logCompletionContext(p, above, current, below, funcCtx) }</span> - <span class="cov1" title="1">if s.llmClient != nil </span><span class="cov1" title="1">{ + <span class="cov2" title="2">if s.llmClient != nil </span><span class="cov2" title="2">{ newFunc := s.isDefiningNewFunction(p.TextDocument.URI, p.Position) extra, has := s.buildAdditionalContext(newFunc, p.TextDocument.URI, p.Position) items, ok := s.tryLLMCompletion(p, above, current, below, funcCtx, docStr, has, extra) - if ok </span><span class="cov1" title="1">{ + if ok </span><span class="cov2" title="2">{ s.reply(req.ID, CompletionList{IsIncomplete: false, Items: items}, nil) return }</span> @@ -5212,26 +5212,26 @@ func (s *Server) handleCompletion(req Request) <span class="cov1" title="1">{ // extractTriggerInfo returns the LSP completion TriggerKind and TriggerCharacter // if provided by the client; when absent it returns zeros. -func extractTriggerInfo(p CompletionParams) (kind int, ch string) <span class="cov2" title="2">{ +func extractTriggerInfo(p CompletionParams) (kind int, ch string) <span class="cov3" title="3">{ if p.Context == nil </span><span class="cov0" title="0">{ return 0, "" }</span> - <span class="cov2" title="2">var ctx struct { + <span class="cov3" title="3">var ctx struct { TriggerKind int `json:"triggerKind"` TriggerCharacter string `json:"triggerCharacter,omitempty"` } if raw, ok := p.Context.(json.RawMessage); ok </span><span class="cov1" title="1">{ _ = json.Unmarshal(raw, &ctx) - }</span> else<span class="cov1" title="1"> { + }</span> else<span class="cov2" title="2"> { b, _ := json.Marshal(p.Context) _ = json.Unmarshal(b, &ctx) }</span> - <span class="cov2" title="2">return ctx.TriggerKind, ctx.TriggerCharacter</span> + <span class="cov3" title="3">return ctx.TriggerKind, ctx.TriggerCharacter</span> } // --- completion helpers --- -func (s *Server) buildDocString(p CompletionParams, above, current, below, funcCtx string) string <span class="cov2" title="2">{ +func (s *Server) buildDocString(p CompletionParams, above, current, below, funcCtx string) string <span class="cov3" title="3">{ return fmt.Sprintf("file: %s\nline: %d\nabove: %s\ncurrent: %s\nbelow: %s\nfunction: %s", p.TextDocument.URI, p.Position.Line, trimLen(above), trimLen(current), trimLen(below), trimLen(funcCtx)) }</span> @@ -5241,7 +5241,7 @@ func (s *Server) logCompletionContext(p CompletionParams, above, current, below, p.TextDocument.URI, p.Position.Line, p.Position.Character, trimLen(above), trimLen(current), trimLen(below), trimLen(funcCtx)) }</span> -func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, funcCtx, docStr string, hasExtra bool, extraText string) ([]CompletionItem, bool) <span class="cov8" title="18">{ +func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, funcCtx, docStr string, hasExtra bool, extraText string) ([]CompletionItem, bool) <span class="cov8" title="19">{ ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) defer cancel() @@ -5250,14 +5250,14 @@ func (s *Server) tryLLMCompletion(p CompletionParams, above, current, below, fun return items, true }</span> - <span class="cov6" title="9">if items, ok := s.tryProviderNativeCompletion(current, p, above, below, funcCtx, docStr, hasExtra, extraText, plan.inParams); ok </span><span class="cov1" title="1">{ + <span class="cov6" title="10">if items, ok := s.tryProviderNativeCompletion(current, p, above, below, funcCtx, docStr, hasExtra, extraText, plan.inParams); ok </span><span class="cov1" title="1">{ return items, true }</span> - <span class="cov6" title="8">return s.executeChatCompletion(ctx, plan)</span> + <span class="cov6" title="9">return s.executeChatCompletion(ctx, plan)</span> } -func (s *Server) prepareCompletionPlan(p CompletionParams, above, current, below, funcCtx, docStr string, hasExtra bool, extraText string) (completionPlan, []CompletionItem, bool) <span class="cov8" title="18">{ +func (s *Server) prepareCompletionPlan(p CompletionParams, above, current, below, funcCtx, docStr string, hasExtra bool, extraText string) (completionPlan, []CompletionItem, bool) <span class="cov8" title="19">{ plan := completionPlan{ params: p, above: above, @@ -5274,10 +5274,10 @@ func (s *Server) prepareCompletionPlan(p CompletionParams, above, current, below logging.Logf("lsp ", "%scompletion skip=no-trigger line=%d char=%d current=%q%s", logging.AnsiYellow, p.Position.Line, p.Position.Character, trimLen(current), logging.AnsiBase) return plan, []CompletionItem{}, true }</span> - <span class="cov6" title="10">if s.shouldSuppressForChatTriggerEOL(current, p) </span><span class="cov0" title="0">{ + <span class="cov6" title="11">if s.shouldSuppressForChatTriggerEOL(current, p) </span><span class="cov0" title="0">{ return plan, []CompletionItem{}, true }</span> - <span class="cov6" title="10">plan.inParams = inParamList(current, p.Position.Character) + <span class="cov6" title="11">plan.inParams = inParamList(current, p.Position.Character) plan.manualInvoke = parseManualInvoke(p.Context) plan.cacheKey = s.completionCacheKey(p, above, current, below, funcCtx, plan.inParams, hasExtra, extraText) if cleaned, ok := s.completionCacheGet(plan.cacheKey); ok && strings.TrimSpace(cleaned) != "" </span><span class="cov1" title="1">{ @@ -5286,107 +5286,107 @@ func (s *Server) prepareCompletionPlan(p CompletionParams, above, current, below logging.AnsiGreen, logging.PreviewForLog(cleaned), logging.AnsiBase) return plan, s.makeCompletionItems(cleaned, plan.inParams, current, p, docStr), true }</span> - <span class="cov6" title="9">if isBareDoubleOpen(current, openChar, closeChar) || isBareDoubleOpen(below, openChar, closeChar) </span><span class="cov0" title="0">{ + <span class="cov6" title="10">if isBareDoubleOpen(current, openChar, closeChar) || isBareDoubleOpen(below, openChar, closeChar) </span><span class="cov0" title="0">{ logging.Logf("lsp ", "%scompletion skip=empty-double-semicolon line=%d char=%d current=%q%s", logging.AnsiYellow, p.Position.Line, p.Position.Character, trimLen(current), logging.AnsiBase) return plan, []CompletionItem{}, true }</span> - <span class="cov6" title="9">if !plan.inParams && !s.prefixHeuristicAllows(plan.inlinePrompt, current, p, plan.manualInvoke) </span><span class="cov0" title="0">{ + <span class="cov6" title="10">if !plan.inParams && !s.prefixHeuristicAllows(plan.inlinePrompt, current, p, plan.manualInvoke) </span><span class="cov0" title="0">{ logging.Logf("lsp ", "%scompletion skip=short-prefix line=%d char=%d current=%q%s", logging.AnsiYellow, p.Position.Line, p.Position.Character, trimLen(current), logging.AnsiBase) return plan, []CompletionItem{}, true }</span> - <span class="cov6" title="9">return plan, nil, false</span> + <span class="cov6" title="10">return plan, nil, false</span> } -func (s *Server) executeChatCompletion(ctx context.Context, plan completionPlan) ([]CompletionItem, bool) <span class="cov6" title="8">{ +func (s *Server) executeChatCompletion(ctx context.Context, plan completionPlan) ([]CompletionItem, bool) <span class="cov6" title="9">{ messages := s.buildCompletionMessages(plan.inlinePrompt, plan.hasExtra, plan.extraText, plan.inParams, plan.params, plan.above, plan.current, plan.below, plan.funcCtx) sentSize := 0 - for _, m := range messages </span><span class="cov7" title="16">{ + for _, m := range messages </span><span class="cov7" title="18">{ sentSize += len(m.Content) }</span> - <span class="cov6" title="8">s.incSentCounters(sentSize) + <span class="cov6" title="9">s.incSentCounters(sentSize) opts := s.llmRequestOpts() s.waitForDebounce(ctx) if !s.waitForThrottle(ctx) </span><span class="cov0" title="0">{ return nil, false }</span> - <span class="cov6" title="8">client := s.currentLLMClient() + <span class="cov6" title="9">client := s.currentLLMClient() if client == nil </span><span class="cov0" title="0">{ return nil, false }</span> - <span class="cov6" title="8">logging.Logf("lsp ", "completion llm=requesting model=%s", client.DefaultModel()) + <span class="cov6" title="9">logging.Logf("lsp ", "completion llm=requesting model=%s", client.DefaultModel()) text, err := client.Chat(ctx, messages, opts...) if err != nil </span><span class="cov0" title="0">{ logging.Logf("lsp ", "llm completion error: %v", err) s.logLLMStats() return nil, false }</span> - <span class="cov6" title="8">s.incRecvCounters(len(text)) + <span class="cov6" title="9">s.incRecvCounters(len(text)) s.logLLMStats() trimmed := strings.TrimSpace(text) cleaned := s.postProcessCompletion(trimmed, plan.current[:plan.params.Position.Character], plan.current) if cleaned == "" </span><span class="cov0" title="0">{ return nil, false }</span> - <span class="cov6" title="8">s.completionCachePut(plan.cacheKey, cleaned) + <span class="cov6" title="9">s.completionCachePut(plan.cacheKey, cleaned) items := s.makeCompletionItems(cleaned, plan.inParams, plan.current, plan.params, plan.docStr) return items, true</span> } // parseManualInvoke inspects the LSP completion context and reports whether the user manually invoked completion. -func parseManualInvoke(ctx any) bool <span class="cov6" title="11">{ +func parseManualInvoke(ctx any) bool <span class="cov6" title="12">{ if ctx == nil </span><span class="cov4" title="5">{ return false }</span> - <span class="cov5" title="6">var c struct { + <span class="cov5" title="7">var c struct { TriggerKind int `json:"triggerKind"` } if raw, ok := ctx.(json.RawMessage); ok </span><span class="cov4" title="5">{ _ = json.Unmarshal(raw, &c) - }</span> else<span class="cov1" title="1"> { + }</span> else<span class="cov2" title="2"> { b, _ := json.Marshal(ctx) _ = json.Unmarshal(b, &c) }</span> - <span class="cov5" title="6">return c.TriggerKind == 1</span> + <span class="cov5" title="7">return c.TriggerKind == 1</span> } // shouldSuppressForChatTriggerEOL returns true when a chat trigger like ">" follows ?, !, :, or ; at EOL. -func (s *Server) shouldSuppressForChatTriggerEOL(current string, p CompletionParams) bool <span class="cov7" title="15">{ +func (s *Server) shouldSuppressForChatTriggerEOL(current string, p CompletionParams) bool <span class="cov7" title="16">{ t := strings.TrimRight(current, " \t") suffix, prefixes, _ := s.chatConfig() if suffix == "" </span><span class="cov1" title="1">{ return false }</span> - <span class="cov7" title="14">if strings.HasSuffix(t, suffix) </span><span class="cov4" title="4">{ + <span class="cov7" title="15">if strings.HasSuffix(t, suffix) </span><span class="cov4" title="5">{ if len(t) < len(suffix)+1 </span><span class="cov0" title="0">{ return false }</span> - <span class="cov4" title="4">prev := string(t[len(t)-len(suffix)-1]) - for _, pf := range prefixes </span><span class="cov6" title="10">{ + <span class="cov4" title="5">prev := string(t[len(t)-len(suffix)-1]) + for _, pf := range prefixes </span><span class="cov7" title="14">{ if prev == pf </span><span class="cov2" title="2">{ logging.Logf("lsp ", "completion skip=chat-trigger-eol uri=%s line=%d", p.TextDocument.URI, p.Position.Line) return true }</span> } } - <span class="cov7" title="12">return false</span> + <span class="cov7" title="13">return false</span> } // prefixHeuristicAllows applies minimal prefix rules unless inlinePrompt or structural triggers apply. -func (s *Server) prefixHeuristicAllows(inlinePrompt bool, current string, p CompletionParams, manualInvoke bool) bool <span class="cov7" title="14">{ +func (s *Server) prefixHeuristicAllows(inlinePrompt bool, current string, p CompletionParams, manualInvoke bool) bool <span class="cov7" title="15">{ // Determine the effective cursor index within current line, clamped, and // skip over trailing spaces/tabs to support cases like "type Matrix| ". idx := p.Position.Character if idx > len(current) </span><span class="cov0" title="0">{ idx = len(current) }</span> - <span class="cov7" title="14">allowNoPrefix := inlinePrompt - if idx > 0 </span><span class="cov7" title="12">{ + <span class="cov7" title="15">allowNoPrefix := inlinePrompt + if idx > 0 </span><span class="cov7" title="13">{ ch := current[idx-1] if ch == '.' || ch == ':' || ch == '/' || ch == '_' || ch == ')' </span><span class="cov4" title="5">{ allowNoPrefix = true }</span> } - <span class="cov7" title="14">if allowNoPrefix </span><span class="cov5" title="7">{ + <span class="cov7" title="15">if allowNoPrefix </span><span class="cov6" title="8">{ return true }</span> // Walk left over whitespace @@ -5410,10 +5410,10 @@ func (s *Server) prefixHeuristicAllows(inlinePrompt bool, current string, p Comp } // tryProviderNativeCompletion attempts provider-native completion and returns items when successful. -func (s *Server) tryProviderNativeCompletion(current string, p CompletionParams, above, below, funcCtx, docStr string, hasExtra bool, extraText string, inParams bool) ([]CompletionItem, bool) <span class="cov7" title="12">{ +func (s *Server) tryProviderNativeCompletion(current string, p CompletionParams, above, below, funcCtx, docStr string, hasExtra bool, extraText string, inParams bool) ([]CompletionItem, bool) <span class="cov7" title="13">{ client := s.currentLLMClient() cc, ok := client.(llm.CodeCompleter) - if !ok </span><span class="cov5" title="6">{ + if !ok </span><span class="cov5" title="7">{ return nil, false }</span> <span class="cov5" title="6">before, after := s.docBeforeAfter(p.TextDocument.URI, p.Position) @@ -5484,9 +5484,9 @@ func (s *Server) tryProviderNativeCompletion(current string, p CompletionParams, // waitForDebounce sleeps until there has been no input activity for at least // completionDebounce. If debounce is zero or ctx is done, it returns promptly. -func (s *Server) waitForDebounce(ctx context.Context) <span class="cov10" title="41">{ +func (s *Server) waitForDebounce(ctx context.Context) <span class="cov10" title="42">{ d := s.completionDebounce() - if d <= 0 </span><span class="cov9" title="39">{ + if d <= 0 </span><span class="cov9" title="40">{ return }</span> <span class="cov2" title="2">for </span><span class="cov4" title="4">{ @@ -5514,9 +5514,9 @@ func (s *Server) waitForDebounce(ctx context.Context) <span class="cov10" title= // waitForThrottle enforces a minimum spacing between LLM calls. Returns false // if the context is canceled while waiting. -func (s *Server) waitForThrottle(ctx context.Context) bool <span class="cov10" title="41">{ +func (s *Server) waitForThrottle(ctx context.Context) bool <span class="cov10" title="42">{ interval := s.completionThrottle() - if interval <= 0 </span><span class="cov9" title="38">{ + if interval <= 0 </span><span class="cov9" title="39">{ return true }</span> <span class="cov3" title="3">var wait time.Duration @@ -5545,7 +5545,7 @@ func (s *Server) waitForThrottle(ctx context.Context) bool <span class="cov10" t } // buildCompletionMessages constructs the LLM messages for completion. -func (s *Server) buildCompletionMessages(inlinePrompt, hasExtra bool, extraText string, inParams bool, p CompletionParams, above, current, below, funcCtx string) []llm.Message <span class="cov7" title="14">{ +func (s *Server) buildCompletionMessages(inlinePrompt, hasExtra bool, extraText string, inParams bool, p CompletionParams, above, current, below, funcCtx string) []llm.Message <span class="cov7" title="15">{ vars := map[string]string{ "file": p.TextDocument.URI, "function": funcCtx, @@ -5561,10 +5561,10 @@ func (s *Server) buildCompletionMessages(inlinePrompt, hasExtra bool, extraText sys = cfg.PromptCompletionSystemParams userTpl = cfg.PromptCompletionUserParams }</span> - <span class="cov7" title="14">if inlinePrompt && strings.TrimSpace(cfg.PromptCompletionSystemInline) != "" </span><span class="cov2" title="2">{ + <span class="cov7" title="15">if inlinePrompt && strings.TrimSpace(cfg.PromptCompletionSystemInline) != "" </span><span class="cov2" title="2">{ sys = cfg.PromptCompletionSystemInline }</span> - <span class="cov7" title="14">user := renderTemplate(userTpl, vars) + <span class="cov7" title="15">user := renderTemplate(userTpl, vars) messages := []llm.Message{{Role: "system", Content: sys}, {Role: "user", Content: user}} if hasExtra && strings.TrimSpace(extraText) != "" </span><span class="cov1" title="1">{ extra := renderTemplate(cfg.PromptCompletionExtraHeader, map[string]string{"context": extraText}) @@ -5573,30 +5573,30 @@ func (s *Server) buildCompletionMessages(inlinePrompt, hasExtra bool, extraText }</span> <span class="cov1" title="1">messages = append(messages, llm.Message{Role: "user", Content: extra})</span> } - <span class="cov7" title="14">return messages</span> + <span class="cov7" title="15">return messages</span> } // postProcessCompletion normalizes and deduplicates completion text and applies indentation rules. -func (s *Server) postProcessCompletion(text string, leftOfCursor string, currentLine string) string <span class="cov6" title="11">{ +func (s *Server) postProcessCompletion(text string, leftOfCursor string, currentLine string) string <span class="cov6" title="12">{ cleaned := stripCodeFences(text) if cleaned != "" && strings.ContainsRune(cleaned, '`') </span><span class="cov0" title="0">{ if inline := stripInlineCodeSpan(cleaned); strings.TrimSpace(inline) != "" </span><span class="cov0" title="0">{ cleaned = inline }</span> } - <span class="cov6" title="11">if cleaned != "" </span><span class="cov6" title="11">{ + <span class="cov6" title="12">if cleaned != "" </span><span class="cov6" title="12">{ cleaned = stripDuplicateAssignmentPrefix(leftOfCursor, cleaned) }</span> - <span class="cov6" title="11">if cleaned != "" </span><span class="cov6" title="11">{ + <span class="cov6" title="12">if cleaned != "" </span><span class="cov6" title="12">{ cleaned = stripDuplicateGeneralPrefix(leftOfCursor, cleaned) }</span> - <span class="cov6" title="11">_, _, openChar, closeChar := s.inlineMarkers() - if cleaned != "" && hasDoubleOpenTrigger(currentLine, openChar, closeChar) </span><span class="cov1" title="1">{ + <span class="cov6" title="12">_, _, openChar, closeChar := s.inlineMarkers() + if cleaned != "" && hasDoubleOpenTrigger(currentLine, openChar, closeChar) </span><span class="cov2" title="2">{ if indent := leadingIndent(currentLine); indent != "" </span><span class="cov1" title="1">{ cleaned = applyIndent(indent, cleaned) }</span> } - <span class="cov6" title="11">return cleaned</span> + <span class="cov6" title="12">return cleaned</span> } </pre> @@ -5696,7 +5696,11 @@ func (s *Server) detectAndHandleChat(uri string) <span class="cov7" title="11">{ _, _, openChar, closeChar := s.inlineMarkers() for i, raw := range d.lines </span><span class="cov10" title="23">{ if lineHasInlinePrompt(raw, openChar, closeChar) </span><span class="cov0" title="0">{ - continue</span> + if s.currentLLMClient() != nil </span><span class="cov0" title="0">{ + pos := Position{Line: i, Character: len(raw)} + go s.runInlinePrompt(uri, pos) + }</span> + <span class="cov0" title="0">continue</span> } // Find last non-space character index <span class="cov10" title="23">j := len(raw) - 1 @@ -5812,6 +5816,47 @@ func (s *Server) applyChatEdits(uri string, lineIdx int, lastNonSpace int, remov s.clientApplyEdit("Hexai: insert chat response", we)</span> } +func (s *Server) runInlinePrompt(uri string, pos Position) <span class="cov0" title="0">{ + if s.currentLLMClient() == nil </span><span class="cov0" title="0">{ + return + }</span> + <span class="cov0 |
