rewrite of find-symbol in preparation for completions

This commit is contained in:
XeroOl 2022-08-28 00:40:46 -05:00
parent 5ac7561f05
commit 82d7913c45
No known key found for this signature in database
GPG Key ID: 9DD4B4B4DAED0322
10 changed files with 137 additions and 104 deletions

View File

@ -3,6 +3,7 @@ The high level analysis system that does deep searches following
the data provided by compiler.fnl." the data provided by compiler.fnl."
(local {: sym? : list? : sequence? : sym : view} (require :fennel)) (local {: sym? : list? : sequence? : sym : view} (require :fennel))
(local {: table?} (require :fennel.utils))
(local utils (require :fennel-ls.utils)) (local utils (require :fennel-ls.utils))
(local state (require :fennel-ls.state)) (local state (require :fennel-ls.state))
@ -109,36 +110,36 @@ the data provided by compiler.fnl."
byte byte
(+ 1 (get-ast-info ?ast :byteend)))))) (+ 1 (get-ast-info ?ast :byteend))))))
(λ find-symbol [ast byte ?stack] (λ find-symbol [ast byte]
(local stack (or ?stack [])) (local parents [])
(if (or (not= :table (type ast)) (λ recurse [ast]
(does-not-contain? ast byte)) (if
nil (sym? ast)
(and (sym? ast) (contains? ast byte)) (values ast parents)
(values ast []) (do
(or (= 0 (length stack)) (table.insert parents ast)
(list? ast) (if
(sequence? ast)) (or (sequence? ast) (list? ast))
;; TODO binary search (accumulate [(result done) nil
(accumulate i child (ipairs ast)
[(result stack*) nil &until result]
_ v (ipairs ast) (if (contains? child byte)
&until (or result (past? v byte))] (recurse child byte)))
(do (table? ast)
(table.insert stack ast) (accumulate [(result done) nil
(match (find-symbol v byte stack) key value (pairs ast)
ret (values ret stack) &until done]
nil (do (table.remove stack) nil)))) (if (contains? key byte)
(accumulate (recurse key byte)
[(result stack*) nil (contains? value byte)
k v (pairs ast) (recurse value byte)))))))
&until result]
(do (values
(table.insert stack ast) (accumulate [result nil i top-level-form (ipairs ast) &until result]
(match (or (find-symbol k byte stack) (if (contains? top-level-form byte)
(find-symbol v byte stack)) (recurse top-level-form byte)))
ret (values ret stack) parents))
nil (do (table.remove stack) nil))))))
{: find-symbol {: find-symbol
: search-main : search-main

View File

@ -73,7 +73,7 @@ These functions are all pure functions, which makes me happy."
(λ get-ast-info [?ast info] (λ get-ast-info [?ast info]
;; find a given key of info from an AST object ;; find a given key of info from an AST object
(or (?. (getmetatable ?ast) info) (or (?. (getmetatable ?ast) info)
(. ?ast info))) (?. ?ast info)))
(fn multi-sym-split [symbol ?offset] (fn multi-sym-split [symbol ?offset]
(local symbol (tostring symbol)) (local symbol (tostring symbol))

View File

@ -3,34 +3,52 @@
(local {: view} (require :fennel)) (local {: view} (require :fennel))
(local {: ROOT-URI (local {: ROOT-URI
: setup-server} (require :test.util)) : open-file
: completion-at
: setup-server} (require :test.utils))
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message)) (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] (describe "completions"
(dispatch.handle* state (it "suggests globals"
(message.create-notification "textDocument/didOpen" (local state (doto [] setup-server))
{:textDocument ;; empty file
{:uri FILENAME (open-file state filename "(")
:languageId "fennel" (let [response (dispatch.handle* state (completion-at filename 0 1))]
:version 1 ;; TODO fix this test. Write a helper that will search a table and ensure at least one value matches.
: text}}))) (is-matching response
(where
[{:result
[{:label a}
{:label b}
{:label c}]}]
(. _G a)
(. _G b)
(. _G c))
"oops"))))
(describe "completions") ;; (it "suggests locals in scope"
;; (it "suggests globals"
;; (local state (doto [] setup-server)) ;; (local state (doto [] setup-server))
;; ;; empty file ;; (open-file state filename "(local x 10)\n(print )")
;; (open-file state "") ;; (let [response (dispatch.handle* state (completion-at filename 1 7))]
;; (let [response (dispatch.handle* state ;; (var seen-suggestion false)
;; (message.create-request 2 "textDocument/completion" ;; (each [_ suggestion (ipairs (. response 1 :result))]
;; {:position {:line 0 :character 0} ;; (if (= suggestion.label :x)
;; :textDocument {:uri FILENAME}}))] ;; (set seen-suggestion true)))
;; (is-matching response nil "oops")))) ;; (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 "does not suggest locals out of scope")
;; (it "suggests fields of tables") ;; (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")

View File

@ -3,7 +3,8 @@
(local {: view} (require :fennel)) (local {: view} (require :fennel))
(local {: ROOT-URI (local {: ROOT-URI
: setup-server} (require :test.util)) : open-file
: setup-server} (require :test.utils))
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))
@ -19,19 +20,12 @@
(table.insert t result) (table.insert t result)
`(accumulate ,t ,body)) `(accumulate ,t ,body))
(fn open-file [state text] (local filename (.. ROOT-URI "imaginary.fnl"))
(dispatch.handle* state
(message.create-notification "textDocument/didOpen"
{:textDocument
{:uri (.. ROOT-URI "imaginary-file.fnl")
:languageId "fennel"
:version 1
: text}})))
(describe "diagnostic messages" (describe "diagnostic messages"
(it "handles compile errors" (it "handles compile errors"
(local state (doto [] setup-server)) (local state (doto [] setup-server))
(let [responses (open-file state "(do do)") (let [responses (open-file state filename "(do do)")
diagnostic diagnostic
(match responses (match responses
[{:params {: diagnostics}}] [{:params {: diagnostics}}]
@ -45,7 +39,7 @@
(it "handles parse errors" (it "handles parse errors"
(local state (doto [] setup-server)) (local state (doto [] setup-server))
(let [responses (open-file state "(do (print :hello(]") (let [responses (open-file state filename "(do (print :hello(]")
diagnostic diagnostic
(match responses (match responses
[{:params {: diagnostics}}] [{:params {: diagnostics}}]
@ -59,7 +53,7 @@
(it "handles (match)" (it "handles (match)"
(local state (doto [] setup-server)) (local state (doto [] setup-server))
(let [responses (open-file state "(match)")] (let [responses (open-file state filename "(match)")]
(is-matching responses (is-matching responses
[{:params [{:params
{:diagnostics {:diagnostics

View File

@ -2,7 +2,7 @@
(local is (require :luassert)) (local is (require :luassert))
(local {: ROOT-URI (local {: ROOT-URI
: setup-server} (require :test.util)) : setup-server} (require :test.utils))
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))

View File

@ -3,7 +3,7 @@
(local {: view} (require :fennel)) (local {: view} (require :fennel))
(local {: ROOT-URI (local {: ROOT-URI
: setup-server} (require :test.util)) : setup-server} (require :test.utils))
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message)) (local message (require :fennel-ls.message))

View File

@ -5,6 +5,5 @@
(require :test.goto-definition-test) (require :test.goto-definition-test)
(require :test.hover-test) (require :test.hover-test)
(require :test.json-rpc-test) (require :test.json-rpc-test)
(require :test.lsp-test)
(require :test.misc-test) (require :test.misc-test)
(require :test.string-processing-test) (require :test.string-processing-test)

View File

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

View File

@ -1,22 +1,54 @@
(import-macros {: is-matching : describe : it : before-each} :test) (import-macros {: is-matching : describe : it : before-each} :test)
(local is (require :luassert)) (local is (require :luassert))
(local fennel (require :fennel)) (local {: view &as fennel} (require :fennel))
(local {: multi-sym-split} (require :fennel-ls.utils)) (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" (describe "multi-sym-split"
(it "should be 1 on regular syms" (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 :" (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 :" (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 :" (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" (it "should be big"
(is.same ["a" "b" "c" "d" "e" "f"] (multi-sym-split "a.b.c.d.e.f")) (is.same ["a" "b" "c" "d" "e" "f"] (utils.multi-sym-split "a.b.c.d.e.f"))
(is.same ["obj" "bar"] (multi-sym-split (fennel.sym "obj.bar"))))) (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

View File

@ -1,4 +1,5 @@
(local dispatch (require :fennel-ls.dispatch)) (local dispatch (require :fennel-ls.dispatch))
(local message (require :fennel-ls.message))
(local ROOT-PATH (local ROOT-PATH
(-> (io.popen "pwd") (-> (io.popen "pwd")
@ -26,4 +27,20 @@
(fn setup-server [state] (fn setup-server [state]
(dispatch.handle* state initialization-message)) (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}