use std::collections::HashMap;
use lsp_types::{Location, SymbolInformation, SymbolKind, Uri, WorkspaceSymbolParams};
use crate::ir::ast::{ClassDefinition, ClassType, StoredDefinition, Variability};
use crate::lsp::utils::{parse_document, token_to_range};
#[allow(deprecated)] pub fn handle_workspace_symbol(
documents: &HashMap<Uri, String>,
params: WorkspaceSymbolParams,
) -> Option<Vec<SymbolInformation>> {
let query = params.query.to_lowercase();
let mut symbols = Vec::new();
for (uri, text) in documents {
let path = uri.path().as_str();
if let Some(ast) = parse_document(text, path) {
collect_symbols_from_ast(&ast, uri, &query, &mut symbols);
}
}
symbols.sort_by(|a, b| {
let a_name = a.name.to_lowercase();
let b_name = b.name.to_lowercase();
let a_exact = a_name == query;
let b_exact = b_name == query;
if a_exact != b_exact {
return b_exact.cmp(&a_exact);
}
let a_prefix = a_name.starts_with(&query);
let b_prefix = b_name.starts_with(&query);
if a_prefix != b_prefix {
return b_prefix.cmp(&a_prefix);
}
a_name.cmp(&b_name)
});
Some(symbols)
}
#[allow(deprecated)] fn collect_symbols_from_ast(
def: &StoredDefinition,
uri: &Uri,
query: &str,
symbols: &mut Vec<SymbolInformation>,
) {
for class in def.class_list.values() {
collect_symbols_from_class(class, uri, query, symbols, None);
}
}
#[allow(deprecated)] fn collect_symbols_from_class(
class: &ClassDefinition,
uri: &Uri,
query: &str,
symbols: &mut Vec<SymbolInformation>,
container: Option<String>,
) {
let class_name = &class.name.text;
if matches_query(class_name, query) {
let kind = class_type_to_symbol_kind(&class.class_type);
let location = Location {
uri: uri.clone(),
range: token_to_range(&class.name),
};
symbols.push(SymbolInformation {
name: class_name.clone(),
kind,
tags: None,
deprecated: None,
location,
container_name: container.clone(),
});
}
for (comp_name, comp) in &class.components {
if matches_query(comp_name, query) {
let kind = match &comp.variability {
Variability::Parameter(_) | Variability::Constant(_) => SymbolKind::CONSTANT,
_ => SymbolKind::VARIABLE,
};
let location = Location {
uri: uri.clone(),
range: token_to_range(&comp.name_token),
};
symbols.push(SymbolInformation {
name: comp_name.clone(),
kind,
tags: None,
deprecated: None,
location,
container_name: Some(class_name.clone()),
});
}
}
let container_name = match &container {
Some(c) => format!("{}.{}", c, class_name),
None => class_name.clone(),
};
for nested_class in class.classes.values() {
collect_symbols_from_class(
nested_class,
uri,
query,
symbols,
Some(container_name.clone()),
);
}
}
fn matches_query(name: &str, query: &str) -> bool {
if query.is_empty() {
return true;
}
let name_lower = name.to_lowercase();
if name_lower == query {
return true;
}
if name_lower.starts_with(query) {
return true;
}
if name_lower.contains(query) {
return true;
}
let mut query_chars = query.chars().peekable();
for name_char in name_lower.chars() {
if let Some(&query_char) = query_chars.peek()
&& name_char == query_char
{
query_chars.next();
}
}
query_chars.peek().is_none()
}
fn class_type_to_symbol_kind(class_type: &ClassType) -> SymbolKind {
match class_type {
ClassType::Model => SymbolKind::CLASS,
ClassType::Class => SymbolKind::CLASS,
ClassType::Block => SymbolKind::CLASS,
ClassType::Connector => SymbolKind::INTERFACE,
ClassType::Type => SymbolKind::TYPE_PARAMETER,
ClassType::Package => SymbolKind::NAMESPACE,
ClassType::Function => SymbolKind::FUNCTION,
ClassType::Record => SymbolKind::STRUCT,
ClassType::Operator => SymbolKind::OPERATOR,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_matches_query_empty() {
assert!(matches_query("Motor", ""));
assert!(matches_query("anything", ""));
}
#[test]
fn test_matches_query_exact() {
assert!(matches_query("Motor", "motor"));
assert!(matches_query("motor", "motor"));
}
#[test]
fn test_matches_query_prefix() {
assert!(matches_query("MotorController", "motor"));
assert!(matches_query("MotorController", "motorc"));
}
#[test]
fn test_matches_query_contains() {
assert!(matches_query("DCMotor", "motor"));
assert!(matches_query("MyMotorClass", "motor"));
}
#[test]
fn test_matches_query_fuzzy() {
assert!(matches_query("MotorController", "mc"));
assert!(matches_query("MotorController", "mtc"));
assert!(matches_query("SomeVeryLongName", "svln"));
}
#[test]
fn test_matches_query_no_match() {
assert!(!matches_query("Motor", "xyz"));
assert!(!matches_query("Motor", "motorz"));
}
}