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
|
# Changelog
|
||||||
|
|
||||||
### Features
|
### Features
|
||||||
|
* Add `documentSymbol` support, for jumping to symbols definitions in the current document
|
||||||
* Add `--fix` command-line argument to automatically apply lint fixes
|
* Add `--fix` command-line argument to automatically apply lint fixes
|
||||||
* Add `:legacy-multival` and `legacy-multival-case` lints, disabled by default
|
* Add `:legacy-multival` and `legacy-multival-case` lints, disabled by default
|
||||||
* Code action "Expand macro" lets you see what a macro expands to
|
* 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.
|
{: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 {: get-ast-info &as utils} (require :fennel-ls.utils))
|
||||||
(local files (require :fennel-ls.files))
|
(local files (require :fennel-ls.files))
|
||||||
(local docs (require :fennel-ls.docs))
|
(local docs (require :fennel-ls.docs))
|
||||||
@ -301,6 +301,29 @@ initialization-opts: {:stack ?list[ast]
|
|||||||
(fcollect [i 1 (length parents)]
|
(fcollect [i 1 (length parents)]
|
||||||
(. parents (- (length parents) i -1)))))
|
(. 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-nearest-call [server file byte]
|
||||||
"Find the nearest call
|
"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})))
|
(search server file symbol {:stop-early? true} {:byte ?byte})))
|
||||||
|
|
||||||
{: find-symbol
|
{: find-symbol
|
||||||
|
: find-document-symbols
|
||||||
: find-nearest-call
|
: find-nearest-call
|
||||||
: find-nearest-definition
|
: find-nearest-definition
|
||||||
: find-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
|
;; :implementationProvider nil
|
||||||
:referencesProvider {:workDoneProgress false}
|
:referencesProvider {:workDoneProgress false}
|
||||||
:documentHighlightProvider {:workDoneProgress false}
|
:documentHighlightProvider {:workDoneProgress false}
|
||||||
;; :documentSymbolProvider nil
|
:documentSymbolProvider {:workDoneProgress false}
|
||||||
:codeActionProvider {:workDoneProgress false}
|
:codeActionProvider {:workDoneProgress false}
|
||||||
;; :codeLensProvider nil
|
;; :codeLensProvider nil
|
||||||
;; :documentLinkProvider 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)})
|
:range (message.ast->range server file symbol)})
|
||||||
(catch _ nil))))
|
(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
|
(set {:textDocument/completion requests.textDocument/completion
|
||||||
:completionItem/resolve requests.completionItem/resolve}
|
:completionItem/resolve requests.completionItem/resolve}
|
||||||
(require :fennel-ls.completion))
|
(require :fennel-ls.completion))
|
||||||
|
|||||||
@ -43,6 +43,12 @@ LSP json objects."
|
|||||||
:WARN :warning
|
:WARN :warning
|
||||||
_ (string.lower k))))
|
_ (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]
|
(λ create-error [code message ?id ?data]
|
||||||
{:jsonrpc "2.0"
|
{:jsonrpc "2.0"
|
||||||
:id ?id
|
:id ?id
|
||||||
@ -124,6 +130,35 @@ LSP json objects."
|
|||||||
{:type (. severity msg-type)
|
{:type (. severity msg-type)
|
||||||
: message}))
|
: 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-notification
|
||||||
: create-request
|
: create-request
|
||||||
: create-response
|
: create-response
|
||||||
@ -137,4 +172,6 @@ LSP json objects."
|
|||||||
: severity
|
: severity
|
||||||
: severity->string
|
: severity->string
|
||||||
: show-message
|
: show-message
|
||||||
: unknown-range}
|
: unknown-range
|
||||||
|
: document-symbol-format
|
||||||
|
: symbol-kind}
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
(local faith (require :faith))
|
(local faith (require :faith))
|
||||||
(local {: create-client} (require :test.utils))
|
(local {: create-client : range-comparator} (require :test.utils))
|
||||||
(local {: null} (require :dkjson))
|
(local {: null} (require :dkjson))
|
||||||
(local {: view} (require :fennel))
|
(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]
|
(fn check [file-contents]
|
||||||
(let [{: client : uri : cursor : highlights} (create-client file-contents)
|
(let [{: client : uri : cursor : highlights} (create-client file-contents)
|
||||||
[response] (client:document-highlight uri cursor)]
|
[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.completion
|
||||||
:test.references
|
:test.references
|
||||||
:test.document-highlight
|
:test.document-highlight
|
||||||
|
:test.document-symbol
|
||||||
:test.signature-help
|
:test.signature-help
|
||||||
:test.lint
|
:test.lint
|
||||||
:test.code-action
|
:test.code-action
|
||||||
|
|||||||
@ -1,20 +1,8 @@
|
|||||||
(local faith (require :faith))
|
(local faith (require :faith))
|
||||||
(local {: create-client} (require :test.utils))
|
(local {: create-client : location-comparator} (require :test.utils))
|
||||||
(local {: null} (require :dkjson))
|
(local {: null} (require :dkjson))
|
||||||
(local {: view} (require :fennel))
|
(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]
|
(fn check [file-contents]
|
||||||
(let [{: client : uri : cursor : locations} (create-client file-contents)
|
(let [{: client : uri : cursor : locations} (create-client file-contents)
|
||||||
[response] (client:references uri cursor)]
|
[response] (client:references uri cursor)]
|
||||||
|
|||||||
@ -60,6 +60,11 @@
|
|||||||
{: position
|
{: position
|
||||||
:textDocument {:uri file}})))
|
: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]
|
(fn signature-help [self file position]
|
||||||
(dispatch.handle*
|
(dispatch.handle*
|
||||||
self.server
|
self.server
|
||||||
@ -105,6 +110,7 @@
|
|||||||
: hover
|
: hover
|
||||||
: references
|
: references
|
||||||
: document-highlight
|
: document-highlight
|
||||||
|
: document-symbol
|
||||||
: signature-help
|
: signature-help
|
||||||
: rename
|
: rename
|
||||||
: code-action
|
: code-action
|
||||||
|
|||||||
@ -104,7 +104,24 @@
|
|||||||
(fn position-past-end-of-text [text ?encoding]
|
(fn position-past-end-of-text [text ?encoding]
|
||||||
(utils.byte->position text (+ (length text) 1) (or ?encoding default-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
|
{: create-client
|
||||||
: position-past-end-of-text
|
: position-past-end-of-text
|
||||||
: parse-markup
|
: parse-markup
|
||||||
|
: range-comparator
|
||||||
|
: location-comparator
|
||||||
: NIL}
|
: NIL}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user