More progress on the language server

This commit is contained in:
XeroOl 2022-08-01 13:09:05 -05:00
parent db59350a1c
commit 680e4a6485
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
14 changed files with 12082 additions and 129 deletions

View File

@ -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

6206
fennel Executable file

File diff suppressed because it is too large Load Diff

5671
fennel.lua Normal file

File diff suppressed because it is too large Load Diff

View File

@ -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)]

View File

@ -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"}))))

View 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")

View File

@ -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)

View File

@ -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")))

View 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}

View 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
View 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}

View File

@ -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}

View 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
View 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}