diff --git a/src/fennel-ls.fnl b/src/fennel-ls.fnl index 87e6f20..6b3b61d 100644 --- a/src/fennel-ls.fnl +++ b/src/fennel-ls.fnl @@ -1,4 +1,4 @@ -(local core (require :fennel-ls.core)) +(local dispatch (require :fennel-ls.dispatch)) (local json-rpc (require :fennel-ls.json-rpc)) (λ main-loop [in out] @@ -6,7 +6,7 @@ (local state []) (while true (let [msg (json-rpc.read in)] - (core.handle state send msg)))) + (dispatch.handle state send msg)))) (λ main [] (main-loop diff --git a/src/fennel-ls/analyze.fnl b/src/fennel-ls/compiler.fnl similarity index 58% rename from src/fennel-ls/analyze.fnl rename to src/fennel-ls/compiler.fnl index 6957674..dc2b39a 100644 --- a/src/fennel-ls/analyze.fnl +++ b/src/fennel-ls/compiler.fnl @@ -1,10 +1,4 @@ (local fennel (require :fennel)) -(local fennelutils (require :fennel.utils)) -(local utils (require :fennel-ls.utils)) -(local insert table.insert) - -(local sym? fennel.sym?) -(local list? fennel.list?) ;; words surrounded by - are symbols, ;; because fennel doesn't allow 'require in a runtime file @@ -13,32 +7,6 @@ (local -λ- (fennel.sym :λ)) (local -lambda- (fennel.sym :lambda)) -(λ contains? [?ast byte] - ;; check if an ast contains a byte - (and (= (type ?ast) :table) - (utils.get-ast-info ?ast :bytestart) - (utils.get-ast-info ?ast :byteend) - (<= (utils.get-ast-info ?ast :bytestart) - byte - (+ 1 (utils.get-ast-info ?ast :byteend))))) - -(λ does-not-contain? [?ast byte] - ;; check if a byte is in range of the ast - (and (= (type ?ast) :table) - (utils.get-ast-info ?ast :bytestart) - (utils.get-ast-info ?ast :byteend) - (not - (<= (utils.get-ast-info ?ast :bytestart) - byte - (+ 1 (utils.get-ast-info ?ast :byteend)))))) - - -(λ past? [?ast byte] - ;; check if a byte is past an ast object - (and (= (type ?ast) :table) - (utils.get-ast-info ?ast :bytestart) - (< byte (utils.get-ast-info ?ast :bytestart)) - false)) (λ multisym? [t] ;; check if t is a symbol with multiple parts, eg. foo.bar.baz @@ -61,7 +29,7 @@ (tset self key val) val))}) -(λ analyze [file] +(λ compile [file] "Compile the file, and record all the useful information from the compiler into the file object" (local references []) @@ -84,6 +52,7 @@ ;; Add a definition to the definitions ;; recursively explore the binding (which, in the general case, is a destructuring assignment) ;; right now I'm not keeping track of *how* the symbol was destructured: just finding all the symbols for now. + ;; also, there's no logic for (values) (λ recurse [binding] (if (fennel.sym? binding) (tset (. definitions scope) @@ -156,68 +125,5 @@ (set file.references references) ;; (set file.definitions definitions) ;; not needed yet (set file.ast ast)) - ;; (set file.analyzed? true)) - -(var - (search-assignment - search-symbol - search) - nil) - -(set search-assignment - (λ search-assignment [self file binding ?definition stack] - (if (= 0 (length stack)) - binding - ;; TODO sift down the binding - (search self file ?definition stack)))) - -(set search-symbol - (λ search-symbol [self file symbol stack] - (let [split (utils.multi-sym-split symbol)] - (for [i (length split) 2 -1] - (table.insert stack (. split i)))) - (match (. file.references symbol) - to (search-assignment self file to.binding to.definition stack) - nil nil))) - -(set search - (λ search [self file item stack] - (if (fennelutils.table? item) - (if (. item (. stack (length stack))) - (search self file (. item (table.remove stack)) stack) - nil) - (sym? item) - (search-symbol self file item stack) - ;; TODO - ;; functioncall (continue searching in body) - ;; require call (search in the new module) - :else (error (.. "I don't know what to do with " (fennel.view item)))))) - -(λ find-symbol [ast byte ?recursively-called] - (if (not= :table (type ast)) - nil - (does-not-contain? ast byte) - nil - (sym? ast) - ast - (or (not ?recursively-called) - (fennel.list? ast) - (fennel.sequence? ast)) - ;; TODO binary search - (accumulate - [result nil - _ v (ipairs ast) - &until (or result (past? v byte))] - (find-symbol v byte true)) - :else - (accumulate - [result nil - k v (pairs ast) - &until result] - (or - (find-symbol k byte true) - (find-symbol v byte true))))) - -{: analyze - : find-symbol - : search-symbol} + ;; (set file.compiled? true)) +{: compile} diff --git a/src/fennel-ls/core.fnl b/src/fennel-ls/dispatch.fnl similarity index 99% rename from src/fennel-ls/core.fnl rename to src/fennel-ls/dispatch.fnl index ea1e854..aeae09d 100644 --- a/src/fennel-ls/core.fnl +++ b/src/fennel-ls/dispatch.fnl @@ -1,4 +1,4 @@ -"core +"Dispatch This module is responsible for deciding which code to call in response to a given LSP request from the client. diff --git a/src/fennel-ls/handlers.fnl b/src/fennel-ls/handlers.fnl index 9b2b5aa..2e1d001 100644 --- a/src/fennel-ls/handlers.fnl +++ b/src/fennel-ls/handlers.fnl @@ -1,14 +1,13 @@ -"Big dispatch +"Handlers You finally made it. Here is the main code that implements the language server protocol Every time the client sends a message, it gets handled by a function in the corresponding table type. (ie, a textDocument/didChange notification will call notifications.textDocument/didChange and a textDocument/defintion request will call requests.textDocument/didChange)" -(local utils (require :fennel-ls.utils)) -(local message (require :fennel-ls.message)) - +(local {: pos->byte : apply-changes} (require :fennel-ls.utils)) +(local message (require :fennel-ls.message)) (local state (require :fennel-ls.state)) -(local analyze (require :fennel-ls.analyze)) +(local language (require :fennel-ls.language)) (local requests []) (local notifications []) @@ -61,17 +60,17 @@ Every time the client sends a message, it gets handled by a function in the corr (λ requests.textDocument/definition [self send {: position :textDocument {: uri}}] (local file (state.get-by-uri self uri)) - (local byte (utils.pos->byte file.text position.line position.character)) - (match (analyze.find-symbol file.ast byte) + (local byte (pos->byte file.text position.line position.character)) + (match (language.find-symbol file.ast byte) symbol - (match (analyze.search-symbol self file symbol []) + (match (language.search-symbol self file symbol []) definition {:range (message.range file.text definition) :uri uri}))) (λ notifications.textDocument/didChange [self send {: contentChanges :textDocument {: uri}}] (local file (state.get-by-uri self uri)) (assert file.open?) - (utils.apply-changes (. self.files uri) contentChanges)) + (apply-changes (. self.files uri) contentChanges)) (λ notifications.textDocument/didOpen [self send {:textDocument {: languageId : text : uri}}] (local file (state.set-uri-contents self uri text)) diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl new file mode 100644 index 0000000..899d7bf --- /dev/null +++ b/src/fennel-ls/language.fnl @@ -0,0 +1,104 @@ +(local fennel (require :fennel)) +(local fennelutils (require :fennel.utils)) +(local utils (require :fennel-ls.utils)) +(local state (require :fennel-ls.state)) + +(local get-ast-info utils.get-ast-info) + +(local sym? fennel.sym?) +(local list? fennel.list?) + +(var + (search-assignment + search-symbol + search) + nil) + +(set search-assignment + (λ search-assignment [self file binding ?definition stack] + (if (= 0 (length stack)) + binding + ;; TODO sift down the binding + (search self file ?definition stack)))) + +(set search-symbol + (λ search-symbol [self file symbol stack] + (let [split (utils.multi-sym-split symbol)] + (for [i (length split) 2 -1] + (table.insert stack (. split i)))) + (match (. file.references symbol) + to (search-assignment self file to.binding to.definition stack) + nil nil))) + +(set search + (λ search [self file item stack] + (if + (fennelutils.table? item) + (if (. item (. stack (length stack))) + (search self file (. item (table.remove stack)) stack) + nil) + (sym? item) + (search-symbol self file item stack) + ;; TODO + ;; functioncall (continue searching in body with parameters bound) + (match item + [-require- mod] + (let [newfile (state.get-by-module self mod) + newitem (. newfile.ast (length newfile.ast))] + (print newfile.uri mod) + (search self newfile newitem stack)) + _ (error (.. "I don't know what to do with " (fennel.view item))))))) + +(λ past? [?ast byte] + ;; check if a byte is past an ast object + (and (= (type ?ast) :table) + (get-ast-info ?ast :bytestart) + (< byte (get-ast-info ?ast :bytestart)) + false)) + +(λ contains? [?ast byte] + ;; check if an ast contains a byte + (and (= (type ?ast) :table) + (get-ast-info ?ast :bytestart) + (get-ast-info ?ast :byteend) + (<= (get-ast-info ?ast :bytestart) + byte + (+ 1 (utils.get-ast-info ?ast :byteend))))) + +(λ does-not-contain? [?ast byte] + ;; check if a byte is in range of the ast + (and (= (type ?ast) :table) + (get-ast-info ?ast :bytestart) + (get-ast-info ?ast :byteend) + (not + (<= (get-ast-info ?ast :bytestart) + byte + (+ 1 (get-ast-info ?ast :byteend)))))) + +(λ find-symbol [ast byte ?recursively-called] + (if (not= :table (type ast)) + nil + (does-not-contain? ast byte) + nil + (and (sym? ast) (contains? ast byte)) + ast + (or (not ?recursively-called) + (fennel.list? ast) + (fennel.sequence? ast)) + ;; TODO binary search + (accumulate + [result nil + _ v (ipairs ast) + &until (or result (past? v byte))] + (find-symbol v byte true)) + :else + (accumulate + [result nil + k v (pairs ast) + &until result] + (or + (find-symbol k byte true) + (find-symbol v byte true))))) + +{: find-symbol + : search-symbol} diff --git a/src/fennel-ls/message.fnl b/src/fennel-ls/message.fnl index 34b3758..daff2d4 100644 --- a/src/fennel-ls/message.fnl +++ b/src/fennel-ls/message.fnl @@ -1,9 +1,11 @@ "Message -Here are all the constructors for the various JSON-RPC responses -that may need to be sent to the client. +Here are all the message constructor helpers for the various +LSP and JSON-RPC responses that may need to be sent to the client. + +I have them all here because I have a feeling I am conflating +missing fields with null fields, and I want to have one location +to look to fix this in the future." -I have them all here because I have a feeling I am conflating missing fields with null fields, -and I want to have one location to look to fix this in the future." (local utils (require :fennel-ls.utils)) (local error-codes diff --git a/src/fennel-ls/state.fnl b/src/fennel-ls/state.fnl index 06854d5..0661d0a 100644 --- a/src/fennel-ls/state.fnl +++ b/src/fennel-ls/state.fnl @@ -1,7 +1,7 @@ (local utils (require :fennel-ls.utils)) (local searcher (require :fennel-ls.searcher)) -(local {: analyze} (require :fennel-ls.analyze)) +(local {: compile} (require :fennel-ls.compiler)) (λ init-state [self params] (set self.files {}) @@ -16,19 +16,26 @@ (λ get-by-uri [self uri] (or (. self.files uri) (let [file (read-file uri)] - (analyze file) + (compile file) (tset self.files uri file) file))) (λ get-by-module [self module] + ;; check the cache (match (. self.modules module) - uri (or (get-by-uri self uri) - ;; if the cached uri isn't found, clear the cache and try again - (do (tset self.modules module nil) - (get-by-module self module))) - nil (let [uri (searcher.lookup self module)] - (tset self.modules module uri) - (get-by-uri self uri)))) + uri + (or (get-by-uri self uri) + ;; if the cached uri isn't found, clear the cache and try again + (do (tset self.modules module nil) + (get-by-module self module))) + nil + (match (searcher.lookup self module) + uri + (do + (tset self.modules module uri) + (get-by-uri self uri)) + nil + (error (.. "cannot find module " module))))) (λ set-uri-contents [self uri text] (if (. self.files uri) @@ -36,12 +43,12 @@ (let [file (. self.files uri)] (when (not= text file.text) (set file.text text) - (analyze file) + (compile file) file)) ;; create new file (let [file {: uri : text}] (tset self.files uri file) - (analyze file) + (compile file) file))) diff --git a/test/goto-definition-test.fnl b/test/goto-definition-test.fnl index 1056104..57e177a 100644 --- a/test/goto-definition-test.fnl +++ b/test/goto-definition-test.fnl @@ -5,14 +5,14 @@ (local {: ROOT-URI : setup-server} (require :test.util)) -(local core (require :fennel-ls.core)) +(local dispatch (require :fennel-ls.dispatch)) (local message (require :fennel-ls.message)) (describe "jump to definition" (fn check [request-file line char response-file start-line start-col end-line end-col] (local state (doto [] setup-server)) - (let [message (core.handle* state + (let [message (dispatch.handle* state (message.create-request 2 "textDocument/definition" {:position {:character char :line line} :textDocument {:uri (.. ROOT-URI "/" request-file)}})) @@ -47,13 +47,13 @@ (check "example.fnl" 21 9 "example.fnl" 16 13 16 16)) (it "can go to a function inside a table" - (check "example.fnl" 28 6 "example.fnl" 4 4 4 7))) + (check "example.fnl" 28 6 "example.fnl" 4 4 4 7)) - ;; (it "handles (local _ (require XXX)" - ;; (check "example.fnl" 0 11 "foo.fnl" 0 0 0 0)) + (it "handles (local _ (require XXX))" + (check "example.fnl" 0 10 "foo.fnl" 0 0 0 0)) - ;; (it "handles (require XXX))" - ;; (check "example.fnl" 1 5 "bar.fnl" 0 0 0 0)) + (it "handles (require XXX))" + (check "example.fnl" 1 5 "bar.fnl" 0 0 0 0))) ;; (it "can go to a field inside of a table") ;; (it "can go to a destructured function argument") diff --git a/test/lsp-test.fnl b/test/lsp-test.fnl index 6a03cae..57739f9 100644 --- a/test/lsp-test.fnl +++ b/test/lsp-test.fnl @@ -2,7 +2,7 @@ (local is (require :luassert)) (local {: ROOT-PATH : ROOT-URI} (require :test.util)) -(local core (require :fennel-ls.core)) +(local dispatch (require :fennel-ls.dispatch)) (local server-initialize-message {:id 1 @@ -22,7 +22,7 @@ (describe "language server" (it "responds to initialize" (is-matching - (core.handle* [] server-initialize-message) + (dispatch.handle* [] server-initialize-message) [{:jsonrpc "2.0" :id 1 :result {:capabilities {} :serverInfo {:name "fennel-ls" : version}}}]))) diff --git a/test/util.fnl b/test/util.fnl index b6a2dbb..5f3c803 100644 --- a/test/util.fnl +++ b/test/util.fnl @@ -1,4 +1,4 @@ -(local core (require :fennel-ls.core)) +(local dispatch (require :fennel-ls.dispatch)) (local ROOT-PATH (-> (io.popen "pwd") @@ -24,6 +24,6 @@ :uri ROOT-URI}]}}) (fn setup-server [state] - (core.handle* state initialization-message)) + (dispatch.handle* state initialization-message)) {: ROOT-URI : setup-server}