summaryrefslogtreecommitdiff
path: root/internal/lsp/handlers_utils.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/lsp/handlers_utils.go')
-rw-r--r--internal/lsp/handlers_utils.go265
1 files changed, 134 insertions, 131 deletions
diff --git a/internal/lsp/handlers_utils.go b/internal/lsp/handlers_utils.go
index e2c35e3..30a21a5 100644
--- a/internal/lsp/handlers_utils.go
+++ b/internal/lsp/handlers_utils.go
@@ -2,17 +2,20 @@
package lsp
import (
- "fmt"
- "codeberg.org/snonux/hexai/internal/llm"
- "codeberg.org/snonux/hexai/internal/logging"
- "strings"
- "time"
+ "fmt"
+ "strings"
+ "time"
+
+ "codeberg.org/snonux/hexai/internal/llm"
+ "codeberg.org/snonux/hexai/internal/logging"
)
// Configurable inline trigger characters (default to '>') used by free helpers below.
// NewServer assigns these based on ServerOptions.
-var inlineOpenChar byte = '>'
-var inlineCloseChar byte = '>'
+var (
+ inlineOpenChar byte = '>'
+ inlineCloseChar byte = '>'
+)
// llmRequestOpts builds request options from server settings.
func (s *Server) llmRequestOpts() []llm.RequestOption {
@@ -129,10 +132,10 @@ func isIdentChar(ch byte) bool {
// Inline prompt utilities
func lineHasInlinePrompt(line string) bool {
- if _, _, _, ok := findStrictInlineTag(line); ok {
- return true
- }
- return hasDoubleOpenTrigger(line)
+ if _, _, _, ok := findStrictInlineTag(line); ok {
+ return true
+ }
+ return hasDoubleOpenTrigger(line)
}
func leadingIndent(line string) string {
@@ -173,60 +176,60 @@ func applyIndent(indent, suggestion string) string {
// opening marker and no space immediately before the closing marker. Returns the
// text between markers, the start index, the end index just after closing, and ok.
func findStrictInlineTag(line string) (string, int, int, bool) {
- pos := 0
- for pos < len(line) {
- // find opening marker
- j := strings.IndexByte(line[pos:], inlineOpenChar)
- if j < 0 {
- return "", 0, 0, false
- }
- j += pos
- // ensure single open (not double) and non-space after
- if j+1 >= len(line) || line[j+1] == inlineOpenChar || line[j+1] == ' ' {
- pos = j + 1
- continue
- }
- // find closing marker
- k := strings.IndexByte(line[j+1:], inlineCloseChar)
- if k < 0 {
- return "", 0, 0, false
- }
- closeIdx := j + 1 + k
- if closeIdx-1 < 0 || line[closeIdx-1] == ' ' {
- pos = closeIdx + 1
- continue
- }
- inner := strings.TrimSpace(line[j+1 : closeIdx])
- if inner == "" {
- pos = closeIdx + 1
- continue
- }
- end := closeIdx + 1
- return inner, j, end, true
- }
- return "", 0, 0, false
+ pos := 0
+ for pos < len(line) {
+ // find opening marker
+ j := strings.IndexByte(line[pos:], inlineOpenChar)
+ if j < 0 {
+ return "", 0, 0, false
+ }
+ j += pos
+ // ensure single open (not double) and non-space after
+ if j+1 >= len(line) || line[j+1] == inlineOpenChar || line[j+1] == ' ' {
+ pos = j + 1
+ continue
+ }
+ // find closing marker
+ k := strings.IndexByte(line[j+1:], inlineCloseChar)
+ if k < 0 {
+ return "", 0, 0, false
+ }
+ closeIdx := j + 1 + k
+ if closeIdx-1 < 0 || line[closeIdx-1] == ' ' {
+ pos = closeIdx + 1
+ continue
+ }
+ inner := strings.TrimSpace(line[j+1 : closeIdx])
+ if inner == "" {
+ pos = closeIdx + 1
+ continue
+ }
+ end := closeIdx + 1
+ return inner, j, end, true
+ }
+ return "", 0, 0, false
}
// isBareDoubleSemicolon reports whether the line contains a standalone
// double-semicolon marker with no inline content (";;" possibly with only
// whitespace after it). It explicitly excludes the valid form ";;text;".
func isBareDoubleOpen(line string) bool {
- t := strings.TrimSpace(line)
- // check for double-open pattern
- dbl := string([]byte{inlineOpenChar, inlineOpenChar})
- if !strings.Contains(t, dbl) {
- return false
- }
- if hasDoubleOpenTrigger(t) {
- return false
- }
- if strings.HasPrefix(t, dbl) {
- rest := strings.TrimSpace(t[len(dbl):])
- if rest == "" || rest == ";" {
- return true
- }
- }
- return false
+ t := strings.TrimSpace(line)
+ // check for double-open pattern
+ dbl := string([]byte{inlineOpenChar, inlineOpenChar})
+ if !strings.Contains(t, dbl) {
+ return false
+ }
+ if hasDoubleOpenTrigger(t) {
+ return false
+ }
+ if strings.HasPrefix(t, dbl) {
+ rest := strings.TrimSpace(t[len(dbl):])
+ if rest == "" || rest == ";" {
+ return true
+ }
+ }
+ return false
}
// stripDuplicateAssignmentPrefix removes a duplicated assignment prefix from the suggestion.
@@ -409,82 +412,82 @@ func (s *Server) collectPromptRemovalEdits(uri string) []TextEdit {
}
func promptRemovalEditsForLine(line string, lineNum int) []TextEdit {
- if hasDoubleOpenTrigger(line) {
- return []TextEdit{{Range: Range{Start: Position{Line: lineNum, Character: 0}, End: Position{Line: lineNum, Character: len(line)}}, NewText: ""}}
- }
- return collectSemicolonMarkers(line, lineNum)
+ if hasDoubleOpenTrigger(line) {
+ return []TextEdit{{Range: Range{Start: Position{Line: lineNum, Character: 0}, End: Position{Line: lineNum, Character: len(line)}}, NewText: ""}}
+ }
+ return collectSemicolonMarkers(line, lineNum)
}
func hasDoubleOpenTrigger(line string) bool {
- pos := 0
- for pos < len(line) {
- // look for double-open sequence
- dbl := string([]byte{inlineOpenChar, inlineOpenChar})
- j := strings.Index(line[pos:], dbl)
- if j < 0 {
- return false
- }
- j += pos
- contentStart := j + len(dbl)
- if contentStart >= len(line) {
- return false
- }
- first := line[contentStart]
- if first == ' ' || first == inlineOpenChar {
- pos = contentStart + 1
- continue
- }
- // find closing
- k := strings.IndexByte(line[contentStart+1:], inlineCloseChar)
- if k < 0 {
- return false
- }
- closeIdx := contentStart + 1 + k
- if closeIdx-1 >= 0 && line[closeIdx-1] == ' ' {
- pos = closeIdx + 1
- continue
- }
- return true
- }
- return false
+ pos := 0
+ for pos < len(line) {
+ // look for double-open sequence
+ dbl := string([]byte{inlineOpenChar, inlineOpenChar})
+ j := strings.Index(line[pos:], dbl)
+ if j < 0 {
+ return false
+ }
+ j += pos
+ contentStart := j + len(dbl)
+ if contentStart >= len(line) {
+ return false
+ }
+ first := line[contentStart]
+ if first == ' ' || first == inlineOpenChar {
+ pos = contentStart + 1
+ continue
+ }
+ // find closing
+ k := strings.IndexByte(line[contentStart+1:], inlineCloseChar)
+ if k < 0 {
+ return false
+ }
+ closeIdx := contentStart + 1 + k
+ if closeIdx-1 >= 0 && line[closeIdx-1] == ' ' {
+ pos = closeIdx + 1
+ continue
+ }
+ return true
+ }
+ return false
}
func collectSemicolonMarkers(line string, lineNum int) []TextEdit {
- var edits []TextEdit
- startSemi := 0
- for startSemi < len(line) {
- j := strings.IndexByte(line[startSemi:], inlineOpenChar)
- if j < 0 {
- break
- }
- j += startSemi
- k := strings.IndexByte(line[j+1:], inlineCloseChar)
- if k < 0 {
- break
- }
- if j+1 >= len(line) || line[j+1] == ' ' {
- startSemi = j + 1
- continue
- }
- if line[j+1] == inlineOpenChar { // skip double-open start
- startSemi = j + 2
- continue
- }
- closeIdx := j + 1 + k
- if closeIdx-1 < 0 || line[closeIdx-1] == ' ' {
- startSemi = closeIdx + 1
- continue
- }
- if closeIdx-(j+1) < 1 {
- startSemi = closeIdx + 1
- continue
- }
- endChar := closeIdx + 1
- if endChar < len(line) && line[endChar] == ' ' {
- endChar++
- }
- edits = append(edits, TextEdit{Range: Range{Start: Position{Line: lineNum, Character: j}, End: Position{Line: lineNum, Character: endChar}}, NewText: ""})
- startSemi = endChar
- }
- return edits
+ var edits []TextEdit
+ startSemi := 0
+ for startSemi < len(line) {
+ j := strings.IndexByte(line[startSemi:], inlineOpenChar)
+ if j < 0 {
+ break
+ }
+ j += startSemi
+ k := strings.IndexByte(line[j+1:], inlineCloseChar)
+ if k < 0 {
+ break
+ }
+ if j+1 >= len(line) || line[j+1] == ' ' {
+ startSemi = j + 1
+ continue
+ }
+ if line[j+1] == inlineOpenChar { // skip double-open start
+ startSemi = j + 2
+ continue
+ }
+ closeIdx := j + 1 + k
+ if closeIdx-1 < 0 || line[closeIdx-1] == ' ' {
+ startSemi = closeIdx + 1
+ continue
+ }
+ if closeIdx-(j+1) < 1 {
+ startSemi = closeIdx + 1
+ continue
+ }
+ endChar := closeIdx + 1
+ if endChar < len(line) && line[endChar] == ' ' {
+ endChar++
+ }
+ edits = append(edits, TextEdit{Range: Range{Start: Position{Line: lineNum, Character: j}, End: Position{Line: lineNum, Character: endChar}}, NewText: ""})
+ startSemi = endChar
+ }
+ return edits
}