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."
(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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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