More progress on the language server
This commit is contained in:
parent
db59350a1c
commit
680e4a6485
11
Makefile
11
Makefile
@ -1,14 +1,13 @@
|
||||
LUA_LIB=/usr/lib/liblua.so.5.4
|
||||
LUA_INCLUDE_PATH=$(shell lua5.4 -e 'print(package.cpath:match("[^;]+"))')
|
||||
LUA_INCLUDE_DIR=/usr/include/lua5.4
|
||||
SOURCES=$(wildcard src/*.fnl)
|
||||
SOURCES+=$(wildcard src/fennel-ls/*.fnl)
|
||||
|
||||
.PHONY: test
|
||||
|
||||
fennel-ls: $(SOURCES)
|
||||
FENNEL_PATH=src/?.fnl fennel --compile-binary src/fennel-ls.fnl fennel-ls $(LUA_LIB) $(LUA_INCLUDE_PATH)
|
||||
|
||||
LUA_PATH="./?.lua;./?/init.lua" FENNEL_PATH="src/?.fnl;src/?/init.fnl" ./fennel --compile-binary src/fennel-ls.fnl fennel-ls $(LUA_LIB) $(LUA_INCLUDE_DIR)
|
||||
clean:
|
||||
rm -f fennel-ls
|
||||
test:
|
||||
fennel --correlate test/init.fnl
|
||||
|
||||
|
||||
./fennel --correlate test/init.fnl
|
||||
|
||||
5671
fennel.lua
Normal file
5671
fennel.lua
Normal file
File diff suppressed because it is too large
Load Diff
@ -4,7 +4,7 @@
|
||||
(local {: log} (require :fennel-ls.log))
|
||||
|
||||
(λ main-loop [in out]
|
||||
(local send #(json-rpc.write out $))
|
||||
(local send (partial json-rpc.write out))
|
||||
(local state [])
|
||||
(while true
|
||||
(let [msg (json-rpc.read in)]
|
||||
|
||||
@ -1,79 +0,0 @@
|
||||
(import-macros {: assert-matches : describe : it} :test.macros)
|
||||
(local assert (require :luassert))
|
||||
|
||||
(local fennel (require :fennel))
|
||||
(local stringx (require :pl.stringx))
|
||||
|
||||
(local document (require :fennel-ls.document))
|
||||
|
||||
(local ROOT-URI
|
||||
(.. "file://"
|
||||
(-> (io.popen "pwd")
|
||||
(: :read :*a)
|
||||
(stringx.strip))))
|
||||
|
||||
(local FILE-URI (.. ROOT-URI "/test_document"))
|
||||
|
||||
(describe "document"
|
||||
(describe "create-from-disk"
|
||||
(it "opens documents from disk"
|
||||
(local uri (.. ROOT-URI "/test/init.fnl"))
|
||||
(local test-fnl-document (document.create-from-disk uri))
|
||||
(assert (stringx.startswith test-fnl-document.text "((require :busted.runner))")))
|
||||
|
||||
(it "crashes on bad file"
|
||||
(assert.errors #(document.create-from-disk "fill://my/path/here"))
|
||||
(assert.errors #(document.create-from-disk "file:///this/path/hopefully/does/not/exist/on/the/host/system&^$!@#%"))))
|
||||
|
||||
(describe "create-from-contents"
|
||||
(it "opens documents from fixed contents"
|
||||
(local uri (.. ROOT-URI "/test_document"))
|
||||
(assert-matches
|
||||
(document.create-from-contents uri "line 1\nline 2\nline 3")
|
||||
{:text "line 1\nline 2\nline 3"})))
|
||||
|
||||
(describe "sub"
|
||||
(it "updates the start of a line"
|
||||
(local my-document (document.create FILE-URI "replace beginning"))
|
||||
(document.replace my-document 0 0 0 7 "the")
|
||||
(assert-matches my-document {:text "the beginning"}))
|
||||
|
||||
(it "updates the end of a line"
|
||||
(local my-document (document.create FILE-URI "first line\nsecond line\nreplace end"))
|
||||
(document.replace my-document 2 7 2 11 "ment")
|
||||
(assert-matches my-document {:text "first line\nsecond line\nreplacement"}))
|
||||
|
||||
(it "replaces a line"
|
||||
(local my-document (document.create FILE-URI "replace all"))
|
||||
(document.replace my-document 0 0 0 11 "new string")
|
||||
(assert-matches my-document {:text "new string"})))
|
||||
|
||||
;; fixme:
|
||||
;; test for errors on out of bounds
|
||||
;; test for multiline edits
|
||||
;; test for unicode utf8 utf16 nightmare
|
||||
|
||||
;; (it "can handle unicode"
|
||||
;; (local uri (.. ROOT-URI "test_document"))
|
||||
;; (local my-document (document.create uri ""))
|
||||
;; (document.replace my-document 0 0 0 0 "どれみふぁそらてぃど")
|
||||
;; (document.replace my-document 0 1 0 3 "😀")
|
||||
;; (document.replace my-document 0 11 0 11 "end")
|
||||
;; (assert-matches my-document {:text "ど😀ふぁそらてぃどend"})))
|
||||
|
||||
(describe "apply-changes"
|
||||
(it "can handle substituting things"
|
||||
(local my-document (document.create FILE-URI "replace beginning"))
|
||||
(document.apply-changes
|
||||
my-document
|
||||
[{:range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 7}}
|
||||
:text "the"}])
|
||||
(assert-matches my-document {:text "the beginning"}))
|
||||
|
||||
(it "can handle replacing everything"
|
||||
(local my-document (document.create FILE-URI "this is the\nold file"))
|
||||
(document.apply-changes
|
||||
my-document
|
||||
[{:text "And this is the\nnew file"}])
|
||||
(assert-matches my-document {:text "And this is the\nnew file"}))))
|
||||
67
test/goto-definition-test.fnl
Normal file
67
test/goto-definition-test.fnl
Normal file
@ -0,0 +1,67 @@
|
||||
(import-macros {: assert-matches : describe : it : before-each} :test.macros)
|
||||
(local assert (require :luassert))
|
||||
|
||||
(local fennel (require :fennel))
|
||||
(local {: ROOT-URI
|
||||
: setup-server} (require :test.util))
|
||||
|
||||
(local dispatch (require :fennel-ls.dispatch))
|
||||
(local message (require :fennel-ls.message))
|
||||
|
||||
(describe "jump to definition"
|
||||
|
||||
(var state nil)
|
||||
|
||||
(before-each
|
||||
(set state [])
|
||||
(setup-server state))
|
||||
|
||||
(fn request-definition-at [line char file]
|
||||
(message.create-request 2 "textDocument/definition"
|
||||
{:position {:character char :line line}
|
||||
:textDocument {:uri (.. ROOT-URI "/" file)}}))
|
||||
|
||||
(it "handles (local _ (require XXX)"
|
||||
(local uri (.. ROOT-URI "/" "foo.fnl"))
|
||||
(assert-matches
|
||||
(dispatch.handle* state (request-definition-at 0 11 "example.fnl"))
|
||||
[{:jsonrpc "2.0" :id 2
|
||||
:result {: uri :range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}}}]))
|
||||
|
||||
(it "handles (require XXX))"
|
||||
(local uri (.. ROOT-URI "/" "bar.fnl"))
|
||||
(assert-matches
|
||||
(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}}}}])))
|
||||
|
||||
;; 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 open a require with a custom fennelpath")
|
||||
;; (it "can go to a fn")
|
||||
;; (it "can go to a local")
|
||||
;; (it "can go to a table and its field")
|
||||
;; (it "can go to a destructured local")
|
||||
;; (it "can go to a table field in another file")
|
||||
;; (it "can go to a table field in another file (through a destructuring assignment)")
|
||||
;; (it "can go to a field in a lua file")
|
||||
;; (it "finds the definition of macros")
|
||||
;; (it "can go through more than one extra file")
|
||||
;; (it "will give up on recursive requires")
|
||||
;; (it "can follow import-macros")
|
||||
|
||||
;; (describe "diagnostic")
|
||||
;; (it "reports compiler errors")
|
||||
;; (it "reports lint warnings")
|
||||
|
||||
;; (describe "completion")
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
((require :busted.runner))
|
||||
(tset (require :fennel) :path "./?.fnl;./src/?.fnl")
|
||||
(require :test.json-rpc-test)
|
||||
(require :test.document-test)
|
||||
(require :test.string-processing-test)
|
||||
(require :test.lsp-test)
|
||||
(require :test.goto-definition-test)
|
||||
|
||||
@ -31,46 +31,7 @@
|
||||
(it "responds to initialize"
|
||||
(assert-matches
|
||||
(dispatch.handle* [] server-initialize-message)
|
||||
[{:id 1
|
||||
:jsonrpc "2.0"
|
||||
[{:jsonrpc "2.0" :id 1
|
||||
:result {:capabilities {}
|
||||
:serverInfo {:name "fennel-ls" : version}}}]))
|
||||
:serverInfo {:name "fennel-ls" : version}}}])))
|
||||
|
||||
(describe "jump to definition"
|
||||
(it "handles (local _ (require XXX)"
|
||||
(local state [])
|
||||
(dispatch.handle* state server-initialize-message)
|
||||
(assert-matches
|
||||
(dispatch.handle* state
|
||||
{:id 2
|
||||
:jsonrpc "2.0"
|
||||
:method "textDocument/definition"
|
||||
:params {:position {:character 11 :line 0}
|
||||
:textDocument {:uri (.. ROOT-URI "/example.fnl")}}})
|
||||
(where [{:id 2
|
||||
:jsonrpc "2.0"
|
||||
:result {: uri :range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}}}]
|
||||
(stringx.endswith uri "foo.fnl"))))
|
||||
|
||||
(it "handles (require XXX))"
|
||||
(local state [])
|
||||
(dispatch.handle* state server-initialize-message)
|
||||
(assert-matches
|
||||
(dispatch.handle* state
|
||||
{:id 2
|
||||
:jsonrpc "2.0"
|
||||
:method "textDocument/definition"
|
||||
:params {:position {:character 5 :line 1}
|
||||
:textDocument {:uri (.. ROOT-URI "/example.fnl")}}})
|
||||
(where [{:id 2
|
||||
:jsonrpc "2.0"
|
||||
:result {: uri :range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 0}}}}]
|
||||
(stringx.endswith uri "bar.fnl"))))))
|
||||
|
||||
;; (it "can go to a fn")
|
||||
;; (it "can go to a local")
|
||||
;; (it "can go to a table and its field")
|
||||
;; (it "can go to a destructured local")
|
||||
;; (it "can go to a table field in another file")))
|
||||
|
||||
@ -10,6 +10,12 @@
|
||||
`((. (require :busted) :describe)
|
||||
,desc (fn [] ,desc ,...)))
|
||||
|
||||
(fn before-each [...]
|
||||
"busted's `describe` function"
|
||||
`((. (require :busted) :before_each)
|
||||
(fn [] ,...)))
|
||||
|
||||
|
||||
(fn assert-matches [item pattern]
|
||||
"check if item matches a pattern according to fennel's `match` builtin"
|
||||
`(match ,item
|
||||
@ -24,4 +30,5 @@
|
||||
|
||||
{: it
|
||||
: describe
|
||||
: assert-matches}
|
||||
: assert-matches
|
||||
: before-each}
|
||||
|
||||
68
test/string-processing-test.fnl
Normal file
68
test/string-processing-test.fnl
Normal file
@ -0,0 +1,68 @@
|
||||
(import-macros {: assert-matches : describe : it} :test.macros)
|
||||
(local assert (require :luassert))
|
||||
|
||||
(local fennel (require :fennel))
|
||||
(local stringx (require :pl.stringx))
|
||||
|
||||
(local util (require :fennel-ls.util))
|
||||
|
||||
(describe "document"
|
||||
|
||||
;; fixme:
|
||||
;; test for errors on out of bounds
|
||||
;; test for multiline edits
|
||||
;; test for unicode utf8 utf16 nightmare
|
||||
|
||||
;; (it "can handle unicode"
|
||||
;; (local uri (.. ROOT-URI "test_document"))
|
||||
;; (local my-document (document.create uri ""))
|
||||
;; (document.replace my-document 0 0 0 0 "どれみふぁそらてぃど")
|
||||
;; (document.replace my-document 0 1 0 3 "😀")
|
||||
;; (document.replace my-document 0 11 0 11 "end")
|
||||
;; (assert-matches my-document {:text "ど😀ふぁそらてぃどend"})))
|
||||
|
||||
(describe "apply-changes"
|
||||
|
||||
(fn range [start-line start-col end-line end-col]
|
||||
{:start {:line start-line :character start-col}
|
||||
:end {:line end-line :character end-col}})
|
||||
|
||||
(it "updates the start of a line"
|
||||
(assert.equal
|
||||
(util.apply-changes
|
||||
"replace beginning"
|
||||
[{:range (range 0 0 0 7)
|
||||
:text "the"}])
|
||||
"the beginning"))
|
||||
|
||||
(it "updates the end of a line"
|
||||
(assert.equal
|
||||
(util.apply-changes
|
||||
"first line\nsecond line\nreplace end"
|
||||
[{:range (range 2 7 2 11)
|
||||
:text "ment"}])
|
||||
"first line\nsecond line\nreplacement"))
|
||||
|
||||
(it "replaces a line"
|
||||
(assert.equal
|
||||
(util.apply-changes
|
||||
"replace all"
|
||||
[{:range (range 0 0 0 11)
|
||||
:text "new string"}])
|
||||
"new string"))
|
||||
|
||||
(it "can handle substituting things"
|
||||
(assert.equal
|
||||
(util.apply-changes
|
||||
"replace beginning"
|
||||
[{:range {:start {:line 0 :character 0}
|
||||
:end {:line 0 :character 7}}
|
||||
:text "the"}])
|
||||
"the beginning"))
|
||||
|
||||
(it "can handle replacing everything"
|
||||
(assert.equal
|
||||
(util.apply-changes
|
||||
"this is the\nold file"
|
||||
[{:text "And this is the\nnew file"}])
|
||||
"And this is the\nnew file"))))
|
||||
11
test/test-project/baz.fnl
Normal file
11
test/test-project/baz.fnl
Normal file
@ -0,0 +1,11 @@
|
||||
|
||||
(fn bazfn []
|
||||
(print "you called bazfn"))
|
||||
|
||||
(fn unused []
|
||||
(print "this function is unused"))
|
||||
|
||||
(fn unused2 []
|
||||
(print "this function is unused, but also exported. Tricky!"))
|
||||
|
||||
{: bazfn : unused2}
|
||||
@ -1,8 +1,13 @@
|
||||
(local foo (require :foo))
|
||||
(require :bar)
|
||||
(local {: bazfn} (require :baz))
|
||||
|
||||
(fn bar [a b]
|
||||
(print a b)
|
||||
(foo.my-export))
|
||||
|
||||
(bar 1 2)
|
||||
|
||||
(print bazfn)
|
||||
|
||||
{: bar}
|
||||
|
||||
6
test/test-project/file-with-compile-error.fnl
Normal file
6
test/test-project/file-with-compile-error.fnl
Normal file
@ -0,0 +1,6 @@
|
||||
;; this file isn't used in any tests yet
|
||||
|
||||
(let [foo true]
|
||||
(an-unknown-mystery-global)) ;; the global should be an error, but ideally the compiler should keep going
|
||||
|
||||
(do do) ;; this should be a compiler error, I don't mind if the compiler can't go past this one
|
||||
30
test/util.fnl
Normal file
30
test/util.fnl
Normal file
@ -0,0 +1,30 @@
|
||||
(local stringx (require :pl.stringx))
|
||||
(local dispatch (require :fennel-ls.dispatch))
|
||||
|
||||
(local ROOT-PATH
|
||||
(-> (io.popen "pwd")
|
||||
(: :read :*a)
|
||||
(stringx.strip)
|
||||
(.. "/test/test-project")))
|
||||
(local ROOT-URI
|
||||
(.. "file://" ROOT-PATH))
|
||||
|
||||
(local initialization-message
|
||||
{:id 1
|
||||
:jsonrpc "2.0"
|
||||
:method "initialize"
|
||||
:params
|
||||
{:capabilities {}
|
||||
:clientInfo {:name "Neovim" :version "0.7.2"}
|
||||
:initializationOptions {}
|
||||
:processId 16245
|
||||
:rootPath ROOT-PATH
|
||||
:rootUri ROOT-URI
|
||||
:trace "off"
|
||||
:workspaceFolders [{:name ROOT-PATH
|
||||
:uri ROOT-URI}]}})
|
||||
|
||||
(fn setup-server [state]
|
||||
(dispatch.handle* state initialization-message))
|
||||
|
||||
{: ROOT-URI : setup-server}
|
||||
Loading…
Reference in New Issue
Block a user