summaryrefslogtreecommitdiff
path: root/internal/lsp/document.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-16 15:35:02 +0300
committerPaul Buetow <paul@buetow.org>2025-08-16 15:35:02 +0300
commit6c8eb6876fe87553770de114ebd34649a0c6ec10 (patch)
tree064517edaf9d59522bec7191a61362a853c195bd /internal/lsp/document.go
parent1e1df8c204f6771719f85d8402128d72138bb863 (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.go145
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
+}