adjust parents, and take advantage of hooks for better completions
This commit is contained in:
parent
b9d084fada
commit
89eb07c064
@ -103,9 +103,15 @@ later by fennel-ls.language to answer requests from the client."
|
||||
|
||||
(λ define-function [ast scope]
|
||||
;; handle the definitions of a function
|
||||
(define-function-name ast scope)
|
||||
(define-function-name ast scope))
|
||||
|
||||
(λ compile-fn [ast scope]
|
||||
(tset scopes ast scope) ;; update scope
|
||||
(define-function-args ast scope))
|
||||
|
||||
(λ compile-do [ast scope]
|
||||
(tset scopes ast scope)) ;; update scope
|
||||
|
||||
(λ call [ast scope]
|
||||
(tset scopes ast scope)
|
||||
;; Most calls aren't interesting, but here's the list of the ones that are:
|
||||
@ -147,12 +153,14 @@ later by fennel-ls.language to answer requests from the client."
|
||||
(let
|
||||
[plugin
|
||||
{:name "fennel-ls"
|
||||
:versions ["1.2.0"]
|
||||
:versions ["1.3.0"]
|
||||
:symbol-to-expression reference
|
||||
:call call
|
||||
:destructure define
|
||||
:assert-compile on-compile-error
|
||||
:parse-error on-parse-error}
|
||||
:parse-error on-parse-error
|
||||
:customhook-early-do compile-do
|
||||
:customhook-early-fn compile-fn}
|
||||
scope (fennel.scope)
|
||||
opts {:filename file.uri
|
||||
:plugins [plugin]
|
||||
@ -171,6 +179,7 @@ later by fennel-ls.language to answer requests from the client."
|
||||
|
||||
;; write things back to the file object
|
||||
(set file.ast ast)
|
||||
(set file.scope scope)
|
||||
(set file.scopes scopes)
|
||||
(set file.definitions definitions)
|
||||
(set file.diagnostics diagnostics)
|
||||
|
||||
@ -72,7 +72,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(match-try (language.find-symbol file.ast byte)
|
||||
(symbol parents)
|
||||
(match-try
|
||||
(let [parent (. parents (length parents))]
|
||||
(let [parent (. parents 1)]
|
||||
(if (. file.require-calls parent)
|
||||
(language.search self file parent [])))
|
||||
nil
|
||||
@ -91,19 +91,32 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
result {:contents {:kind "markdown"
|
||||
:value (formatter.hover-format result)}})))
|
||||
|
||||
(λ collect-scope [scope typ callback ?target]
|
||||
(let [result (or ?target [])]
|
||||
(var scope scope)
|
||||
(while scope
|
||||
(icollect [i v (pairs (. scope typ)) &into result]
|
||||
(callback i v))
|
||||
(set scope scope.parent))
|
||||
result))
|
||||
|
||||
(λ find-things-in-scope [file parents typ callback ?target]
|
||||
(let [scope (or (accumulate [result nil
|
||||
i parent (ipairs parents)
|
||||
&until result]
|
||||
(. file.scopes parent))
|
||||
file.scope)]
|
||||
(collect-scope scope typ callback ?target)))
|
||||
|
||||
(λ requests.textDocument/completion [self send {: position :textDocument {: uri}}]
|
||||
(let [file (state.get-by-uri self uri)
|
||||
byte (pos->byte file.text position.line position.character)
|
||||
(?symbol parents) (language.find-symbol file.ast byte)]
|
||||
;; TODO build a recursive searcher,
|
||||
;; store file scopes parents length parents as an intermediate
|
||||
;; potentially reverse parents order so it's only (. parents 1)
|
||||
(let [begin (if (?. file.scopes (. parents (length parents)))
|
||||
(icollect [k (pairs (. file.scopes (. parents (length parents)) :manglings))]
|
||||
{:label k})
|
||||
[])]
|
||||
(icollect [_ k (ipairs file.allowed-globals) &into begin] {:label k}))))
|
||||
|
||||
(let [result []]
|
||||
(find-things-in-scope file parents :manglings #{:label $} result)
|
||||
(find-things-in-scope file parents :macros #{:label $} result)
|
||||
(find-things-in-scope file parents :specials #{:label $} result)
|
||||
(icollect [_ k (ipairs file.allowed-globals) &into result] {:label k}))))
|
||||
|
||||
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
|
||||
(local file (state.get-by-uri self uri))
|
||||
@ -120,7 +133,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(set file.open? false))
|
||||
|
||||
(λ requests.shutdown [self send]
|
||||
"The server still needs to respond to this request, so the program can't close yet. Wait until notifications.exit"
|
||||
"The server still needs to respond to this request, so the program can't close yet. Just wait until notifications.exit"
|
||||
nil)
|
||||
|
||||
(λ notifications.exit [self]
|
||||
|
||||
@ -142,7 +142,8 @@ the data provided by compiler.fnl."
|
||||
(accumulate [result nil i top-level-form (ipairs ast) &until result]
|
||||
(if (contains? top-level-form byte)
|
||||
(recurse top-level-form byte)))
|
||||
parents))
|
||||
(fcollect [i 1 (length parents)]
|
||||
(. parents (- (length parents) i -1)))))
|
||||
|
||||
|
||||
{: find-symbol
|
||||
|
||||
@ -79,7 +79,8 @@ These functions are all pure functions, which makes me happy."
|
||||
(local symbol (tostring symbol))
|
||||
(if (or (= symbol ".")
|
||||
(= symbol "..")
|
||||
(= symbol "..."))
|
||||
(= symbol "...")
|
||||
(= symbol "?."))
|
||||
[symbol]
|
||||
(let [offset (or ?offset (length symbol))
|
||||
next-separator (or (symbol:find ".[.:]" offset)
|
||||
|
||||
@ -12,36 +12,66 @@
|
||||
|
||||
(local filename (.. ROOT-URI "imaginary-file.fnl"))
|
||||
|
||||
(fn check-completion [body line col expected ?unexpected]
|
||||
(local state (doto [] setup-server))
|
||||
(open-file state filename body)
|
||||
(let [response (dispatch.handle* state (completion-at filename line col))
|
||||
seen (collect [_ suggestion (ipairs (. response 1 :result))]
|
||||
suggestion.label suggestion.label)]
|
||||
(each [_ exp (ipairs expected)]
|
||||
(is.truthy (. seen exp) (.. exp " was not suggested, but should be")))
|
||||
(if ?unexpected
|
||||
(each [_ exp (ipairs ?unexpected)]
|
||||
(is.nil (. seen exp) (.. exp " was suggested, but shouldn't be"))))))
|
||||
|
||||
(describe "completions"
|
||||
(it "suggests globals"
|
||||
(local state (doto [] setup-server))
|
||||
;; empty file
|
||||
(open-file state filename "(")
|
||||
(let [response (dispatch.handle* state (completion-at filename 0 1))]
|
||||
;; TODO fix this test. Write a helper that will search a table and ensure at least one value matches.
|
||||
(is-matching response
|
||||
(where
|
||||
[{:result
|
||||
[{:label a}
|
||||
{:label b}
|
||||
{:label c}]}]
|
||||
(. _G a)
|
||||
(. _G b)
|
||||
(. _G c))
|
||||
"oops")))
|
||||
(check-completion "(" 0 1 [:_G :debug :table :io :getmetatable :setmetatable :_VERSION :ipairs :pairs :next]))
|
||||
|
||||
(it "suggests locals in scope"
|
||||
(local state (doto [] setup-server))
|
||||
(open-file state filename "(local x 10)\n(print )")
|
||||
(let [response (dispatch.handle* state (completion-at filename 1 7))]
|
||||
(var seen-suggestion false)
|
||||
(each [_ suggestion (ipairs (. response 1 :result))]
|
||||
(if (= suggestion.label :x)
|
||||
(set seen-suggestion true)))
|
||||
(assert seen-suggestion "x was not suggested"))))
|
||||
(check-completion "(local x 10)\n(print )" 1 7 [:x]))
|
||||
|
||||
;; (it "treats things in a call position differently")
|
||||
(it "suggests locals in scope at the top level"
|
||||
(check-completion "(local x 10)\n\n" 1 0 [:x]))
|
||||
|
||||
(it "suggests more locals in scope"
|
||||
(check-completion "(let [x 10] (let [y 100] \n nil\n ))" 2 4 [:x :y]))
|
||||
|
||||
(it "suggests specials and macros at beginning of list"
|
||||
(check-completion "()" 0 1 [:do :let :fn :doto :-> :-?>> :?.])
|
||||
(check-completion "(d)" 0 3 [:do :doto]))
|
||||
|
||||
(it "suggests macros in scope"
|
||||
(check-completion "(macro funny [] `nil)\n()" 1 1 [:funny])))
|
||||
|
||||
;; ;; Compiler hardening
|
||||
;; (it "works without requiring the close parentheses"))
|
||||
;; (it "works without a body in the `let`"))
|
||||
;; (it "does not suggest locals out of scope")
|
||||
;; (it "suggests items from the previous definitions in the same `let`")
|
||||
|
||||
;; ;; Functions
|
||||
;; (it "suggests function arguments at the top scope of the function")
|
||||
;; (it "suggests function arguments deep within the function")
|
||||
|
||||
;; ;; 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 without a symbol")
|
||||
;; (it "does not suggest locals past the suggestion point at the top level")
|
||||
;; (it "does not suggest items from later definitions in the same `let`")
|
||||
;; (it "does not suggest macros defined from later definitions")
|
||||
|
||||
;; ;; Call ordering rules
|
||||
|
||||
;; (it "doesn't suggest specials in the middle of a list (open paren required)"
|
||||
;; (check-completion "(do )"
|
||||
;; 0 4 [] [:do :let :fn :-> :-?>> :?.])))
|
||||
|
||||
|
||||
;; (it "doesn't suggest specials at the very top level")
|
||||
;; (it "doesn't suggest macros in the middle of a list (open paren required)")
|
||||
;; (it "doesn't suggest macros at the very top level")
|
||||
|
||||
;; (it "suggests fields of tables")
|
||||
;; (it "suggests known fn fields of tables when using a method call multisym")
|
||||
;; (it "suggests known fn keys when using the `:` special")
|
||||
|
||||
@ -29,6 +29,8 @@
|
||||
(is.same ["a" "b" "c" "d" "e" "f"] (utils.multi-sym-split "a.b.c.d.e.f"))
|
||||
(is.same ["obj" "bar"] (utils.multi-sym-split (fennel.sym "obj.bar")))))
|
||||
|
||||
(describe "utf8") ;; TODO
|
||||
|
||||
(describe "find-symbol"
|
||||
(it "finds a symbol and parents"
|
||||
(local state (doto [] setup-server))
|
||||
@ -38,7 +40,7 @@
|
||||
(is.equal symbol (fennel.sym :sym-one))
|
||||
(is-matching
|
||||
;; awful way to check AST equality, but I don't mind
|
||||
parents [[[:match] [1 2 4] [1 2 [:sym-one]] [:sym-one]] [1 2 [:sym-one]]]
|
||||
parents [[1 2 [:sym-one]] [[:match] [1 2 4] [1 2 [:sym-one]] [:sym-one]]]
|
||||
"bad parents"))
|
||||
|
||||
(it "finds nothing, but still gives parents"
|
||||
@ -48,7 +50,5 @@
|
||||
(local (symbol parents) (language.find-symbol file.ast 18))
|
||||
(is.equal symbol nil)
|
||||
(is-matching
|
||||
parents [[[:match] [1 2 4] [1 2 [:sym-one]] [:sym-one]] [1 2 [:sym-one]]]
|
||||
parents [[1 2 [:sym-one]] [[:match] [1 2 4] [1 2 [:sym-one]] [:sym-one]]]
|
||||
"bad parents")))
|
||||
|
||||
;; TODO parents for failed forms
|
||||
|
||||
Loading…
Reference in New Issue
Block a user