mirror of
https://github.com/denoland/deno.git
synced 2024-11-21 15:04:11 -05:00
feat(lsp): Implement textDocument/documentSymbol (#9981)
Co-authored-by: Kitson Kelly <me@kitsonkelly.com>
This commit is contained in:
parent
6d404ec54b
commit
2079da0f1c
5 changed files with 563 additions and 2 deletions
|
@ -103,7 +103,8 @@ pub fn server_capabilities(
|
|||
)),
|
||||
references_provider: Some(OneOf::Left(true)),
|
||||
document_highlight_provider: Some(OneOf::Left(true)),
|
||||
document_symbol_provider: None,
|
||||
// TODO: Provide a label once https://github.com/gluon-lang/lsp-types/pull/207 is merged
|
||||
document_symbol_provider: Some(OneOf::Left(true)),
|
||||
workspace_symbol_provider: None,
|
||||
code_action_provider: Some(code_action_provider),
|
||||
code_lens_provider: Some(CodeLensOptions {
|
||||
|
|
|
@ -718,6 +718,49 @@ impl Inner {
|
|||
self.performance.measure(mark);
|
||||
}
|
||||
|
||||
async fn document_symbol(
|
||||
&self,
|
||||
params: DocumentSymbolParams,
|
||||
) -> LspResult<Option<DocumentSymbolResponse>> {
|
||||
if !self.enabled() {
|
||||
return Ok(None);
|
||||
}
|
||||
let mark = self.performance.mark("selection_range");
|
||||
let specifier = self.url_map.normalize_url(¶ms.text_document.uri);
|
||||
|
||||
let line_index =
|
||||
if let Some(line_index) = self.get_line_index_sync(&specifier) {
|
||||
line_index
|
||||
} else {
|
||||
return Err(LspError::invalid_params(format!(
|
||||
"An unexpected specifier ({}) was provided.",
|
||||
specifier
|
||||
)));
|
||||
};
|
||||
|
||||
let req = tsc::RequestMethod::GetNavigationTree(specifier);
|
||||
let navigation_tree: tsc::NavigationTree = self
|
||||
.ts_server
|
||||
.request(self.snapshot(), req)
|
||||
.await
|
||||
.map_err(|err| {
|
||||
error!("Failed to request to tsserver {}", err);
|
||||
LspError::invalid_request()
|
||||
})?;
|
||||
|
||||
let response = if let Some(child_items) = navigation_tree.child_items {
|
||||
let mut document_symbols = Vec::<DocumentSymbol>::new();
|
||||
for item in child_items {
|
||||
item.collect_document_symbols(&line_index, &mut document_symbols);
|
||||
}
|
||||
Some(DocumentSymbolResponse::Nested(document_symbols))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.performance.measure(mark);
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
async fn formatting(
|
||||
&self,
|
||||
params: DocumentFormattingParams,
|
||||
|
@ -2165,6 +2208,13 @@ impl lspower::LanguageServer for LanguageServer {
|
|||
self.0.lock().await.did_change_watched_files(params).await
|
||||
}
|
||||
|
||||
async fn document_symbol(
|
||||
&self,
|
||||
params: DocumentSymbolParams,
|
||||
) -> LspResult<Option<DocumentSymbolResponse>> {
|
||||
self.0.lock().await.document_symbol(params).await
|
||||
}
|
||||
|
||||
async fn formatting(
|
||||
&self,
|
||||
params: DocumentFormattingParams,
|
||||
|
@ -3406,6 +3456,410 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_document_symbol() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
(
|
||||
LspFixture::Path("initialize_request.json"),
|
||||
LspResponse::RequestAny,
|
||||
),
|
||||
(
|
||||
LspFixture::Path("initialized_notification.json"),
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
LspFixture::Path("document_symbol_did_open_notification.json"),
|
||||
LspResponse::None,
|
||||
),
|
||||
(
|
||||
LspFixture::Path("document_symbol_request.json"),
|
||||
LspResponse::Request(
|
||||
2,
|
||||
json!([
|
||||
{
|
||||
"name": "bar",
|
||||
"kind": 13,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"character": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"character": 26
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 17,
|
||||
"character": 4
|
||||
},
|
||||
"end": {
|
||||
"line": 17,
|
||||
"character": 7
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Bar",
|
||||
"kind": 5,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 13,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 4,
|
||||
"character": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 4,
|
||||
"character": 9
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "constructor",
|
||||
"kind": 9,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 35
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 35
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "baz",
|
||||
"kind": 6,
|
||||
"tags": [
|
||||
1
|
||||
],
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"character": 25
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 8,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 8,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "foo",
|
||||
"kind": 6,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 6,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"character": 24
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 6,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 6,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "getStaticBar",
|
||||
"kind": 6,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 12,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 12,
|
||||
"character": 57
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 12,
|
||||
"character": 17
|
||||
},
|
||||
"end": {
|
||||
"line": 12,
|
||||
"character": 29
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "staticBar",
|
||||
"kind": 7,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 11,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"character": 32
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 11,
|
||||
"character": 9
|
||||
},
|
||||
"end": {
|
||||
"line": 11,
|
||||
"character": 18
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"kind": 7,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"character": 35
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 9,
|
||||
"character": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 9,
|
||||
"character": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "value",
|
||||
"kind": 7,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 10,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 10,
|
||||
"character": 42
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 10,
|
||||
"character": 6
|
||||
},
|
||||
"end": {
|
||||
"line": 10,
|
||||
"character": 11
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "x",
|
||||
"kind": 7,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 14
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 30
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 5,
|
||||
"character": 21
|
||||
},
|
||||
"end": {
|
||||
"line": 5,
|
||||
"character": 22
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "IFoo",
|
||||
"kind": 11,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 2,
|
||||
"character": 1
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 0,
|
||||
"character": 10
|
||||
},
|
||||
"end": {
|
||||
"line": 0,
|
||||
"character": 14
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "foo",
|
||||
"kind": 6,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 17
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 1,
|
||||
"character": 2
|
||||
},
|
||||
"end": {
|
||||
"line": 1,
|
||||
"character": 5
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Values",
|
||||
"kind": 10,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 0
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 30
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 5
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 11
|
||||
}
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"name": "value1",
|
||||
"kind": 13,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 14
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 20
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 14
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 20
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "value2",
|
||||
"kind": 13,
|
||||
"range": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 22
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 28
|
||||
}
|
||||
},
|
||||
"selectionRange": {
|
||||
"start": {
|
||||
"line": 15,
|
||||
"character": 22
|
||||
},
|
||||
"end": {
|
||||
"line": 15,
|
||||
"character": 28
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]),
|
||||
),
|
||||
),
|
||||
(
|
||||
LspFixture::Path("shutdown_request.json"),
|
||||
LspResponse::Request(3, json!(null)),
|
||||
),
|
||||
(
|
||||
LspFixture::Path("exit_notification.json"),
|
||||
LspResponse::None,
|
||||
),
|
||||
]);
|
||||
harness.run().await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn test_folding_range() {
|
||||
let mut harness = LspTestHarness::new(vec![
|
||||
|
|
|
@ -41,7 +41,7 @@ use std::collections::HashSet;
|
|||
use std::thread;
|
||||
use std::{borrow::Cow, cmp};
|
||||
use std::{collections::HashMap, path::Path};
|
||||
use text_size::TextSize;
|
||||
use text_size::{TextRange, TextSize};
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
|
@ -621,6 +621,90 @@ impl NavigationTree {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn collect_document_symbols(
|
||||
&self,
|
||||
line_index: &LineIndex,
|
||||
document_symbols: &mut Vec<lsp::DocumentSymbol>,
|
||||
) -> bool {
|
||||
let mut should_include = self.should_include_entry();
|
||||
if !should_include
|
||||
&& self.child_items.as_ref().map_or(true, |v| v.is_empty())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let children = self
|
||||
.child_items
|
||||
.as_ref()
|
||||
.map_or(&[] as &[NavigationTree], |v| v.as_slice());
|
||||
for span in self.spans.iter() {
|
||||
let range = TextRange::at(span.start.into(), span.length.into());
|
||||
let mut symbol_children = Vec::<lsp::DocumentSymbol>::new();
|
||||
for child in children.iter() {
|
||||
let should_traverse_child = child
|
||||
.spans
|
||||
.iter()
|
||||
.map(|child_span| {
|
||||
TextRange::at(child_span.start.into(), child_span.length.into())
|
||||
})
|
||||
.any(|child_range| range.intersect(child_range).is_some());
|
||||
if should_traverse_child {
|
||||
let included_child =
|
||||
child.collect_document_symbols(line_index, &mut symbol_children);
|
||||
should_include = should_include || included_child;
|
||||
}
|
||||
}
|
||||
|
||||
if should_include {
|
||||
let mut selection_span = span;
|
||||
if let Some(name_span) = self.name_span.as_ref() {
|
||||
let name_range =
|
||||
TextRange::at(name_span.start.into(), name_span.length.into());
|
||||
if range.contains_range(name_range) {
|
||||
selection_span = name_span;
|
||||
}
|
||||
}
|
||||
|
||||
let mut tags: Option<Vec<lsp::SymbolTag>> = None;
|
||||
let kind_modifiers = parse_kind_modifier(&self.kind_modifiers);
|
||||
if kind_modifiers.contains("deprecated") {
|
||||
tags = Some(vec![lsp::SymbolTag::Deprecated]);
|
||||
}
|
||||
|
||||
let children = if !symbol_children.is_empty() {
|
||||
Some(symbol_children)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// The field `deprecated` is deprecated but DocumentSymbol does not have
|
||||
// a default, therefore we have to supply the deprecated deprecated
|
||||
// field. It is like a bad version of Inception.
|
||||
#[allow(deprecated)]
|
||||
document_symbols.push(lsp::DocumentSymbol {
|
||||
name: self.text.clone(),
|
||||
kind: self.kind.clone().into(),
|
||||
range: span.to_range(line_index),
|
||||
selection_range: selection_span.to_range(line_index),
|
||||
tags,
|
||||
children,
|
||||
detail: None,
|
||||
deprecated: None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
should_include
|
||||
}
|
||||
|
||||
fn should_include_entry(&self) -> bool {
|
||||
if let ScriptElementKind::Alias = self.kind {
|
||||
return false;
|
||||
}
|
||||
|
||||
!self.text.is_empty() && self.text != "<function>" && self.text != "<class>"
|
||||
}
|
||||
|
||||
pub fn walk<F>(&self, callback: &F)
|
||||
where
|
||||
F: Fn(&NavigationTree, Option<&NavigationTree>),
|
||||
|
|
12
cli/tests/lsp/document_symbol_did_open_notification.json
Normal file
12
cli/tests/lsp/document_symbol_did_open_notification.json
Normal file
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "textDocument/didOpen",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts",
|
||||
"languageId": "typescript",
|
||||
"version": 1,
|
||||
"text": "interface IFoo {\n foo(): boolean;\n}\n\nclass Bar implements IFoo {\n constructor(public x: number) { }\n foo() { return true; }\n /** @deprecated */\n baz() { return false; }\n get value(): number { return 0; }\n set value(newVavlue: number) { return; }\n static staticBar = new Bar(0);\n private static getStaticBar() { return Bar.staticBar; }\n}\n\nenum Values { value1, value2 }\n\nvar bar: IFoo = new Bar(3);"
|
||||
}
|
||||
}
|
||||
}
|
10
cli/tests/lsp/document_symbol_request.json
Normal file
10
cli/tests/lsp/document_symbol_request.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "textDocument/documentSymbol",
|
||||
"params": {
|
||||
"textDocument": {
|
||||
"uri": "file:///a/file.ts"
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue