diff options
| author | Paul Buetow <paul@buetow.org> | 2025-08-14 00:21:06 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-08-14 00:21:06 +0300 |
| commit | a6a8b84690c50767f714b413496b5aeb45b31c21 (patch) | |
| tree | 1d50c91f331676b98a7a0b67b83c3b8ab91f1a42 /internal | |
| parent | eb0bb96fd23cae6e92c5f8d77ef29db8b6d50dc1 (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.go | 141 |
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 } |
