Implement textDocument/documentSymbol feature

This commit is contained in:
silvanshade 2022-06-15 15:08:15 -06:00
parent 01b836d598
commit db38b5ed74
No known key found for this signature in database
4 changed files with 135 additions and 45 deletions

View file

@ -17,6 +17,7 @@ console_error_panic_hook = "0.1.7"
dashmap = "5.3.4"
demo-lsp-language = { version = "0.0", path = "../language" }
futures = "0.3.21"
indoc = "1.0"
js-sys = "0.3.57"
log = "0.4"
lsp = { version = "0.93", package = "lsp-types" }
@ -29,6 +30,7 @@ tree-sitter = { package = "tree-sitter-facade", version = "0.4" }
wasm-bindgen = "0.2.80"
wasm-bindgen-futures = { version = "0.4.30", features = ["futures-core-03-stream"] }
wasm-streams = "0.2.3"
web-tree-sitter-sys = "0.6"
[dependencies.web-sys]
version = "0.3.57"

View file

@ -1,6 +1,8 @@
pub mod text_document {
use std::sync::Arc;
use lsp_text::RopeExt;
pub async fn did_open(
session: Arc<crate::core::Session>,
params: lsp::DidOpenTextDocumentParams,
@ -38,4 +40,129 @@ pub mod text_document {
session.client()?.publish_diagnostics(uri, diagnostics, version).await;
Ok(())
}
pub async fn document_symbol(
session: Arc<crate::core::Session>,
params: lsp::DocumentSymbolParams,
) -> anyhow::Result<Option<lsp::DocumentSymbolResponse>> {
use wasm_bindgen::JsCast;
fn make_symbol(
uri: &lsp::Url,
content: &ropey::Rope,
declaration: tree_sitter::Node,
identifier: tree_sitter::Node,
kind: lsp::SymbolKind,
) -> lsp::SymbolInformation {
let name = content.utf8_text_for_tree_sitter_node(&identifier).into();
let range = content.tree_sitter_range_to_lsp_range(declaration.range());
#[allow(deprecated)]
lsp::SymbolInformation {
name,
kind,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location::new(uri.clone(), range),
container_name: Default::default(),
}
}
let uri = &params.text_document.uri;
let text = session.get_text(uri).await?;
let content = &text.content;
let tree = session.get_tree(uri).await?;
let tree = tree.lock().await.clone();
// NOTE: transmutes here because we do not yet support query functionality in
// tree-sitter-facade; thus we use the raw bindings from web-tree-sitter-sys.
#[allow(unsafe_code)]
let node = unsafe { std::mem::transmute::<_, web_tree_sitter_sys::SyntaxNode>(tree.root_node()) };
#[allow(unsafe_code)]
let language = unsafe { std::mem::transmute::<_, web_tree_sitter_sys::Language>(session.language.clone()) };
static QUERY: &str = indoc::indoc! {r"
(function_declaration
name: (identifier) @identifier) @function_declaration
(lexical_declaration
(variable_declarator
name: (identifier) @identifier)) @class_declaration
(variable_declaration
(variable_declarator
name: (identifier) @identifier)) @variable_declaration
(class_declaration
name: (identifier) @identifier) @class_declaration
"};
let query = language.query(&QUERY.into()).expect("failed to create query");
let matches = {
let start_position = None;
let end_position = None;
query
.matches(&node, start_position, end_position)
.into_vec()
.into_iter()
.map(JsCast::unchecked_into::<web_tree_sitter_sys::QueryMatch>)
};
let mut symbols = vec![];
for r#match in matches {
let captures = r#match
.captures()
.into_vec()
.into_iter()
.map(JsCast::unchecked_into::<web_tree_sitter_sys::QueryCapture>)
.collect::<Vec<_>>();
if let [declaration, identifier] = captures.as_slice() {
// NOTE: reverse the transmutes from above so we can use tree-sitter-facade bindings for Node
#[allow(unsafe_code)]
let declaration_node = unsafe { std::mem::transmute::<_, tree_sitter::Node>(declaration.node()) };
#[allow(unsafe_code)]
let identifier_node = unsafe { std::mem::transmute::<_, tree_sitter::Node>(identifier.node()) };
match String::from(declaration.name()).as_str() {
"function_declaration" => {
symbols.push(make_symbol(
uri,
content,
declaration_node,
identifier_node,
lsp::SymbolKind::FUNCTION,
));
},
"lexical_declaration" => {
symbols.push(make_symbol(
uri,
content,
declaration_node,
identifier_node,
lsp::SymbolKind::VARIABLE,
));
},
"variable_declaration" => {
symbols.push(make_symbol(
uri,
content,
declaration_node,
identifier_node,
lsp::SymbolKind::VARIABLE,
));
},
"class_declaration" => {
symbols.push(make_symbol(
uri,
content,
declaration_node,
identifier_node,
lsp::SymbolKind::VARIABLE,
));
},
_ => {},
}
}
}
Ok(Some(lsp::DocumentSymbolResponse::Flat(symbols)))
}
}

View file

@ -81,46 +81,8 @@ impl LanguageServer for Server {
params: lsp::DocumentSymbolParams,
) -> jsonrpc::Result<Option<lsp::DocumentSymbolResponse>> {
web_sys::console::log_1(&"server::document_symbol".into());
let _params = params;
let _session = self.session.clone();
let uri = lsp::Url::parse("inmemory://model.fs").expect("failed to parse url");
Ok(Some(lsp::DocumentSymbolResponse::Flat(vec![
#[allow(deprecated)]
lsp::SymbolInformation {
name: "foo".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(0, 0), lsp::Position::new(1, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
#[allow(deprecated)]
lsp::SymbolInformation {
name: "bar".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(2, 0), lsp::Position::new(3, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
#[allow(deprecated)]
lsp::SymbolInformation {
name: "baz".into(),
kind: lsp::SymbolKind::FUNCTION,
tags: Default::default(),
deprecated: Default::default(),
location: lsp::Location {
range: lsp::Range::new(lsp::Position::new(4, 0), lsp::Position::new(5, 1)),
uri: uri.clone(),
},
container_name: Default::default(),
},
])))
let session = self.session.clone();
let result = crate::handler::text_document::document_symbol(session, params).await;
Ok(result.map_err(crate::core::IntoJsonRpcError)?)
}
}

View file

@ -35,10 +35,9 @@ export default class App {
const value = `
function foo() {
}
function bar() {
}
function baz() {
}
const bar = 42;
var baz;
class Qux {}
`.replace(/^\s*\n/gm, "");
const id = language.id;
const uri = monaco.Uri.parse("inmemory://demo.js");