Created a compiler plugin, local go-to-definition
This commit is contained in:
parent
052f9a67d0
commit
4847179afd
2
Makefile
2
Makefile
@ -10,4 +10,4 @@ fennel-ls: $(SOURCES)
|
||||
clean:
|
||||
rm -f fennel-ls
|
||||
test:
|
||||
FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" ./fennel --correlate test/init.fnl
|
||||
FENNEL_PATH="./src/?.fnl;./src/?/init.fnl" ./fennel --correlate test/init.fnl --verbose
|
||||
|
||||
@ -18,20 +18,16 @@
|
||||
(and (= (type ast) :table)
|
||||
(< byte (get-ast-info ast :bytestart))))
|
||||
|
||||
(fn range [ast]
|
||||
(fn range [text ast]
|
||||
"create a LSP range representing the span of an AST object"
|
||||
(if (= (type ast) :table)
|
||||
(match (values (get-ast-info ast :bytestart) (get-ast-info ast :byteend))
|
||||
(i j)
|
||||
(let [(start-line start-col) (util.byte->pos i)
|
||||
(end-line end-col) (util.byte->pos j)]
|
||||
(let [(start-line start-col) (util.byte->pos text i)
|
||||
(end-line end-col) (util.byte->pos text j)]
|
||||
{:start {:line start-line :character start-col}
|
||||
:end {:line end-line :character end-col}}))))
|
||||
|
||||
(fn from-fennel [file]
|
||||
(icollect [k v (fennel.parser file.text file.uri)]
|
||||
v))
|
||||
|
||||
{: from-fennel
|
||||
: contains?
|
||||
: past?}
|
||||
{: contains?
|
||||
: past?
|
||||
: range}
|
||||
|
||||
84
src/fennel-ls/plugin.fnl
Normal file
84
src/fennel-ls/plugin.fnl
Normal file
@ -0,0 +1,84 @@
|
||||
(local fennel (require :fennel))
|
||||
(local insert table.insert)
|
||||
|
||||
;; words surrounded by - are symbols,
|
||||
;; because fennel doesn't allow 'require in a runtime file
|
||||
(local -require- (fennel.sym :require))
|
||||
(local -local- (fennel.sym :local))
|
||||
(local -fn- (fennel.sym :fn))
|
||||
|
||||
;; types of things in the file.references list
|
||||
{:from "a literal range" :to "a literal range"}
|
||||
{:from "a literal range" :to-other-module ["modname" "key1" "key2" "key3" "key4" "etc"]}
|
||||
|
||||
(fn table? [t]
|
||||
(= :table (type t)))
|
||||
|
||||
(fn multisym? [t]
|
||||
(and (fennel.sym? t)
|
||||
(let [t (tostring t)]
|
||||
(or (t:find "%.")
|
||||
(t:find ":")))))
|
||||
|
||||
|
||||
(λ analyze [file]
|
||||
(assert file.text (fennel.view file))
|
||||
(assert file.uri)
|
||||
(set file.references [])
|
||||
|
||||
(local scope-notes
|
||||
(doto {}
|
||||
(setmetatable
|
||||
{:__index
|
||||
(fn [self key]
|
||||
(let [val {}]
|
||||
(tset self key val)
|
||||
val))})))
|
||||
|
||||
(fn find-reference [name scope]
|
||||
(when scope
|
||||
(or (. scope-notes scope (tostring name))
|
||||
(find-reference name scope.parent))))
|
||||
|
||||
(fn call [ast scope]
|
||||
"called for every function call. Most calls aren't interesting, but (require) and (local) are"
|
||||
(match ast
|
||||
[-local- name value]
|
||||
nil ;; not actually interesting, I pranked you
|
||||
(where [-require- mod] (= :string (type mod)))
|
||||
(insert file.references {:from ast :to-other-module [mod]})))
|
||||
;; nothing
|
||||
|
||||
(fn reference [ast scope]
|
||||
"called whenever a variable is referenced"
|
||||
(assert (fennel.sym? ast))
|
||||
(let [name (or (string.match (tostring ast) "[^%.:]+"))]
|
||||
(table.insert file.references {:from ast :to (find-reference name scope)})))
|
||||
|
||||
(fn fn* [ast scope]
|
||||
(match ast
|
||||
(where [-fn- name args]
|
||||
(and (fennel.sym? name)
|
||||
(not (multisym? name))
|
||||
(table? args)))
|
||||
(tset (. scope-notes scope.parent) (tostring name) ast)))
|
||||
;; (each [_ argument (ipairs args)]))
|
||||
|
||||
(fn define [definition binding scope]
|
||||
"called whenever a local variable or destructure statement is introduced"
|
||||
(when (fennel.sym? binding) ;; for now, I am going to bury my head in the sand and ignore destructure logic
|
||||
(tset (. scope-notes scope) (tostring binding) binding)))
|
||||
|
||||
(local plugin
|
||||
{:name "fennel-ls"
|
||||
:versions ["1.2.0"]
|
||||
:symbol-to-expression reference
|
||||
: call
|
||||
:fn fn*
|
||||
:destructure define})
|
||||
|
||||
(fennel.compileString file.text
|
||||
{:filename file.uri
|
||||
:plugins [plugin]}))
|
||||
|
||||
{: analyze}
|
||||
@ -1,7 +1,7 @@
|
||||
(local util (require :fennel-ls.util))
|
||||
(local mod (require :fennel-ls.mod))
|
||||
|
||||
(λ analyze [] "TODO")
|
||||
(local {: analyze} (require :fennel-ls.plugin))
|
||||
|
||||
(λ init-state [self params]
|
||||
(set self.files {})
|
||||
|
||||
@ -68,39 +68,19 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
(local local* (fennel.sym :local))
|
||||
(λ requests.textDocument/definition [self send {: position :textDocument {: uri}}]
|
||||
(local file (state.get-by-uri self uri))
|
||||
|
||||
(set file.ast (or file.ast
|
||||
(parser.from-fennel (. self.files uri))))
|
||||
(local ast file.ast)
|
||||
|
||||
(local byte (util.pos->byte file.text position.line position.character))
|
||||
(accumulate [result nil
|
||||
_ reference (ipairs file.references) &until (or result (parser.past? reference.from byte))]
|
||||
(if (parser.contains? reference.from byte)
|
||||
(match reference
|
||||
{: from : to}
|
||||
{:range (parser.range file.text to)
|
||||
:uri file.uri}
|
||||
|
||||
(var result nil)
|
||||
(λ check [ast]
|
||||
(log (fennel.view ast))
|
||||
(each [_ item (ipairs ast) :until (or result (parser.past? item byte))]
|
||||
(log (fennel.view ast))
|
||||
(if (parser.contains? item byte)
|
||||
(match item
|
||||
(where [require* module &as l]
|
||||
(and (fennel.list? l)
|
||||
(string? module)))
|
||||
(set result module)
|
||||
(where [local* _ [require* module &as l1] &as l2]
|
||||
(and (fennel.list? l1)
|
||||
(fennel.list? l2)
|
||||
(string? module)))
|
||||
(set result module)
|
||||
(where obj (fennel.list? obj))
|
||||
(check obj)))))
|
||||
|
||||
(check ast)
|
||||
|
||||
(if result
|
||||
{:uri (mod.lookup self result)
|
||||
:range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}}))
|
||||
|
||||
{: from : to-other-module}
|
||||
{:range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}
|
||||
:uri (mod.lookup self (. to-other-module 1))}))))
|
||||
|
||||
(λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}]
|
||||
(local file (state.get-by-uri self uri))
|
||||
|
||||
@ -35,16 +35,15 @@
|
||||
(dispatch.handle* state (request-definition-at 1 5 "example.fnl"))
|
||||
[{:jsonrpc "2.0" :id 2
|
||||
:result {: uri :range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}}}])))
|
||||
:end {:line 0 :character 0}}}}]))
|
||||
|
||||
;; TODO
|
||||
;; (it "can go to a fn"
|
||||
;; (local uri (.. ROOT-URI "/" "example.fnl"))
|
||||
;; (assert-matches
|
||||
;; (dispatch.handle* state (request-definition-at 8 2 "example.fnl"))
|
||||
;; [{:jsonrpc "2.0" :id 2
|
||||
;; :result {: uri :range {:start {:line 4 :character 0}
|
||||
;; :end {:line 6 :character 17}}}}])))
|
||||
(it "can go to a fn"
|
||||
(local uri (.. ROOT-URI "/" "example.fnl"))
|
||||
(assert-matches
|
||||
(dispatch.handle* state (request-definition-at 9 3 "example.fnl"))
|
||||
[{:jsonrpc "2.0" :id 2
|
||||
:result {: uri :range {:start {:line 4 :character 0}
|
||||
:end {:line 7 :character 19}}}}])))
|
||||
|
||||
;; (it "can open a require with a custom fennelpath")
|
||||
;; (it "can go to a fn")
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
|
||||
(fn bar [a b]
|
||||
(print a b)
|
||||
(foo.my-export))
|
||||
(local c 10)
|
||||
(foo.my-export c))
|
||||
|
||||
(bar 1 2)
|
||||
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
(local constant 5)
|
||||
|
||||
(fn my-export [])
|
||||
(fn my-export [a]
|
||||
a)
|
||||
|
||||
{: my-export : constant}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user