summaryrefslogtreecommitdiff
path: root/internal/lsp/context.go
blob: 8f345df2c43b11f6143256a215f122a687e46213 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package lsp

import (
    "strings"
    "hexai/internal/logging"
)

// buildAdditionalContext builds extra context messages based on the configured mode.
// Modes:
// - minimal: no extra context
// - 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) {
    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)
}

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)
}

// 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]
}