summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-26 08:19:26 +0300
committerPaul Buetow <paul@buetow.org>2025-09-26 08:19:26 +0300
commit9bcccbd80d36ae678d58cd8f83c4d0c790c16b48 (patch)
treeccbfdec5119daf443332db020824bc5845bbcf78 /docs
parent439ebb14fa6fb43bfda2e0ee6811c37f96b15ecc (diff)
Auto apply inline prompt completions
Diffstat (limited to 'docs')
-rw-r--r--docs/coverage.html715
-rw-r--r--docs/coverage.out3874
2 files changed, 2461 insertions, 2128 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] = &amp;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 &lt; 0 </span><span class="cov0" title="0">{
idx = 0
}</span>
- <span class="cov4" title="6">if idx &gt;= len(d.lines) </span><span class="cov0" title="0">{
+ <span class="cov4" title="7">if idx &gt;= 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 &gt;= 0 </span><span class="cov4" title="6">{
above = d.lines[idx-1]
}</span>
- <span class="cov4" title="6">if idx+1 &lt; len(d.lines) </span><span class="cov4" title="6">{
+ <span class="cov4" title="7">if idx+1 &lt; len(d.lines) </span><span class="cov4" title="6">{
below = d.lines[idx+1]
}</span>
- <span class="cov4" title="6">for i := idx; i &gt;= 0; i-- </span><span class="cov5" title="8">{
+ <span class="cov4" title="7">for i := idx; i &gt;= 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 &lt; 0 </span><span class="cov0" title="0">{
idx = 0
}</span>
- <span class="cov5" title="11">if idx &gt;= len(d.lines) </span><span class="cov0" title="0">{
+ <span class="cov5" title="12">if idx &gt;= 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 &gt;= 0; i-- </span><span class="cov7" title="20">{
+ <span class="cov5" title="12">sigStart := -1
+ for i := idx; i &gt;= 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) &gt; 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 &gt;= 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 &gt; 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 &lt; 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) &gt; 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, &amp;p); err == nil </span><span class="cov1" title="1">{
+ if err := json.Unmarshal(req.Params, &amp;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, &amp;ctx)
- }</span> else<span class="cov1" title="1"> {
+ }</span> else<span class="cov2" title="2"> {
b, _ := json.Marshal(p.Context)
_ = json.Unmarshal(b, &amp;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 &amp;&amp; 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 &amp;&amp; !s.prefixHeuristicAllows(plan.inlinePrompt, current, p, plan.manualInvoke) </span><span class="cov0" title="0">{
+ <span class="cov6" title="10">if !plan.inParams &amp;&amp; !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, &amp;c)
- }</span> else<span class="cov1" title="1"> {
+ }</span> else<span class="cov2" title="2"> {
b, _ := json.Marshal(ctx)
_ = json.Unmarshal(b, &amp;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 "&gt;" 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) &lt; 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 &gt; len(current) </span><span class="cov0" title="0">{
idx = len(current)
}</span>
- <span class="cov7" title="14">allowNoPrefix := inlinePrompt
- if idx &gt; 0 </span><span class="cov7" title="12">{
+ <span class="cov7" title="15">allowNoPrefix := inlinePrompt
+ if idx &gt; 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 &lt;= 0 </span><span class="cov9" title="39">{
+ if d &lt;= 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 &lt;= 0 </span><span class="cov9" title="38">{
+ if interval &lt;= 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 &amp;&amp; strings.TrimSpace(cfg.PromptCompletionSystemInline) != "" </span><span class="cov2" title="2">{
+ <span class="cov7" title="15">if inlinePrompt &amp;&amp; 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 &amp;&amp; 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 != "" &amp;&amp; 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 != "" &amp;&amp; hasDoubleOpenTrigger(currentLine, openChar, closeChar) </span><span class="cov1" title="1">{
+ <span class="cov6" title="12">_, _, openChar, closeChar := s.inlineMarkers()
+ if cleaned != "" &amp;&amp; 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
+