mirror of
https://github.com/denoland/deno.git
synced 2024-11-25 15:29:32 -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)),
|
references_provider: Some(OneOf::Left(true)),
|
||||||
document_highlight_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,
|
workspace_symbol_provider: None,
|
||||||
code_action_provider: Some(code_action_provider),
|
code_action_provider: Some(code_action_provider),
|
||||||
code_lens_provider: Some(CodeLensOptions {
|
code_lens_provider: Some(CodeLensOptions {
|
||||||
|
|
|
@ -718,6 +718,49 @@ impl Inner {
|
||||||
self.performance.measure(mark);
|
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(
|
async fn formatting(
|
||||||
&self,
|
&self,
|
||||||
params: DocumentFormattingParams,
|
params: DocumentFormattingParams,
|
||||||
|
@ -2165,6 +2208,13 @@ impl lspower::LanguageServer for LanguageServer {
|
||||||
self.0.lock().await.did_change_watched_files(params).await
|
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(
|
async fn formatting(
|
||||||
&self,
|
&self,
|
||||||
params: DocumentFormattingParams,
|
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]
|
#[tokio::test]
|
||||||
async fn test_folding_range() {
|
async fn test_folding_range() {
|
||||||
let mut harness = LspTestHarness::new(vec![
|
let mut harness = LspTestHarness::new(vec![
|
||||||
|
|
|
@ -41,7 +41,7 @@ use std::collections::HashSet;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::{borrow::Cow, cmp};
|
use std::{borrow::Cow, cmp};
|
||||||
use std::{collections::HashMap, path::Path};
|
use std::{collections::HashMap, path::Path};
|
||||||
use text_size::TextSize;
|
use text_size::{TextRange, TextSize};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use tokio::sync::oneshot;
|
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)
|
pub fn walk<F>(&self, callback: &F)
|
||||||
where
|
where
|
||||||
F: Fn(&NavigationTree, Option<&NavigationTree>),
|
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