summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-08-14 00:21:06 +0300
committerPaul Buetow <paul@buetow.org>2025-08-14 00:21:06 +0300
commita6a8b84690c50767f714b413496b5aeb45b31c21 (patch)
tree1d50c91f331676b98a7a0b67b83c3b8ab91f1a42 /internal
parenteb0bb96fd23cae6e92c5f8d77ef29db8b6d50dc1 (diff)
Revert "feat(lsp): gate completion by context — require preceding space and >=2s idle since last change; add logging for gating decisions"
This reverts commit eb0bb96fd23cae6e92c5f8d77ef29db8b6d50dc1.
Diffstat (limited to 'internal')
-rw-r--r--internal/lsp/server.go141
1 files changed, 41 insertions, 100 deletions
diff --git a/internal/lsp/server.go b/internal/lsp/server.go
index 32ae7f2..3949680 100644
--- a/internal/lsp/server.go
+++ b/internal/lsp/server.go
@@ -12,7 +12,6 @@ import (
"strconv"
"strings"
"sync"
- "time"
)
// JSON-RPC 2.0 structures (minimal)
@@ -79,11 +78,10 @@ type Server struct {
mu sync.RWMutex
docs map[string]*document
logContext bool
- lastChange map[string]time.Time
}
func NewServer(r io.Reader, w io.Writer, logger *log.Logger, logContext bool) *Server {
- return &Server{in: bufio.NewReader(r), out: w, logger: logger, docs: make(map[string]*document), logContext: logContext, lastChange: make(map[string]time.Time)}
+ return &Server{in: bufio.NewReader(r), out: w, logger: logger, docs: make(map[string]*document), logContext: logContext}
}
func (s *Server) Run() error {
@@ -135,30 +133,26 @@ func (s *Server) handle(req Request) {
s.exited = true
// No response per spec.
os.Exit(0)
- case "textDocument/didOpen":
- var p DidOpenTextDocumentParams
- if err := json.Unmarshal(req.Params, &p); err == nil {
- s.setDocument(p.TextDocument.URI, p.TextDocument.Text)
- s.setLastChange(p.TextDocument.URI, time.Now())
- }
- case "textDocument/didChange":
- var p DidChangeTextDocumentParams
- if err := json.Unmarshal(req.Params, &p); err == nil {
- if len(p.ContentChanges) > 0 {
- s.setDocument(p.TextDocument.URI, p.ContentChanges[len(p.ContentChanges)-1].Text)
- s.setLastChange(p.TextDocument.URI, time.Now())
- }
- }
- case "textDocument/didClose":
- var p DidCloseTextDocumentParams
- if err := json.Unmarshal(req.Params, &p); err == nil {
- s.deleteDocument(p.TextDocument.URI)
- s.clearLastChange(p.TextDocument.URI)
- }
+ case "textDocument/didOpen":
+ var p DidOpenTextDocumentParams
+ if err := json.Unmarshal(req.Params, &p); err == nil {
+ s.setDocument(p.TextDocument.URI, p.TextDocument.Text)
+ }
+ case "textDocument/didChange":
+ var p DidChangeTextDocumentParams
+ if err := json.Unmarshal(req.Params, &p); err == nil {
+ if len(p.ContentChanges) > 0 {
+ s.setDocument(p.TextDocument.URI, p.ContentChanges[len(p.ContentChanges)-1].Text)
+ }
+ }
+ case "textDocument/didClose":
+ var p DidCloseTextDocumentParams
+ if err := json.Unmarshal(req.Params, &p); err == nil {
+ s.deleteDocument(p.TextDocument.URI)
+ }
case "textDocument/completion":
var p CompletionParams
var docStr string
- allowed := true
if err := json.Unmarshal(req.Params, &p); err == nil {
above, current, below, funcCtx := s.lineContext(p.TextDocument.URI, p.Position)
docStr = fmt.Sprintf("file: %s\nline: %d\nabove: %s\ncurrent: %s\nbelow: %s\nfunction: %s", p.TextDocument.URI, p.Position.Line, trimLen(above), trimLen(current), trimLen(below), trimLen(funcCtx))
@@ -166,28 +160,15 @@ func (s *Server) handle(req Request) {
s.logger.Printf("completion ctx uri=%s line=%d char=%d above=%q current=%q below=%q function=%q",
p.TextDocument.URI, p.Position.Line, p.Position.Character, trimLen(above), trimLen(current), trimLen(below), trimLen(funcCtx))
}
- // Apply gating: require space before cursor AND >=2s idle since last change
- if !s.prevCharIsSpace(p.TextDocument.URI, p.Position) {
- allowed = false
- }
- if since := s.sinceLastChange(p.TextDocument.URI); since >= 0 && since < 2*time.Second {
- allowed = false
- }
- }
- var items []CompletionItem
- if allowed {
- items = []CompletionItem{{
- Label: "hexai-complete",
- Kind: 14,
- Detail: "dummy completion",
- InsertText: "hexai",
- SortText: "0000",
- Documentation: docStr,
- }}
- } else if s.logContext {
- s.logger.Printf("completion gated: uri=%s allowed=%v idle=%v spaceBefore=%v",
- p.TextDocument.URI, allowed, s.sinceLastChange(p.TextDocument.URI), s.prevCharIsSpace(p.TextDocument.URI, p.Position))
}
+ items := []CompletionItem{{
+ Label: "hexai-complete",
+ Kind: 14,
+ Detail: "dummy completion",
+ InsertText: "hexai",
+ SortText: "0000",
+ Documentation: docStr,
+ }}
s.reply(req.ID, CompletionList{IsIncomplete: false, Items: items}, nil)
default:
// Unknown method; reply with Method Not Found for requests that have an ID.
@@ -264,9 +245,9 @@ type document struct {
}
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)}
+ s.mu.Lock()
+ defer s.mu.Unlock()
+ s.docs[uri] = &document{uri: uri, text: text, lines: splitLines(text)}
}
func (s *Server) deleteDocument(uri string) {
@@ -282,8 +263,8 @@ func (s *Server) getDocument(uri string) *document {
}
func splitLines(sx string) []string {
- sx = strings.ReplaceAll(sx, "\r\n", "\n")
- return strings.Split(sx, "\n")
+ sx = strings.ReplaceAll(sx, "\r\n", "\n")
+ return strings.Split(sx, "\n")
}
// LSP param types (subset)
@@ -334,10 +315,10 @@ type CompletionParams struct {
}
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 "", "", "", ""
- }
+ d := s.getDocument(uri)
+ if d == nil || len(d.lines) == 0 {
+ return "", "", "", ""
+ }
idx := pos.Line
if idx < 0 {
idx = 0
@@ -359,7 +340,7 @@ func (s *Server) lineContext(uri string, pos Position) (above, current, below, f
break
}
}
- return
+ return
}
func hasAny(s string, needles []string) bool {
@@ -372,49 +353,9 @@ func hasAny(s string, needles []string) bool {
}
func trimLen(s string) string {
- s = strings.TrimSpace(s)
- if len(s) > 200 {
- return s[:200] + "…"
- }
- return s
-}
-
-// --- Gating helpers ---
-func (s *Server) setLastChange(uri string, t time.Time) {
- s.mu.Lock()
- defer s.mu.Unlock()
- s.lastChange[uri] = t
-}
-
-func (s *Server) clearLastChange(uri string) {
- s.mu.Lock()
- defer s.mu.Unlock()
- delete(s.lastChange, uri)
-}
-
-func (s *Server) sinceLastChange(uri string) time.Duration {
- s.mu.RLock()
- t, ok := s.lastChange[uri]
- s.mu.RUnlock()
- if !ok {
- return -1
- }
- return time.Since(t)
-}
-
-func (s *Server) prevCharIsSpace(uri string, pos Position) bool {
- d := s.getDocument(uri)
- if d == nil {
- return false
- }
- if pos.Line < 0 || pos.Line >= len(d.lines) {
- return false
- }
- line := d.lines[pos.Line]
- // Convert to runes to be safe with multibyte
- r := []rune(line)
- if pos.Character <= 0 || pos.Character > len(r) {
- return false
- }
- return r[pos.Character-1] == ' '
+ s = strings.TrimSpace(s)
+ if len(s) > 200 {
+ return s[:200] + "…"
+ }
+ return s
}