diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-16 15:35:02 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-16 15:35:02 +0300 |
| commit | 6c8eb6876fe87553770de114ebd34649a0c6ec10 (patch) | |
| tree | 064517edaf9d59522bec7191a61362a853c195bd /internal/lsp/document.go | |
| parent | 1e1df8c204f6771719f85d8402128d72138bb863 (diff) | |
lsp: split monolithic server.go into modules; add configurable max tokens and context strategies (minimal|window|file-on-new-func|always-full); provide flags/env fallbacks; add unit tests for helpers and context; update README; remove obsolete files
Diffstat (limited to 'internal/lsp/document.go')
| -rw-r--r-- | internal/lsp/document.go | 145 |
1 files changed, 145 insertions, 0 deletions
diff --git a/internal/lsp/document.go b/internal/lsp/document.go new file mode 100644 index 0000000..e5eaf06 --- /dev/null +++ b/internal/lsp/document.go @@ -0,0 +1,145 @@ +package lsp + +import ( + "strings" + "time" +) + +// --- Document store and helpers --- + +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] +} + +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 +} + +// 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 +} |
