use crate::{from_lsp, state::LanguageServerSnapshot, to_lsp, FilePosition};
use lsp_types::{CompletionContext, CompletionItem, DocumentSymbol};
use mun_syntax::{AstNode, TextSize};
pub(crate) fn handle_document_symbol(
snapshot: LanguageServerSnapshot,
params: lsp_types::DocumentSymbolParams,
) -> anyhow::Result<Option<lsp_types::DocumentSymbolResponse>> {
let file_id = from_lsp::file_id(&snapshot, ¶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: to_lsp::symbol_kind(symbol.kind),
tags: None,
deprecated: None,
range: to_lsp::range(symbol.node_range, &line_index),
selection_range: to_lsp::range(symbol.navigation_range, &line_index),
children: None,
};
parents.push((doc_symbol, symbol.parent));
}
Ok(Some(build_hierarchy_from_flat_list(parents).into()))
}
pub(crate) fn handle_completion(
snapshot: LanguageServerSnapshot,
params: lsp_types::CompletionParams,
) -> anyhow::Result<Option<lsp_types::CompletionResponse>> {
let position = from_lsp::file_position(&snapshot, params.text_document_position)?;
if is_position_at_single_colon(&snapshot, position, params.context)? {
return Ok(None);
}
let items = match snapshot.analysis.completions(position)? {
None => return Ok(None),
Some(items) => items,
};
let items: Vec<CompletionItem> = items.into_iter().map(to_lsp::completion_item).collect();
return Ok(Some(items.into()));
fn is_position_at_single_colon(
snapshot: &LanguageServerSnapshot,
position: FilePosition,
context: Option<CompletionContext>,
) -> anyhow::Result<bool> {
if let Some(ctx) = context {
if ctx.trigger_character.unwrap_or_default() == ":" {
let source_file = snapshot.analysis.parse(position.file_id)?;
let syntax = source_file.syntax();
let text = syntax.text();
if let Some(next_char) = text.char_at(position.offset) {
let diff = TextSize::of(next_char) + TextSize::of(':');
let prev_char = position.offset - diff;
if text.char_at(prev_char) != Some(':') {
return Ok(true);
}
}
}
}
Ok(false)
}
}
fn build_hierarchy_from_flat_list(
mut symbols_and_parent: Vec<(DocumentSymbol, Option<usize>)>,
) -> Vec<DocumentSymbol> {
let mut result = Vec::new();
while let Some((mut node, parent_index)) = symbols_and_parent.pop() {
if let Some(children) = &mut node.children {
children.reverse();
}
let parent = match parent_index {
None => &mut result,
Some(i) => symbols_and_parent[i]
.0
.children
.get_or_insert_with(Vec::new),
};
parent.push(node);
}
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
}]
)
}
}