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
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
|
// In-memory document model for the LSP; tracks text, lines, and applies edits.
package lsp
import (
"strings"
"time"
)
type document struct {
uri string
text string
lines []string
}
func (s *Server) setDocument(uri, text string) {
s.mu.Lock()
defer s.mu.Unlock()
s.docs[uri] = &document{uri: uri, text: text, lines: splitLines(text)}
}
func (s *Server) deleteDocument(uri string) {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.docs, uri)
}
func (s *Server) markActivity() {
s.mu.Lock()
s.lastInput = time.Now()
s.mu.Unlock()
}
func (s *Server) getDocument(uri string) *document {
s.mu.RLock()
defer s.mu.RUnlock()
return s.docs[uri]
}
// splitLines splits the input string into lines, normalizing line endings to '\n'.
func splitLines(sx string) []string {
sx = strings.ReplaceAll(sx, "\r\n", "\n")
return strings.Split(sx, "\n")
}
func (s *Server) lineContext(uri string, pos Position) (above, current, below, funcCtx string) {
d := s.getDocument(uri)
if d == nil || len(d.lines) == 0 {
return "", "", "", ""
}
idx := pos.Line
if idx < 0 {
idx = 0
}
if idx >= len(d.lines) {
idx = len(d.lines) - 1
}
current = d.lines[idx]
if idx-1 >= 0 {
above = d.lines[idx-1]
}
if idx+1 < len(d.lines) {
below = d.lines[idx+1]
}
for i := idx; i >= 0; i-- {
line := strings.TrimSpace(d.lines[i])
if hasAny(line, []string{"func ", "def ", "class ", "fn ", "procedure ", "sub "}) {
funcCtx = line
break
}
}
return above, current, below, funcCtx
}
// 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 {
d := s.getDocument(uri)
if d == nil || len(d.lines) == 0 {
return false
}
idx := pos.Line
if idx < 0 {
idx = 0
}
if idx >= len(d.lines) {
idx = len(d.lines) - 1
}
// Find signature start
sigStart := -1
for i := idx; i >= 0; i-- {
if strings.Contains(d.lines[i], "func ") {
sigStart = i
break
}
// stop if we hit a closing brace which likely ends a previous block
if strings.Contains(d.lines[i], "}") {
break
}
}
if sigStart == -1 {
return false
}
// Scan for '{' from sigStart up to cursor position; if found before or at cursor, we're in body
for i := sigStart; i <= idx; i++ {
line := d.lines[i]
brace := strings.Index(line, "{")
if brace >= 0 {
if i < idx {
return false // body started on a previous line
}
// same line as cursor: if brace position < cursor character, then already in body
if pos.Character > brace {
return false
}
}
}
return true
}
func hasAny(s string, needles []string) bool {
for _, n := range needles {
if strings.Contains(s, n) {
return true
}
}
return false
}
func trimLen(s string) string {
s = strings.TrimSpace(s)
if len(s) > 200 {
return s[:200] + "…"
}
return s
}
func firstLine(s string) string {
s = strings.ReplaceAll(s, "\r\n", "\n")
if idx := strings.IndexByte(s, '\n'); idx >= 0 {
return s[:idx]
}
return s
}
|