clean up some tests, restore (fn M.method []) completion handling

This commit is contained in:
XeroOl 2025-04-23 22:38:46 -05:00
parent 2800038005
commit 3f1c2ea361
4 changed files with 68 additions and 66 deletions

View File

@ -20,6 +20,7 @@
* Ignore unused locals if they end in underscore. * Ignore unused locals if they end in underscore.
* Settings file: `flsproject.fnl`. Settings are now editor agnostic. * Settings file: `flsproject.fnl`. Settings are now editor agnostic.
* Support `:intersection` as a Lua version; only includes globals present in every Lua. * Support `:intersection` as a Lua version; only includes globals present in every Lua.
* Support better completions. The eglot client is no longer a special case.
### Changes ### Changes

View File

@ -83,14 +83,26 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
:keys ?keys :keys ?keys
:multival ?multival :multival ?multival
:fields ?fields}} assignment] :fields ?fields}} assignment]
(when (and (= 0 (length stack))
opts.save-last-binding)
(tset opts.save-last-binding 1 assignment.target))
(if (and (= 0 (length stack)) opts.stop-early?) (if (and (= 0 (length stack)) opts.stop-early?)
assignment.target ;; BASE CASE!! assignment.target ;; BASE CASE!!
;; search a virtual field from :fields
;; :fields
(and (not= 0 (length stack)) (?. ?fields (. stack (length stack)))) (and (not= 0 (length stack)) (?. ?fields (. stack (length stack))))
(search-assignment server file {:target (. ?fields (table.remove stack))} stack opts) (search-assignment server file {:target (. ?fields (table.remove stack))} stack opts)
;; This case is hard to explain.
;; Under these conditions, search-multival is planning on returning {:definition ?definition : file}
;; but assignment.target is more rich with information.
;; Specifically, it has the :fields key, which is useful in finding non-local fields.
;; For example, The `my-method` field of `M` in `(local M {})\n(fn M.my-method [])`
;; is shared using this :fields mechanism.
(and (not (list? ?definition))
(not (varg? ?definition))
(= 1 (or 1 ?multival))
(not (sym? ?definition))
(= 0 (length stack)))
assignment.target
(search-multival server file ?definition (stack-add-keys! stack ?keys) (or ?multival 1) opts)))) (search-multival server file ?definition (stack-add-keys! stack ?keys) (or ?multival 1) opts))))
(λ search-reference [server file ref stack opts] (λ search-reference [server file ref stack opts]
@ -212,10 +224,10 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
opts (or ?opts {})] opts (or ?opts {})]
(case (docs.get-builtin server (. split 1)) (case (docs.get-builtin server (. split 1))
metadata (search-document server metadata stack opts) metadata (search-document server metadata stack opts)
_ (case (docs.get-global server (. split 1)) _ (case (find-local-definition file name scope)
metadata (search-document server metadata stack opts) def (search-val server file def.definition (stack-add-keys! stack def.keys) opts)
_ (case (find-local-definition file name scope) _ (case (docs.get-global server (. split 1))
def (search-val server file def.definition (stack-add-keys! stack def.keys) opts)))))) metadata (search-document server metadata stack opts))))))
(λ past? [?ast byte] (λ past? [?ast byte]
;; check if a byte is past an ast object ;; check if a byte is past an ast object

View File

@ -29,7 +29,7 @@
file.scope) file.scope)
range (if ?symbol (message.ast->range server file ?symbol) {:start position :end position}) range (if ?symbol (message.ast->range server file ?symbol) {:start position :end position})
results [] results []
seen []] seen {}]
(fn add-completion [definition name] (fn add-completion [definition name]
(table.insert results (table.insert results
@ -53,45 +53,54 @@
(and (fennel.list? def.definition) (and (fennel.list? def.definition)
;; TODO check that arg is called `self` ;; TODO check that arg is called `self`
(or (and (fennel.sym? (. def.definition 1) "fn") (or (and (fennel.sym? (. def.definition 1) "fn")
(fennel.sym? (. def.definition 2) "self")) (fennel.sym? (?. def.definition 2 1) "self"))
(and (fennel.sym? (. def.definition 1) "λ") (and (fennel.sym? (. def.definition 1) "λ")
(fennel.sym? (. def.definition 2) "self"))))) (fennel.sym? (?. def.definition 2 1) "self")))))
(add-completion-recursively def (.. name ":" field)) (add-completion-recursively def (.. name ":" field))
(add-completion-recursively def (.. name "." field))) (add-completion-recursively def (.. name "." field)))
_ (do _ (do
(io.stderr:write "BAD!!!! undocumented field: " (tostring field) "\n") (io.stderr:write "BAD!!!! undocumented field: " (tostring field) "\n")
{:label field}))))) {:label field})))))
(when definition.fields (when definition.fields
(each [field value (pairs definition.fields)] (each [field value (pairs definition.fields)]
(when (= (type field) :string) (when (= (type field) :string)
(if (or (= :self (tostring (?. value :metadata :fnl/arglist 1))) (if (or (= :self (tostring (?. value :metadata :fnl/arglist 1)))
(and (fennel.list? value.definition) (and (fennel.list? value.definition)
;; TODO check that arg is called `self` ;; TODO check that arg is called `self`
(or (and (fennel.sym? (. value.definition 1) "fn") (or (and (fennel.sym? (. value.definition 1) "fn")
(fennel.sym? (. value.definition 2) "self")) (fennel.sym? (?. value.definition 2 1) "self"))
(and (fennel.sym? (. value.definition 1) "λ") (and (fennel.sym? (. value.definition 1) "λ")
(fennel.sym? (. value.definition 2) "self"))))) (fennel.sym? (?. value.definition 2 1) "self")))))
(add-completion-recursively value (.. name ":" field)) (add-completion-recursively value (.. name ":" field))
(add-completion-recursively value (.. name "." field)))))) (add-completion-recursively value (.. name "." field))))))
(set (. seen definition) false))) (set (. seen definition) false)))
;; end yield ;; end yield
(local seen-manglings {})
(each [_ global* (ipairs file.allowed-globals)] (each [_ global* (ipairs file.allowed-globals)]
(case (analyzer.search-name-and-scope server file global* scope) (when (not (. seen-manglings global*))
def (if (and (= :_G (tostring global*)) (set (. seen-manglings global*) true)
(not (: (tostring ?symbol) :match "_G[:.]"))) (case (analyzer.search-name-and-scope server file global* scope)
(add-completion def global*) def (if (and (= :_G (tostring global*))
(add-completion-recursively def global*)) (not (: (tostring ?symbol) :match "_G[:.]")))
_ (do (add-completion def global*)
(io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n") (add-completion-recursively def global*))
{:label global*}))) _ (do
(io.stderr:write "BAD!!!! undocumented global: " (tostring global*) "\n")
{:label global*}))))
(var scope scope) (var scope scope)
(while scope (while scope
(each [mangling (pairs scope.manglings)] (each [mangling (pairs scope.manglings)]
(case (analyzer.search-name-and-scope server file mangling scope) (when (not (. seen-manglings mangling))
def (add-completion-recursively def mangling) (set (. seen-manglings mangling) true)
_ (add-completion-recursively {} mangling))) (case (analyzer.search-name-and-scope server file mangling scope)
def (add-completion-recursively def mangling)
_ (add-completion-recursively {} mangling))))
(when in-call-position? (when in-call-position?
(each [macro* (pairs scope.macros)] (each [macro* (pairs scope.macros)]
(table.insert results {:label macro* :filterText macro* :textEdit {:newText macro* : range}})) (table.insert results {:label macro* :filterText macro* :textEdit {:newText macro* : range}}))

View File

@ -154,29 +154,29 @@
nil) nil)
(fn test-field [] (fn test-field []
(check "(local x {:field (fn [])})\n(x:" [:x:field] []) (check "(local x {:field (fn [self])})\n(x:" [:x:field] [])
(check "(local x {:field (fn [self])})\n(x:fi|" [:x:field] [])
;; regression test for not crashing ;; regression test for not crashing
(check "(local x {:field (fn [])})\n(x::f" [] []) (check "(local x {:field (fn [self])})\n(x::f" [] [])
(check (check
"(let [my-table {:foo 10 :bar 20}]\n my-table.|)))" "(let [my-table {:foo 10 :bar 20}]\n my-table.|)))"
[:foo :bar] [:my-table.foo :my-table.bar]
[:local :doto :+]) ;; no specials or macros because it's not in a head position [])
(check (check
{:main.fnl "(let [foo (require :fooo)] {:main.fnl "(let [foo (require :fooo)]
foo.|)))" foo.|)))"
:fooo.fnl "(fn my-export [x] (print x)) :fooo.fnl "(fn my-export [x] (print x))
{: my-export :constant 10}"} {: my-export :constant 10}"}
[:my-export :constant] [:foo.my-export :foo.constant]
[:_G :local :doto :+]) [])
(check (check
{:main.fnl "(let [foo (require :fooo)] {:main.fnl "(let [foo (require :fooo)]
foo.|)))" foo.|)))"
:fooo.fnl "(local M {:constant 10}) :fooo.fnl "(local M {:constant 10})
(fn M.my-export [x] (print x)) (fn M.my-export [x] (print x))
M"} M"}
[:my-export :constant] [:foo.my-export :foo.constant]
[:_G :local :doto :+]) ;; no globals, specials, macros, or others [])
(check "(local x {:field (fn [])})\n(x:fi|" [:field] [:table])
nil) nil)
(fn test-docs [] (fn test-docs []
@ -231,12 +231,11 @@
(check "(coroutine.y|" (check "(coroutine.y|"
[{:label "yield" [{:label "yield"
:documentation #(and $.value ($.value:find "```fnl\n(coroutine.yield ...)\n```" 1 true))}] :documentation #(and $.value ($.value:find "```fnl\n(coroutine.yield ...)\n```" 1 true))}]
["coroutine" "_G" "do" [{:documentation #(= nil $)}])
{:documentation #(= nil $)}])
(check "(local c coroutine) (check "(local c coroutine)
(c.y" (c.y"
[{:label "yield"}] ["coroutine.yield" "c.yield"]
["coroutine" "_G" "do"]) [{:documentation #(= nil $)}])
(check "(local t table) (check "(local t table)
(t.i" (t.i"
["insert"] ["insert"]
@ -248,24 +247,6 @@
nil) nil)
(fn test-eglot-fields []
"tests for handling Eglot specially"
(check "(coroutine.y|"
[{:label "coroutine.yield"
:filterText "coroutine.yield"
:documentation #(and $.value ($.value:find "```fnl\n(coroutine.yield ...)\n```" 1 true))}]
[])
(check "(local c coroutine)
(c.y"
[{:label "c.yield"
:filterText "c.yield"
:textEdit #(= nil $)}]
[])
(check "(local x {:field (fn [])})
(x:"
[:x:field]
[]))
;; ;; Future tests / features ;; ;; Future tests / features
;; ;; Scope Ordering Rules ;; ;; Scope Ordering Rules
;; (it "does not suggest locals past the suggestion location when a symbol is partially typed") ;; (it "does not suggest locals past the suggestion location when a symbol is partially typed")
@ -292,5 +273,4 @@
: test-fn-arg : test-fn-arg
: test-field : test-field
: test-docs : test-docs
: test-module : test-module}
: test-eglot-fields}