summaryrefslogtreecommitdiff
path: root/internal/lsp/context.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-17 00:06:00 +0300
committerPaul Buetow <paul@buetow.org>2025-08-17 00:06:00 +0300
commitdc383b4faef881f3bb22816f42c53a79236a4152 (patch)
tree7c6a48487fc1d51fed72ea5d15618d133132cdaa /internal/lsp/context.go
parent6a1d48036105e92193aef11a15a77a569eeb1562 (diff)
lsp/config: make completion trigger characters configurable
- Add trigger_characters to JSON config and ServerOptions - Store on server and advertise in initialize - Update README and example config - Preserve previous defaults when unset
Diffstat (limited to 'internal/lsp/context.go')
-rw-r--r--internal/lsp/context.go116
1 files changed, 58 insertions, 58 deletions
diff --git a/internal/lsp/context.go b/internal/lsp/context.go
index 8f345df..e746058 100644
--- a/internal/lsp/context.go
+++ b/internal/lsp/context.go
@@ -1,8 +1,8 @@
package lsp
import (
- "strings"
- "hexai/internal/logging"
+ "hexai/internal/logging"
+ "strings"
)
// buildAdditionalContext builds extra context messages based on the configured mode.
@@ -12,71 +12,71 @@ import (
// - 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) {
- mode := s.contextMode
- switch mode {
- case "minimal":
- return "", false
- case "window":
- return s.windowContext(uri, pos), true
- case "file-on-new-func":
- if newFunc {
- return s.fullFileContext(uri), true
- }
- return "", false
- case "always-full":
- return s.fullFileContext(uri), true
- default:
- // fallback to minimal if unknown
- return "", false
- }
+ mode := s.contextMode
+ switch mode {
+ case "minimal":
+ return "", false
+ case "window":
+ return s.windowContext(uri, pos), true
+ case "file-on-new-func":
+ if newFunc {
+ return s.fullFileContext(uri), true
+ }
+ return "", false
+ case "always-full":
+ return s.fullFileContext(uri), true
+ default:
+ // fallback to minimal if unknown
+ return "", false
+ }
}
func (s *Server) windowContext(uri string, pos Position) string {
- d := s.getDocument(uri)
- if d == nil || len(d.lines) == 0 {
- logging.Logf("lsp ", "context: window requested but document not open; skipping uri=%s", uri)
- return ""
- }
- n := len(d.lines)
- half := s.windowLines / 2
- start := pos.Line - half
- if start < 0 {
- start = 0
- }
- end := pos.Line + half + 1
- if end > n {
- end = n
- }
- text := strings.Join(d.lines[start:end], "\n")
- return truncateToApproxTokens(text, s.maxContextTokens)
+ d := s.getDocument(uri)
+ if d == nil || len(d.lines) == 0 {
+ logging.Logf("lsp ", "context: window requested but document not open; skipping uri=%s", uri)
+ return ""
+ }
+ n := len(d.lines)
+ half := s.windowLines / 2
+ start := pos.Line - half
+ if start < 0 {
+ start = 0
+ }
+ end := pos.Line + half + 1
+ if end > n {
+ end = n
+ }
+ text := strings.Join(d.lines[start:end], "\n")
+ return truncateToApproxTokens(text, s.maxContextTokens)
}
func (s *Server) fullFileContext(uri string) string {
- d := s.getDocument(uri)
- if d == nil {
- logging.Logf("lsp ", "context: full-file requested but document not open; skipping uri=%s", uri)
- return ""
- }
- return truncateToApproxTokens(d.text, s.maxContextTokens)
+ d := s.getDocument(uri)
+ if d == nil {
+ logging.Logf("lsp ", "context: full-file requested but document not open; skipping uri=%s", uri)
+ return ""
+ }
+ return truncateToApproxTokens(d.text, s.maxContextTokens)
}
// truncateToApproxTokens naively truncates the input to fit approx N tokens.
// Uses 4 chars/token heuristic for speed and determinism.
func truncateToApproxTokens(text string, maxTokens int) string {
- if maxTokens <= 0 {
- return ""
- }
- maxChars := maxTokens * 4
- if len(text) <= maxChars {
- return text
- }
- // try to cut on a line boundary near maxChars
- cut := maxChars
- if cut > len(text) {
- cut = len(text)
- }
- if i := strings.LastIndex(text[:cut], "\n"); i > 0 {
- cut = i
- }
- return text[:cut]
+ if maxTokens <= 0 {
+ return ""
+ }
+ maxChars := maxTokens * 4
+ if len(text) <= maxChars {
+ return text
+ }
+ // try to cut on a line boundary near maxChars
+ cut := maxChars
+ if cut > len(text) {
+ cut = len(text)
+ }
+ if i := strings.LastIndex(text[:cut], "\n"); i > 0 {
+ cut = i
+ }
+ return text[:cut]
}