-
-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #297 from baszalmstra/feat/document_symbol_provider2
feature(lsp): adds document symbol provider
- Loading branch information
Showing
15 changed files
with
558 additions
and
32 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
use lsp_types::{ | ||
ClientCapabilities, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, | ||
ClientCapabilities, OneOf, ServerCapabilities, TextDocumentSyncCapability, TextDocumentSyncKind, | ||
}; | ||
|
||
/// Returns the capabilities of this LSP server implementation given the capabilities of the client. | ||
pub fn server_capabilities(_client_caps: &ClientCapabilities) -> ServerCapabilities { | ||
ServerCapabilities { | ||
text_document_sync: Some(TextDocumentSyncCapability::Kind(TextDocumentSyncKind::Full)), | ||
document_symbol_provider: Some(OneOf::Left(true)), | ||
..Default::default() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
use crate::SymbolKind; | ||
use mun_syntax::{ | ||
ast::{self, NameOwner}, | ||
match_ast, AstNode, SourceFile, SyntaxNode, TextRange, WalkEvent, | ||
}; | ||
|
||
/// A description of a symbol in a source file. | ||
#[derive(Debug, Clone)] | ||
pub struct StructureNode { | ||
/// An optional parent of this symbol. Refers to the index of the symbol in the collection that | ||
/// this instance resides in. | ||
pub parent: Option<usize>, | ||
|
||
/// The text label | ||
pub label: String, | ||
|
||
/// The range to navigate to if selected | ||
pub navigation_range: TextRange, | ||
|
||
/// The entire range of the node in the file | ||
pub node_range: TextRange, | ||
|
||
/// The type of symbol | ||
pub kind: SymbolKind, | ||
|
||
/// Optional detailed information | ||
pub detail: Option<String>, | ||
} | ||
|
||
/// Provides a tree of symbols defined in a `SourceFile`. | ||
pub(crate) fn file_structure(file: &SourceFile) -> Vec<StructureNode> { | ||
let mut result = Vec::new(); | ||
let mut stack = Vec::new(); | ||
|
||
for event in file.syntax().preorder() { | ||
match event { | ||
WalkEvent::Enter(node) => { | ||
if let Some(mut symbol) = try_convert_to_structure_node(&node) { | ||
symbol.parent = stack.last().copied(); | ||
stack.push(result.len()); | ||
result.push(symbol); | ||
} | ||
} | ||
WalkEvent::Leave(node) => { | ||
if try_convert_to_structure_node(&node).is_some() { | ||
stack.pop().unwrap(); | ||
} | ||
} | ||
} | ||
} | ||
|
||
result | ||
} | ||
|
||
/// Tries to convert an ast node to something that would reside in the hierarchical file structure. | ||
fn try_convert_to_structure_node(node: &SyntaxNode) -> Option<StructureNode> { | ||
/// Create a `StructureNode` from a declaration | ||
fn decl<N: NameOwner>(node: N, kind: SymbolKind) -> Option<StructureNode> { | ||
decl_with_detail(&node, None, kind) | ||
} | ||
|
||
/// Create a `StructureNode` from a declaration with extra text detail | ||
fn decl_with_detail<N: NameOwner>( | ||
node: &N, | ||
detail: Option<String>, | ||
kind: SymbolKind, | ||
) -> Option<StructureNode> { | ||
let name = node.name()?; | ||
|
||
Some(StructureNode { | ||
parent: None, | ||
label: name.text().to_string(), | ||
navigation_range: name.syntax().text_range(), | ||
node_range: node.syntax().text_range(), | ||
kind, | ||
detail, | ||
}) | ||
} | ||
|
||
/// Given a `SyntaxNode` get the text without any whitespaces | ||
fn collapse_whitespaces(node: &SyntaxNode, output: &mut String) { | ||
let mut can_insert_ws = false; | ||
node.text().for_each_chunk(|chunk| { | ||
for line in chunk.lines() { | ||
let line = line.trim(); | ||
if line.is_empty() { | ||
if can_insert_ws { | ||
output.push(' '); | ||
can_insert_ws = false; | ||
} | ||
} else { | ||
output.push_str(line); | ||
can_insert_ws = true; | ||
} | ||
} | ||
}) | ||
} | ||
|
||
/// Given a `SyntaxNode` construct a `StructureNode` by referring to the type of a node. | ||
fn decl_with_type_ref<N: NameOwner>( | ||
node: &N, | ||
type_ref: Option<ast::TypeRef>, | ||
kind: SymbolKind, | ||
) -> Option<StructureNode> { | ||
let detail = type_ref.map(|type_ref| { | ||
let mut detail = String::new(); | ||
collapse_whitespaces(type_ref.syntax(), &mut detail); | ||
detail | ||
}); | ||
decl_with_detail(node, detail, kind) | ||
} | ||
|
||
match_ast! { | ||
match node { | ||
ast::FunctionDef(it) => { | ||
let mut detail = String::from("fn"); | ||
if let Some(param_list) = it.param_list() { | ||
collapse_whitespaces(param_list.syntax(), &mut detail); | ||
} | ||
if let Some(ret_type) = it.ret_type() { | ||
detail.push(' '); | ||
collapse_whitespaces(ret_type.syntax(), &mut detail); | ||
} | ||
|
||
decl_with_detail(&it, Some(detail), SymbolKind::Function) | ||
}, | ||
ast::StructDef(it) => decl(it, SymbolKind::Struct), | ||
ast::TypeAliasDef(it) => decl_with_type_ref(&it, it.type_ref(), SymbolKind::TypeAlias), | ||
_ => None | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
use crate::{ | ||
conversion::{convert_range, convert_symbol_kind}, | ||
state::LanguageServerSnapshot, | ||
}; | ||
use lsp_types::DocumentSymbol; | ||
|
||
/// Computes the document symbols for a specific document. Converts the LSP types to internal | ||
/// formats and calls [`LanguageServerSnapshot::file_structure`] to fetch the symbols in the | ||
/// requested document. Once completed, returns the result converted back to LSP types. | ||
pub(crate) fn handle_document_symbol( | ||
snapshot: LanguageServerSnapshot, | ||
params: lsp_types::DocumentSymbolParams, | ||
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> { | ||
let file_id = snapshot.uri_to_file_id(¶ms.text_document.uri)?; | ||
let line_index = snapshot.analysis.file_line_index(file_id)?; | ||
|
||
let mut parents: Vec<(DocumentSymbol, Option<usize>)> = Vec::new(); | ||
|
||
for symbol in snapshot.analysis.file_structure(file_id)? { | ||
#[allow(deprecated)] | ||
let doc_symbol = DocumentSymbol { | ||
name: symbol.label, | ||
detail: symbol.detail, | ||
kind: convert_symbol_kind(symbol.kind), | ||
tags: None, | ||
deprecated: None, | ||
range: convert_range(symbol.node_range, &line_index), | ||
selection_range: convert_range(symbol.navigation_range, &line_index), | ||
children: None, | ||
}; | ||
|
||
parents.push((doc_symbol, symbol.parent)); | ||
} | ||
|
||
Ok(Some(build_hierarchy_from_flat_list(parents).into())) | ||
} | ||
|
||
/// Constructs a hierarchy of DocumentSymbols for a list of symbols that specify which index is the | ||
/// parent of a symbol. The parent index must always be smaller than the current index. | ||
fn build_hierarchy_from_flat_list( | ||
mut symbols_and_parent: Vec<(DocumentSymbol, Option<usize>)>, | ||
) -> Vec<DocumentSymbol> { | ||
let mut result = Vec::new(); | ||
|
||
// Iterate over all elements in the list from back to front. | ||
while let Some((mut node, parent_index)) = symbols_and_parent.pop() { | ||
// If this node has children (added by the code below), they are in the reverse order. This | ||
// is because we iterate the input from back to front. | ||
if let Some(children) = &mut node.children { | ||
children.reverse(); | ||
} | ||
|
||
// Get the parent index of the current node. | ||
let parent = match parent_index { | ||
// If the parent doesnt have a node, directly use the result vector (its a root). | ||
None => &mut result, | ||
|
||
// If there is a parent, get a reference to the children vector of that parent. | ||
Some(i) => symbols_and_parent[i] | ||
.0 | ||
.children | ||
.get_or_insert_with(Vec::new), | ||
}; | ||
|
||
parent.push(node); | ||
} | ||
|
||
// The items where pushed in the reverse order, so reverse it right back | ||
result.reverse(); | ||
result | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::handlers::build_hierarchy_from_flat_list; | ||
use lsp_types::{DocumentSymbol, SymbolKind}; | ||
|
||
#[test] | ||
fn test_build_hierarchy_from_flat_list() { | ||
#[allow(deprecated)] | ||
let default_symbol = DocumentSymbol { | ||
name: "".to_string(), | ||
detail: None, | ||
kind: SymbolKind::File, | ||
tags: None, | ||
deprecated: None, | ||
range: Default::default(), | ||
selection_range: Default::default(), | ||
children: None, | ||
}; | ||
|
||
let mut list = Vec::new(); | ||
|
||
list.push(( | ||
DocumentSymbol { | ||
name: "a".to_string(), | ||
..default_symbol.clone() | ||
}, | ||
None, | ||
)); | ||
|
||
list.push(( | ||
DocumentSymbol { | ||
name: "b".to_string(), | ||
..default_symbol.clone() | ||
}, | ||
Some(0), | ||
)); | ||
|
||
list.push(( | ||
DocumentSymbol { | ||
name: "c".to_string(), | ||
..default_symbol.clone() | ||
}, | ||
Some(0), | ||
)); | ||
|
||
list.push(( | ||
DocumentSymbol { | ||
name: "d".to_string(), | ||
..default_symbol.clone() | ||
}, | ||
Some(1), | ||
)); | ||
|
||
assert_eq!( | ||
build_hierarchy_from_flat_list(list), | ||
vec![DocumentSymbol { | ||
name: "a".to_string(), | ||
children: Some(vec![ | ||
DocumentSymbol { | ||
name: "b".to_string(), | ||
children: Some(vec![DocumentSymbol { | ||
name: "d".to_string(), | ||
..default_symbol.clone() | ||
}]), | ||
..default_symbol.clone() | ||
}, | ||
DocumentSymbol { | ||
name: "c".to_string(), | ||
..default_symbol.clone() | ||
} | ||
]), | ||
..default_symbol.clone() | ||
}] | ||
) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.