Add documentSymbol support
Implements LSP documentSymbol request to provide a list of all symbols defined in the current document. Clients use it to show an outline and allow to jump to the symbol's definition.
This commit is contained in:
parent
53426c0a3e
commit
9ea6a0965b
@ -1,6 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
### Features
|
||||
* Add `documentSymbol` support, for jumping to symbols definitions in the current document
|
||||
* Add `--fix` command-line argument to automatically apply lint fixes
|
||||
* Add `:legacy-multival` and `legacy-multival-case` lints, disabled by default
|
||||
* Code action "Expand macro" lets you see what a macro expands to
|
||||
|
||||
@ -38,7 +38,7 @@ find the definition `10`, but if `opts.stop-early?` is set, it would find
|
||||
{:binding z :definition y}, referring to the `(local z y)` binding.
|
||||
"
|
||||
|
||||
(local {: sym? : list? : sequence? : varg?} (require :fennel))
|
||||
(local {: sym? : multi-sym? : list? : sequence? : varg?} (require :fennel))
|
||||
(local {: get-ast-info &as utils} (require :fennel-ls.utils))
|
||||
(local files (require :fennel-ls.files))
|
||||
(local docs (require :fennel-ls.docs))
|
||||
@ -301,6 +301,29 @@ initialization-opts: {:stack ?list[ast]
|
||||
(fcollect [i 1 (length parents)]
|
||||
(. parents (- (length parents) i -1)))))
|
||||
|
||||
(λ find-document-symbols [server file]
|
||||
"Find all the symbols defined in the file
|
||||
|
||||
returns a sequential table of tables containing each symbol and its definition."
|
||||
(compiler.compile server file)
|
||||
(let [symbols []]
|
||||
(each [symbol definition (pairs file.definitions)]
|
||||
(when (and (or (sym? symbol) (multi-sym? symbol))
|
||||
definition.binding
|
||||
(. file.lexical symbol) ; exclude gensyms
|
||||
(not (= (tostring symbol) "_")))
|
||||
(table.insert symbols {: symbol : definition}))
|
||||
; definitions doesn't have multi-syms so we get them out of fields
|
||||
(when definition.fields
|
||||
(each [_field-name field-definition (pairs definition.fields)]
|
||||
(when (and field-definition.binding
|
||||
(or (sym? field-definition.binding)
|
||||
(multi-sym? field-definition.binding)))
|
||||
(table.insert symbols
|
||||
{:symbol field-definition.binding
|
||||
:definition field-definition})))))
|
||||
symbols))
|
||||
|
||||
(λ find-nearest-call [server file byte]
|
||||
"Find the nearest call
|
||||
|
||||
@ -334,6 +357,7 @@ returns the called symbol and the number of the argument closest to byte"
|
||||
(search server file symbol {:stop-early? true} {:byte ?byte})))
|
||||
|
||||
{: find-symbol
|
||||
: find-document-symbols
|
||||
: find-nearest-call
|
||||
: find-nearest-definition
|
||||
: find-definition
|
||||
|
||||
@ -43,7 +43,7 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
;; :implementationProvider nil
|
||||
:referencesProvider {:workDoneProgress false}
|
||||
:documentHighlightProvider {:workDoneProgress false}
|
||||
;; :documentSymbolProvider nil
|
||||
:documentSymbolProvider {:workDoneProgress false}
|
||||
:codeActionProvider {:workDoneProgress false}
|
||||
;; :codeLensProvider nil
|
||||
;; :documentLinkProvider nil
|
||||
@ -159,6 +159,11 @@ Every time the client sends a message, it gets handled by a function in the corr
|
||||
:range (message.ast->range server file symbol)})
|
||||
(catch _ nil))))
|
||||
|
||||
(λ requests.textDocument/documentSymbol [server _send {:textDocument {: uri}}]
|
||||
(let [file (files.get-by-uri server uri)
|
||||
symbols (analyzer.find-document-symbols server file)]
|
||||
(message.document-symbol-format server file symbols)))
|
||||
|
||||
(set {:textDocument/completion requests.textDocument/completion
|
||||
:completionItem/resolve requests.completionItem/resolve}
|
||||
(require :fennel-ls.completion))
|
||||
|
||||
@ -43,6 +43,12 @@ LSP json objects."
|
||||
:WARN :warning
|
||||
_ (string.lower k))))
|
||||
|
||||
(local symbol-kind
|
||||
{:File 1 :Module 2 :Namespace 3 :Package 4 :Class 5 :Method 6 :Property 7
|
||||
:Field 8 :Constructor 9 :Enum 10 :Interface 11 :Function 12 :Variable 13
|
||||
:Constant 14 :String 15 :Number 16 :Boolean 17 :Array 18 :Object 19 :Key 20
|
||||
:Null 21 :EnumMember 22 :Struct 23 :Event 24 :Operator 25 :TypeParameter 26})
|
||||
|
||||
(λ create-error [code message ?id ?data]
|
||||
{:jsonrpc "2.0"
|
||||
:id ?id
|
||||
@ -124,6 +130,35 @@ LSP json objects."
|
||||
{:type (. severity msg-type)
|
||||
: message}))
|
||||
|
||||
(λ definition->symbol-kind [definition]
|
||||
(let [def definition.definition]
|
||||
(if (fennel.list? def)
|
||||
(let [head (. def 1)]
|
||||
(if (or (fennel.sym? head :fn)
|
||||
(fennel.sym? head :lambda)
|
||||
(fennel.sym? head :λ))
|
||||
symbol-kind.Function
|
||||
symbol-kind.Variable))
|
||||
symbol-kind.Variable)))
|
||||
|
||||
(λ document-symbol-format [server file symbols]
|
||||
(let [symbols (icollect [_ {: symbol : definition} (ipairs symbols)]
|
||||
(let [name (tostring symbol)
|
||||
kind (definition->symbol-kind definition)
|
||||
range (ast->range server file definition.binding)]
|
||||
(when range
|
||||
{: name
|
||||
: kind
|
||||
: range
|
||||
:selectionRange range})))]
|
||||
; the spec doesn't define an order, and not all clients sort the results
|
||||
(table.sort symbols
|
||||
(fn [a b]
|
||||
(or (< a.range.start.line b.range.start.line)
|
||||
(and (= a.range.start.line b.range.start.line)
|
||||
(< a.range.start.character b.range.start.character)))))
|
||||
symbols))
|
||||
|
||||
{: create-notification
|
||||
: create-request
|
||||
: create-response
|
||||
@ -137,4 +172,6 @@ LSP json objects."
|
||||
: severity
|
||||
: severity->string
|
||||
: show-message
|
||||
: unknown-range}
|
||||
: unknown-range
|
||||
: document-symbol-format
|
||||
: symbol-kind}
|
||||
|
||||
@ -1,18 +1,8 @@
|
||||
(local faith (require :faith))
|
||||
(local {: create-client} (require :test.utils))
|
||||
(local {: create-client : range-comparator} (require :test.utils))
|
||||
(local {: null} (require :dkjson))
|
||||
(local {: view} (require :fennel))
|
||||
|
||||
(fn range-comparator [a b]
|
||||
(or (< a.range.start.line b.range.start.line)
|
||||
(and (= a.range.start.line b.range.start.line)
|
||||
(or (< a.range.start.character b.range.start.character)
|
||||
(and (= a.range.start.character b.range.start.character)
|
||||
(or (< a.range.end.line b.range.end.line)
|
||||
(and (= a.range.end.line b.range.end.line)
|
||||
(or (< a.range.end.character b.range.end.character)
|
||||
(= a.range.end.character b.range.end.character)))))))))
|
||||
|
||||
(fn check [file-contents]
|
||||
(let [{: client : uri : cursor : highlights} (create-client file-contents)
|
||||
[response] (client:document-highlight uri cursor)]
|
||||
|
||||
146
test/document-symbol.fnl
Normal file
146
test/document-symbol.fnl
Normal file
@ -0,0 +1,146 @@
|
||||
(local faith (require :faith))
|
||||
(local {: create-client : range-comparator} (require :test.utils))
|
||||
(local {: null} (require :dkjson))
|
||||
|
||||
(fn check [file-contents expected-symbols]
|
||||
(let [{: client : uri} (create-client file-contents)
|
||||
[response] (client:document-symbol uri)]
|
||||
(if (not= null response.result)
|
||||
(do
|
||||
(table.sort response.result range-comparator)
|
||||
(faith.= expected-symbols response.result))
|
||||
(faith.= expected-symbols []))))
|
||||
|
||||
(fn test-simple-locals []
|
||||
(check "(local x 10)"
|
||||
[{:name "x"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}
|
||||
:selectionRange {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}}])
|
||||
|
||||
(check "(local y 20)
|
||||
(local z 30)"
|
||||
[{:name "y"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}
|
||||
:selectionRange {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}}
|
||||
{:name "z"
|
||||
:kind 13
|
||||
:range {:start {:line 1 :character 17}
|
||||
:end {:line 1 :character 18}}
|
||||
:selectionRange {:start {:line 1 :character 17}
|
||||
:end {:line 1 :character 18}}}])
|
||||
nil)
|
||||
|
||||
(fn test-functions []
|
||||
(check "(fn my-func [])"
|
||||
[{:name "my-func"
|
||||
:kind 12
|
||||
:range {:start {:line 0 :character 4}
|
||||
:end {:line 0 :character 11}}
|
||||
:selectionRange {:start {:line 0 :character 4}
|
||||
:end {:line 0 :character 11}}}])
|
||||
|
||||
(check "(λ another-fn [x] x)"
|
||||
[{:name "another-fn"
|
||||
:kind 12
|
||||
:range {:start {:line 0 :character 4}
|
||||
:end {:line 0 :character 14}}
|
||||
:selectionRange {:start {:line 0 :character 4}
|
||||
:end {:line 0 :character 14}}}
|
||||
{:name "x"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 16}
|
||||
:end {:line 0 :character 17}}
|
||||
:selectionRange {:start {:line 0 :character 16}
|
||||
:end {:line 0 :character 17}}}])
|
||||
nil)
|
||||
|
||||
(fn test-mixed []
|
||||
(check "(local x 10)
|
||||
(fn my-func [y] (+ x y))"
|
||||
[{:name "x"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}
|
||||
:selectionRange {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}}
|
||||
{:name "my-func"
|
||||
:kind 12
|
||||
:range {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 21}}
|
||||
:selectionRange {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 21}}}
|
||||
{:name "y"
|
||||
:kind 13
|
||||
:range {:start {:line 1 :character 23}
|
||||
:end {:line 1 :character 24}}
|
||||
:selectionRange {:start {:line 1 :character 23}
|
||||
:end {:line 1 :character 24}}}])
|
||||
nil)
|
||||
|
||||
(fn test-multi-sym-functions []
|
||||
(check "(local M {})
|
||||
(fn M.my-func [x] x)"
|
||||
[{:name "M"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}
|
||||
:selectionRange {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 8}}}
|
||||
{:name "M.my-func"
|
||||
:kind 12
|
||||
:range {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 23}}
|
||||
:selectionRange {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 23}}}
|
||||
{:name "x"
|
||||
:kind 13
|
||||
:range {:start {:line 1 :character 25}
|
||||
:end {:line 1 :character 26}}
|
||||
:selectionRange {:start {:line 1 :character 25}
|
||||
:end {:line 1 :character 26}}}])
|
||||
|
||||
(check "(local module {})
|
||||
(fn module.func1 [])
|
||||
(fn module.func2 [a b])"
|
||||
[{:name "module"
|
||||
:kind 13
|
||||
:range {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 13}}
|
||||
:selectionRange {:start {:line 0 :character 7}
|
||||
:end {:line 0 :character 13}}}
|
||||
{:name "module.func1"
|
||||
:kind 12
|
||||
:range {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 26}}
|
||||
:selectionRange {:start {:line 1 :character 14}
|
||||
:end {:line 1 :character 26}}}
|
||||
{:name "module.func2"
|
||||
:kind 12
|
||||
:range {:start {:line 2 :character 14}
|
||||
:end {:line 2 :character 26}}
|
||||
:selectionRange {:start {:line 2 :character 14}
|
||||
:end {:line 2 :character 26}}}
|
||||
{:name "a"
|
||||
:kind 13
|
||||
:range {:start {:line 2 :character 28}
|
||||
:end {:line 2 :character 29}}
|
||||
:selectionRange {:start {:line 2 :character 28}
|
||||
:end {:line 2 :character 29}}}
|
||||
{:name "b"
|
||||
:kind 13
|
||||
:range {:start {:line 2 :character 30}
|
||||
:end {:line 2 :character 31}}
|
||||
:selectionRange {:start {:line 2 :character 30}
|
||||
:end {:line 2 :character 31}}}])
|
||||
nil)
|
||||
|
||||
{: test-simple-locals
|
||||
: test-functions
|
||||
: test-mixed
|
||||
: test-multi-sym-functions}
|
||||
@ -22,6 +22,7 @@
|
||||
:test.completion
|
||||
:test.references
|
||||
:test.document-highlight
|
||||
:test.document-symbol
|
||||
:test.signature-help
|
||||
:test.lint
|
||||
:test.code-action
|
||||
|
||||
@ -1,20 +1,8 @@
|
||||
(local faith (require :faith))
|
||||
(local {: create-client} (require :test.utils))
|
||||
(local {: create-client : location-comparator} (require :test.utils))
|
||||
(local {: null} (require :dkjson))
|
||||
(local {: view} (require :fennel))
|
||||
|
||||
(fn location-comparator [a b]
|
||||
(or (< a.uri b.uri)
|
||||
(and (= a.uri b.uri)
|
||||
(or (< a.range.start.line b.range.start.line)
|
||||
(and (= a.range.start.line b.range.start.line)
|
||||
(or (< a.range.start.character b.range.start.character)
|
||||
(and (= a.range.start.character b.range.start.character)
|
||||
(or (< a.range.end.line b.range.end.line)
|
||||
(and (= a.range.end.line b.range.end.line)
|
||||
(or (< a.range.end.character b.range.end.character)
|
||||
(= a.range.end.character b.range.end.character)))))))))))
|
||||
|
||||
(fn check [file-contents]
|
||||
(let [{: client : uri : cursor : locations} (create-client file-contents)
|
||||
[response] (client:references uri cursor)]
|
||||
|
||||
@ -60,6 +60,11 @@
|
||||
{: position
|
||||
:textDocument {:uri file}})))
|
||||
|
||||
(fn document-symbol [self file]
|
||||
(dispatch.handle* self.server
|
||||
(message.create-request (next-id! self) :textDocument/documentSymbol
|
||||
{:textDocument {:uri file}})))
|
||||
|
||||
(fn signature-help [self file position]
|
||||
(dispatch.handle*
|
||||
self.server
|
||||
@ -105,6 +110,7 @@
|
||||
: hover
|
||||
: references
|
||||
: document-highlight
|
||||
: document-symbol
|
||||
: signature-help
|
||||
: rename
|
||||
: code-action
|
||||
|
||||
@ -104,7 +104,24 @@
|
||||
(fn position-past-end-of-text [text ?encoding]
|
||||
(utils.byte->position text (+ (length text) 1) (or ?encoding default-encoding)))
|
||||
|
||||
(fn range-comparator [a b]
|
||||
(or (< a.range.start.line b.range.start.line)
|
||||
(and (= a.range.start.line b.range.start.line)
|
||||
(or (< a.range.start.character b.range.start.character)
|
||||
(and (= a.range.start.character b.range.start.character)
|
||||
(or (< a.range.end.line b.range.end.line)
|
||||
(and (= a.range.end.line b.range.end.line)
|
||||
(or (< a.range.end.character b.range.end.character)
|
||||
(= a.range.end.character b.range.end.character)))))))))
|
||||
|
||||
(fn location-comparator [a b]
|
||||
(or (< a.uri b.uri)
|
||||
(and (= a.uri b.uri)
|
||||
(range-comparator a b))))
|
||||
|
||||
{: create-client
|
||||
: position-past-end-of-text
|
||||
: parse-markup
|
||||
: range-comparator
|
||||
: location-comparator
|
||||
: NIL}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user