use std::collections::HashMap;
use lsp_types::{
DocumentSymbol, DocumentSymbolParams, DocumentSymbolResponse, Position, Range, SymbolKind, Uri,
};
use crate::ir::ast::{Causality, ClassDefinition, ClassType, Variability};
use crate::lsp::utils::{location_to_range, parse_document, token_to_range};
pub fn handle_document_symbols(
documents: &HashMap<Uri, String>,
params: DocumentSymbolParams,
) -> Option<DocumentSymbolResponse> {
let uri = ¶ms.text_document.uri;
let text = documents.get(uri)?;
let path = uri.path().as_str();
let ast = parse_document(text, path)?;
let mut symbols = Vec::new();
for (class_name, class_def) in &ast.class_list {
if let Some(symbol) = build_class_symbol(class_name, class_def) {
symbols.push(symbol);
}
}
Some(DocumentSymbolResponse::Nested(symbols))
}
#[allow(deprecated)] fn build_class_symbol(name: &str, class: &ClassDefinition) -> Option<DocumentSymbol> {
let kind = match class.class_type {
ClassType::Model => SymbolKind::CLASS,
ClassType::Block => SymbolKind::CLASS,
ClassType::Connector => SymbolKind::INTERFACE,
ClassType::Record => SymbolKind::STRUCT,
ClassType::Type => SymbolKind::TYPE_PARAMETER,
ClassType::Package => SymbolKind::NAMESPACE,
ClassType::Function => SymbolKind::FUNCTION,
ClassType::Class => SymbolKind::CLASS,
_ => SymbolKind::CLASS,
};
let range = location_to_range(&class.location);
let selection_range = token_to_range(&class.name);
let mut children = Vec::new();
let mut parameters = Vec::new();
let mut variables = Vec::new();
let mut inputs = Vec::new();
let mut outputs = Vec::new();
for (comp_name, comp) in &class.components {
let (comp_kind, category) = match (&comp.variability, &comp.causality) {
(Variability::Parameter(_), _) => (SymbolKind::CONSTANT, &mut parameters),
(Variability::Constant(_), _) => (SymbolKind::CONSTANT, &mut parameters),
(_, Causality::Input(_)) => (SymbolKind::PROPERTY, &mut inputs),
(_, Causality::Output(_)) => (SymbolKind::PROPERTY, &mut outputs),
_ => (SymbolKind::VARIABLE, &mut variables),
};
let comp_range = location_to_range(&comp.location);
let comp_selection_range = token_to_range(&comp.name_token);
let mut detail = comp.type_name.to_string();
if !comp.shape.is_empty() {
detail += &format!(
"[{}]",
comp.shape
.iter()
.map(|d| d.to_string())
.collect::<Vec<_>>()
.join(", ")
);
}
category.push(DocumentSymbol {
name: comp_name.clone(),
detail: Some(detail),
kind: comp_kind,
tags: None,
deprecated: None,
range: comp_range,
selection_range: comp_selection_range,
children: None,
});
}
fn compute_group_range(symbols: &[DocumentSymbol]) -> Range {
let mut min_start = Position {
line: u32::MAX,
character: u32::MAX,
};
let mut max_end = Position {
line: 0,
character: 0,
};
for sym in symbols {
if sym.range.start.line < min_start.line
|| (sym.range.start.line == min_start.line
&& sym.range.start.character < min_start.character)
{
min_start = sym.range.start;
}
if sym.range.end.line > max_end.line
|| (sym.range.end.line == max_end.line
&& sym.range.end.character > max_end.character)
{
max_end = sym.range.end;
}
}
if min_start.line > max_end.line
|| (min_start.line == max_end.line && min_start.character > max_end.character)
{
max_end = min_start;
}
Range {
start: min_start,
end: max_end,
}
}
if !parameters.is_empty() {
let group_range = compute_group_range(¶meters);
children.push(DocumentSymbol {
name: "Parameters".to_string(),
detail: Some(format!("{} items", parameters.len())),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range: group_range,
selection_range: group_range,
children: Some(parameters),
});
}
if !inputs.is_empty() {
let group_range = compute_group_range(&inputs);
children.push(DocumentSymbol {
name: "Inputs".to_string(),
detail: Some(format!("{} items", inputs.len())),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range: group_range,
selection_range: group_range,
children: Some(inputs),
});
}
if !outputs.is_empty() {
let group_range = compute_group_range(&outputs);
children.push(DocumentSymbol {
name: "Outputs".to_string(),
detail: Some(format!("{} items", outputs.len())),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range: group_range,
selection_range: group_range,
children: Some(outputs),
});
}
if !variables.is_empty() {
let group_range = compute_group_range(&variables);
children.push(DocumentSymbol {
name: "Variables".to_string(),
detail: Some(format!("{} items", variables.len())),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range: group_range,
selection_range: group_range,
children: Some(variables),
});
}
for (nested_name, nested_class) in &class.classes {
if let Some(nested_symbol) = build_class_symbol(nested_name, nested_class) {
children.push(nested_symbol);
}
}
fn compute_equations_range(equations: &[crate::ir::ast::Equation]) -> Option<Range> {
let mut min_line = u32::MAX;
let mut max_line = 0u32;
let mut min_col = u32::MAX;
let mut max_col = 0u32;
for eq in equations {
if let Some(loc) = eq.get_location() {
let line = loc.start_line.saturating_sub(1);
let col = loc.start_column.saturating_sub(1);
if line < min_line || (line == min_line && col < min_col) {
min_line = line;
min_col = col;
}
if line > max_line || (line == max_line && col + 20 > max_col) {
max_line = line;
max_col = col + 20; }
}
}
if min_line == u32::MAX {
None
} else {
if max_line < min_line || (max_line == min_line && max_col < min_col) {
max_line = min_line;
max_col = min_col;
}
Some(Range {
start: Position {
line: min_line,
character: min_col,
},
end: Position {
line: max_line,
character: max_col,
},
})
}
}
let equation_count = class.equations.len() + class.initial_equations.len();
if equation_count > 0 {
let all_equations: Vec<_> = class
.equations
.iter()
.chain(class.initial_equations.iter())
.cloned()
.collect();
let eq_range = compute_equations_range(&all_equations).unwrap_or(range);
children.push(DocumentSymbol {
name: "Equations".to_string(),
detail: Some(format!("{} equations", equation_count)),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range: eq_range,
selection_range: eq_range,
children: None,
});
}
let algorithm_count = class.algorithms.len() + class.initial_algorithms.len();
if algorithm_count > 0 {
children.push(DocumentSymbol {
name: "Algorithms".to_string(),
detail: Some(format!("{} algorithm sections", algorithm_count)),
kind: SymbolKind::NAMESPACE,
tags: None,
deprecated: None,
range,
selection_range: range,
children: None,
});
}
let detail = format!("{:?}", class.class_type);
Some(DocumentSymbol {
name: name.to_string(),
detail: Some(detail),
kind,
tags: None,
deprecated: None,
range,
selection_range,
children: if children.is_empty() {
None
} else {
Some(children)
},
})
}