From 82d7913c455ff11e1fd934209103e1642c32799d Mon Sep 17 00:00:00 2001 From: XeroOl Date: Sun, 28 Aug 2022 00:40:46 -0500 Subject: [PATCH] rewrite of find-symbol in preparation for completions --- src/fennel-ls/language.fnl | 61 ++++++++++++++++++----------------- src/fennel-ls/utils.fnl | 2 +- test/completion-test.fnl | 60 ++++++++++++++++++++++------------ test/diagnostic-test.fnl | 18 ++++------- test/goto-definition-test.fnl | 2 +- test/hover-test.fnl | 2 +- test/init.fnl | 1 - test/lsp-test.fnl | 28 ---------------- test/misc-test.fnl | 48 ++++++++++++++++++++++----- test/{util.fnl => utils.fnl} | 19 ++++++++++- 10 files changed, 137 insertions(+), 104 deletions(-) delete mode 100644 test/lsp-test.fnl rename test/{util.fnl => utils.fnl} (57%) diff --git a/src/fennel-ls/language.fnl b/src/fennel-ls/language.fnl index 279ab61..b7f71f2 100644 --- a/src/fennel-ls/language.fnl +++ b/src/fennel-ls/language.fnl @@ -3,6 +3,7 @@ The high level analysis system that does deep searches following the data provided by compiler.fnl." (local {: sym? : list? : sequence? : sym : view} (require :fennel)) +(local {: table?} (require :fennel.utils)) (local utils (require :fennel-ls.utils)) (local state (require :fennel-ls.state)) @@ -109,36 +110,36 @@ the data provided by compiler.fnl." byte (+ 1 (get-ast-info ?ast :byteend)))))) -(λ find-symbol [ast byte ?stack] - (local stack (or ?stack [])) - (if (or (not= :table (type ast)) - (does-not-contain? ast byte)) - nil - (and (sym? ast) (contains? ast byte)) - (values ast []) - (or (= 0 (length stack)) - (list? ast) - (sequence? ast)) - ;; TODO binary search - (accumulate - [(result stack*) nil - _ v (ipairs ast) - &until (or result (past? v byte))] - (do - (table.insert stack ast) - (match (find-symbol v byte stack) - ret (values ret stack) - nil (do (table.remove stack) nil)))) - (accumulate - [(result stack*) nil - k v (pairs ast) - &until result] - (do - (table.insert stack ast) - (match (or (find-symbol k byte stack) - (find-symbol v byte stack)) - ret (values ret stack) - nil (do (table.remove stack) nil)))))) +(λ find-symbol [ast byte] + (local parents []) + (λ recurse [ast] + (if + (sym? ast) + (values ast parents) + (do + (table.insert parents ast) + (if + (or (sequence? ast) (list? ast)) + (accumulate [(result done) nil + i child (ipairs ast) + &until result] + (if (contains? child byte) + (recurse child byte))) + (table? ast) + (accumulate [(result done) nil + key value (pairs ast) + &until done] + (if (contains? key byte) + (recurse key byte) + (contains? value byte) + (recurse value byte))))))) + + (values + (accumulate [result nil i top-level-form (ipairs ast) &until result] + (if (contains? top-level-form byte) + (recurse top-level-form byte))) + parents)) + {: find-symbol : search-main diff --git a/src/fennel-ls/utils.fnl b/src/fennel-ls/utils.fnl index 3778ec0..c088ab9 100644 --- a/src/fennel-ls/utils.fnl +++ b/src/fennel-ls/utils.fnl @@ -73,7 +73,7 @@ These functions are all pure functions, which makes me happy." (λ get-ast-info [?ast info] ;; find a given key of info from an AST object (or (?. (getmetatable ?ast) info) - (. ?ast info))) + (?. ?ast info))) (fn multi-sym-split [symbol ?offset] (local symbol (tostring symbol)) diff --git a/test/completion-test.fnl b/test/completion-test.fnl index e00e776..a2452a4 100644 --- a/test/completion-test.fnl +++ b/test/completion-test.fnl @@ -3,34 +3,52 @@ (local {: view} (require :fennel)) (local {: ROOT-URI - : setup-server} (require :test.util)) + : open-file + : completion-at + : setup-server} (require :test.utils)) (local dispatch (require :fennel-ls.dispatch)) (local message (require :fennel-ls.message)) -(local FILENAME (.. ROOT-URI "imaginary-file.fnl")) +(local filename (.. ROOT-URI "imaginary-file.fnl")) -(fn open-file [state text] - (dispatch.handle* state - (message.create-notification "textDocument/didOpen" - {:textDocument - {:uri FILENAME - :languageId "fennel" - :version 1 - : text}}))) +(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")))) -(describe "completions") - ;; (it "suggests globals" + ;; (it "suggests locals in scope" ;; (local state (doto [] setup-server)) - ;; ;; empty file - ;; (open-file state "") - ;; (let [response (dispatch.handle* state - ;; (message.create-request 2 "textDocument/completion" - ;; {:position {:line 0 :character 0} - ;; :textDocument {:uri FILENAME}}))] - ;; (is-matching response nil "oops")))) + ;; (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")))) - ;; (it "suggests locals in scope") + ;; (it "treats things in a call position differently") ;; (it "does not suggest locals out of scope") ;; (it "suggests fields of tables") - ;; (it "knows what fields are meant to be inside of globals") + ;; (it "suggests known fn fields of tables when using a method call multisym") + ;; (it "suggests known fn keys when using the `:` special") + ;; (it "suggests known keys when using the `.` special") + ;; (it "suggests known module names in `require` and `include` and `import-macros` and `require-macros` and friends") + ;; (it "knows the fields of the standard lua library.") + ;; (it "suggests special forms for the call position of a list, but not other positions") + ;; (it "does not suggest special forms for the \"call\" position when a list isn't actually a call, ie destructuring assignment") + ;; (it "suggests keys when typing out destructuring, as in `(local {: typinghere} (require :mod))`") + ;; (it "only suggests tables for `ipairs` / begin work on type checking system") diff --git a/test/diagnostic-test.fnl b/test/diagnostic-test.fnl index 73ee148..17f0d8a 100644 --- a/test/diagnostic-test.fnl +++ b/test/diagnostic-test.fnl @@ -3,7 +3,8 @@ (local {: view} (require :fennel)) (local {: ROOT-URI - : setup-server} (require :test.util)) + : open-file + : setup-server} (require :test.utils)) (local dispatch (require :fennel-ls.dispatch)) (local message (require :fennel-ls.message)) @@ -19,19 +20,12 @@ (table.insert t result) `(accumulate ,t ,body)) -(fn open-file [state text] - (dispatch.handle* state - (message.create-notification "textDocument/didOpen" - {:textDocument - {:uri (.. ROOT-URI "imaginary-file.fnl") - :languageId "fennel" - :version 1 - : text}}))) +(local filename (.. ROOT-URI "imaginary.fnl")) (describe "diagnostic messages" (it "handles compile errors" (local state (doto [] setup-server)) - (let [responses (open-file state "(do do)") + (let [responses (open-file state filename "(do do)") diagnostic (match responses [{:params {: diagnostics}}] @@ -45,7 +39,7 @@ (it "handles parse errors" (local state (doto [] setup-server)) - (let [responses (open-file state "(do (print :hello(]") + (let [responses (open-file state filename "(do (print :hello(]") diagnostic (match responses [{:params {: diagnostics}}] @@ -59,7 +53,7 @@ (it "handles (match)" (local state (doto [] setup-server)) - (let [responses (open-file state "(match)")] + (let [responses (open-file state filename "(match)")] (is-matching responses [{:params {:diagnostics diff --git a/test/goto-definition-test.fnl b/test/goto-definition-test.fnl index 3aee2d1..4f2e7a1 100644 --- a/test/goto-definition-test.fnl +++ b/test/goto-definition-test.fnl @@ -2,7 +2,7 @@ (local is (require :luassert)) (local {: ROOT-URI - : setup-server} (require :test.util)) + : setup-server} (require :test.utils)) (local dispatch (require :fennel-ls.dispatch)) (local message (require :fennel-ls.message)) diff --git a/test/hover-test.fnl b/test/hover-test.fnl index 9db5c3c..928a76c 100644 --- a/test/hover-test.fnl +++ b/test/hover-test.fnl @@ -3,7 +3,7 @@ (local {: view} (require :fennel)) (local {: ROOT-URI - : setup-server} (require :test.util)) + : setup-server} (require :test.utils)) (local dispatch (require :fennel-ls.dispatch)) (local message (require :fennel-ls.message)) diff --git a/test/init.fnl b/test/init.fnl index 171e964..4e3d327 100644 --- a/test/init.fnl +++ b/test/init.fnl @@ -5,6 +5,5 @@ (require :test.goto-definition-test) (require :test.hover-test) (require :test.json-rpc-test) -(require :test.lsp-test) (require :test.misc-test) (require :test.string-processing-test) diff --git a/test/lsp-test.fnl b/test/lsp-test.fnl deleted file mode 100644 index 8dad205..0000000 --- a/test/lsp-test.fnl +++ /dev/null @@ -1,28 +0,0 @@ -(import-macros {: is-matching : describe : it} :test) -(local is (require :luassert)) - -(local {: ROOT-PATH : ROOT-URI} (require :test.util)) -(local dispatch (require :fennel-ls.dispatch)) - -(local server-initialize-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}]}}) - -(describe "language server" - (it "responds to initialize" - (is-matching - (dispatch.handle* [] server-initialize-message) - [{:jsonrpc "2.0" :id 1 - :result {:capabilities {} - :serverInfo {:name "fennel-ls" : version}}}]))) diff --git a/test/misc-test.fnl b/test/misc-test.fnl index e0e20aa..0fc9f4e 100644 --- a/test/misc-test.fnl +++ b/test/misc-test.fnl @@ -1,22 +1,54 @@ (import-macros {: is-matching : describe : it : before-each} :test) (local is (require :luassert)) -(local fennel (require :fennel)) -(local {: multi-sym-split} (require :fennel-ls.utils)) +(local {: view &as fennel} (require :fennel)) +(local {: setup-server + : open-file + : ROOT-URI} + (require :test.utils)) + +(local language (require :fennel-ls.language)) +(local utils (require :fennel-ls.utils)) + +(local filename (.. ROOT-URI "imaginary.fnl")) (describe "multi-sym-split" (it "should be 1 on regular syms" - (is.same ["foo"] (multi-sym-split "foo" 2))) + (is.same ["foo"] (utils.multi-sym-split "foo" 2))) (it "should be 1 before the :" - (is.same ["foo"] (multi-sym-split "foo:bar" 3))) + (is.same ["foo"] (utils.multi-sym-split "foo:bar" 3))) (it "should be 2 at the :" - (is.same ["foo" "bar"] (multi-sym-split "foo:bar" 4))) + (is.same ["foo" "bar"] (utils.multi-sym-split "foo:bar" 4))) (it "should be 2 after the :" - (is.same ["is" "equal"] (multi-sym-split "is.equal" 5))) + (is.same ["is" "equal"] (utils.multi-sym-split "is.equal" 5))) (it "should be big" - (is.same ["a" "b" "c" "d" "e" "f"] (multi-sym-split "a.b.c.d.e.f")) - (is.same ["obj" "bar"] (multi-sym-split (fennel.sym "obj.bar"))))) + (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 "find-symbol" + (it "finds a symbol and parents" + (local state (doto [] setup-server)) + (open-file state filename "(match [1 2 4] [1 2 sym-one] sym-one)") + (local file (. state.files filename)) + (local (symbol parents) (language.find-symbol file.ast 23)) + (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]]] + "bad parents")) + + (it "finds nothing, but still gives parents" + (local state (doto [] setup-server)) + (open-file state filename "(match [1 2 4] [1 2 sym-one] sym-one)") + (local file (. state.files filename)) + (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]]] + "bad parents"))) + +;; TODO parents for failed forms diff --git a/test/util.fnl b/test/utils.fnl similarity index 57% rename from test/util.fnl rename to test/utils.fnl index 5f3c803..2cd2020 100644 --- a/test/util.fnl +++ b/test/utils.fnl @@ -1,4 +1,5 @@ (local dispatch (require :fennel-ls.dispatch)) +(local message (require :fennel-ls.message)) (local ROOT-PATH (-> (io.popen "pwd") @@ -26,4 +27,20 @@ (fn setup-server [state] (dispatch.handle* state initialization-message)) -{: ROOT-URI : setup-server} +(fn open-file [state name text] + (dispatch.handle* state + (message.create-notification "textDocument/didOpen" + {:textDocument + {:uri name + :languageId "fennel" + :version 1 + : text}}))) + +(fn completion-at [file line character] + (message.create-request 2 "textDocument/completion" + {:position {: line : character} :textDocument {:uri file}})) + +{: ROOT-URI + : setup-server + : open-file + : completion-at}