fix completions in comments and strings, and more reliable binding completions

This commit is contained in:
XeroOl 2025-07-11 14:04:11 -05:00
parent 28e20f74cb
commit 7b215e15e8
2 changed files with 46 additions and 17 deletions

View File

@ -5,6 +5,7 @@
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))
(local format (require :fennel-ls.formatter)) (local format (require :fennel-ls.formatter))
(local navigate (require :fennel-ls.navigate)) (local navigate (require :fennel-ls.navigate))
(local compiler (require :fennel-ls.compiler))
(local {:metadata METADATA} (require :fennel.compiler)) (local {:metadata METADATA} (require :fennel.compiler))
(local hardcoded-completions (local hardcoded-completions
@ -20,16 +21,21 @@
(let [file (files.get-by-uri server uri) (let [file (files.get-by-uri server uri)
;; find where the cursor is ;; find where the cursor is
byte (utils.position->byte file.text position server.position-encoding) byte (utils.position->byte file.text position server.position-encoding)
;; create a brand new file
file {:text (.. (file.text:sub 1 (- byte 1)) "|" (file.text:sub byte)) :uri file.uri}
_ (compiler.compile server file)
;; find what ast objects are under the cursor ;; find what ast objects are under the cursor
(?symbol parents) (analyzer.find-symbol file.ast byte) (symbol parents) (analyzer.find-symbol file.ast byte)
;; check what context I'm in ;; check what context I'm in
in-call-position? (and (fennel.list? (. parents 1)) in-call-position? (and (fennel.list? (. parents 1))
(= ?symbol (. parents 1 1))) (= symbol (. parents 1 1)))
;; find the first one that contains a scope ;; find the first one that contains a scope
scope (or (accumulate [?find nil _ parent (ipairs parents) &until ?find] scope (or (accumulate [?find nil _ parent (ipairs parents) &until ?find]
(. file.scopes parent)) (. file.scopes parent))
file.scope) file.scope)
range (if ?symbol (message.ast->range server file ?symbol) {:start position :end position}) range (case (message.ast->range server file symbol)
r (do (set r.end.character (- r.end.character 1)) r)
_ {:start position :end position})
results [] results []
seen {}] seen {}]
@ -67,7 +73,7 @@
(set (. seen-manglings global*) true) (set (. seen-manglings global*) true)
(case (analyzer.search-name-and-scope server file global* scope) (case (analyzer.search-name-and-scope server file global* scope)
def (if (and (= :_G (tostring global*)) def (if (and (= :_G (tostring global*))
(not (: (tostring ?symbol) :match "_G[:.]"))) (not (: (tostring symbol) :match "_G[:.]")))
(add-completion! global* def) (add-completion! global* def)
(add-completion-recursively! global* def)) (add-completion-recursively! global* def))
_ (do _ (do
@ -104,10 +110,10 @@
(case (message:match "unknown identifier: ([a-zA-Z0-9_-]+)") (case (message:match "unknown identifier: ([a-zA-Z0-9_-]+)")
identifier (add-completion! identifier {} :Variable)))) identifier (add-completion! identifier {} :Variable))))
(if (. file.definitions ?symbol) (when symbol
(if (. file.definitions symbol)
(binding-completions) (binding-completions)
(expression-completions)) (expression-completions)))
(if server.can-do-good-completions? (if server.can-do-good-completions?
{:itemDefaults {:editRange range :data {: uri : byte}} {:itemDefaults {:editRange range :data {: uri : byte}}

View File

@ -39,7 +39,7 @@
(= e.textEdit.range.end.line c.textEdit.range.end.line) (= e.textEdit.range.end.line c.textEdit.range.end.line)
(= e.textEdit.range.end.character c.textEdit.range.end.character) (= e.textEdit.range.end.character c.textEdit.range.end.character)
(= e.textEdit.newText c.textEdit.newText)) (= e.textEdit.newText c.textEdit.newText))
(and (= (type e.textEdit) :function)) (e.textEdit c.textEdit)))) (and (= (type e.textEdit) :function) (e.textEdit c.textEdit)))))
i))) i)))
(fn check [file-contents expected unexpected ?client-opts] (fn check [file-contents expected unexpected ?client-opts]
@ -121,8 +121,8 @@
(check "(local x {:field {:deep 100}})\n(if de" [:x.field.deep] []) (check "(local x {:field {:deep 100}})\n(if de" [:x.field.deep] [])
(check "(local t {:field (fn [foo] nil)})\n(t|" [:t.field] []) (check "(local t {:field (fn [foo] nil)})\n(t|" [:t.field] [])
(check "(local t {:field (fn [self] nil)})\n(t|" [:t:field] []) (check "(local t {:field (fn [self] nil)})\n(t|" [:t:field] [])
(check "(local t {})\n(fn t.field [foo] nil)})\n(t|" [:t.field] []) (check "(local t {})\n(fn t.field [foo] nil)\n(t|" [:t.field] [])
(check "(local t {})\n(fn t.field [self] nil)})\n(t|" [:t:field] []) (check "(local t {})\n(fn t.field [self] nil)\n(t|" [:t:field] [])
nil) nil)
(fn test-builtin [] (fn test-builtin []
@ -258,13 +258,35 @@
nil) nil)
(fn test-destructure [] (fn test-destructure []
;; this is a binding variable, we don't want all the normal completions ;; this is in a destructure location, so we don't want all the normal completions
(check "(local f|)\n(print foo)" (check "(local |)
(print foo)"
[:foo] [:foo]
[:setmetatable :_G]) [:math])
(check "(let [f|]\n(print foo)" (check "(local f|)
(print foo)"
[:foo] [:foo]
[:setmetatable :_G]) [:math])
(check "(let [f|]
(print foo))"
[:foo]
[:math])
(check "(let [foo |] ; cursor is in an expression so we want expressions now
(print foo))"
[:math]
[])
nil)
(fn test-no-completion []
(check "; ("
[]
[:math])
(check "\" (|\n\""
[]
[:math])
(check "\"\n(|\""
[]
[:math])
nil) nil)
;; ;; Future tests / features ;; ;; Future tests / features
@ -294,4 +316,5 @@
: test-field : test-field
: test-docs : test-docs
: test-module : test-module
: test-destructure} : test-destructure
: test-no-completion}