use lsp_types::{CodeLens, CodeLensParams, Command, Position, Range, Uri};
use crate::dae::balance::BalanceStatus;
use crate::ir::ast::{ClassDefinition, ClassType, StoredDefinition};
use crate::lsp::WorkspaceState;
use crate::lsp::utils::parse_document;
pub fn handle_code_lens(
workspace: &WorkspaceState,
params: CodeLensParams,
) -> Option<Vec<CodeLens>> {
let uri = ¶ms.text_document.uri;
let text = workspace.get_document(uri)?;
let path = uri.path().as_str();
let mut lenses = Vec::new();
if let Some(ast) = parse_document(text, path) {
for class in ast.class_list.values() {
collect_class_lenses(
class,
text,
&ast,
uri,
workspace,
"", &mut lenses,
);
}
}
Some(lenses)
}
fn collect_class_lenses(
class: &ClassDefinition,
text: &str,
ast: &StoredDefinition,
uri: &Uri,
workspace: &WorkspaceState,
prefix: &str,
lenses: &mut Vec<CodeLens>,
) {
let class_line = class.name.location.start_line.saturating_sub(1);
let class_path = if prefix.is_empty() {
class.name.text.clone()
} else {
format!("{}.{}", prefix, class.name.text)
};
if matches!(
class.class_type,
ClassType::Model | ClassType::Block | ClassType::Class | ClassType::Connector
) && let Some(balance) = workspace.get_balance(uri, &class_path)
{
let time_str = if balance.compile_time_ms > 0 {
format!(" ({}ms)", balance.compile_time_ms)
} else {
String::new()
};
let title = match &balance.status {
BalanceStatus::Balanced => format!(
"{} states, {} unknowns, {} equations [✓]{}",
balance.num_states, balance.num_unknowns, balance.num_equations, time_str
),
BalanceStatus::Partial => format!(
"{} states, {} unknowns, {} equations [◐ partial]{}",
balance.num_states, balance.num_unknowns, balance.num_equations, time_str
),
BalanceStatus::Unbalanced => {
let icon = if balance.difference() > 0 {
"⚠ over"
} else {
"⚠ under"
};
format!(
"{} states, {} unknowns, {} equations [{}]{}",
balance.num_states, balance.num_unknowns, balance.num_equations, icon, time_str
)
}
BalanceStatus::CompileError(msg) => format!("✗ compile error: {}{}", msg, time_str),
};
lenses.push(CodeLens {
range: Range {
start: Position {
line: class_line,
character: 0,
},
end: Position {
line: class_line,
character: 0,
},
},
command: Some(Command {
title,
command: String::new(), arguments: None,
}),
data: None,
});
}
if !class.extends.is_empty() {
let extends_names: Vec<String> = class.extends.iter().map(|e| e.comp.to_string()).collect();
lenses.push(CodeLens {
range: Range {
start: Position {
line: class_line,
character: 0,
},
end: Position {
line: class_line,
character: 0,
},
},
command: Some(Command {
title: format!("extends {}", extends_names.join(", ")),
command: String::new(),
arguments: None,
}),
data: None,
});
}
let ref_count = count_references(&class.name.text, text, ast);
if ref_count > 0 {
lenses.push(CodeLens {
range: Range {
start: Position {
line: class_line,
character: 0,
},
end: Position {
line: class_line,
character: 0,
},
},
command: Some(Command {
title: format!(
"{} reference{}",
ref_count,
if ref_count == 1 { "" } else { "s" }
),
command: "editor.action.findReferences".to_string(),
arguments: None,
}),
data: None,
});
}
if class.class_type == ClassType::Function {
let input_count = class
.components
.values()
.filter(|c| matches!(c.causality, crate::ir::ast::Causality::Input(_)))
.count();
let output_count = class
.components
.values()
.filter(|c| matches!(c.causality, crate::ir::ast::Causality::Output(_)))
.count();
lenses.push(CodeLens {
range: Range {
start: Position {
line: class_line,
character: 0,
},
end: Position {
line: class_line,
character: 0,
},
},
command: Some(Command {
title: format!(
"{} input{}, {} output{}",
input_count,
if input_count == 1 { "" } else { "s" },
output_count,
if output_count == 1 { "" } else { "s" }
),
command: String::new(),
arguments: None,
}),
data: None,
});
}
for nested in class.classes.values() {
collect_class_lenses(nested, text, ast, uri, workspace, &class_path, lenses);
}
}
fn count_references(name: &str, _text: &str, ast: &StoredDefinition) -> usize {
let mut count = 0;
for class in ast.class_list.values() {
count += count_references_in_class(name, class);
}
count
}
fn count_references_in_class(name: &str, class: &ClassDefinition) -> usize {
let mut count = 0;
for comp in class.components.values() {
if comp.type_name.to_string() == name {
count += 1;
}
}
for ext in &class.extends {
if ext.comp.to_string() == name {
count += 1;
}
}
for nested in class.classes.values() {
count += count_references_in_class(name, nested);
}
count
}
#[cfg(test)]
mod tests {
#[test]
fn test_count_references_simple() {
let text = "model Test\n MyType x;\n MyType y;\nend Test;";
let count = text.matches("MyType").count();
assert_eq!(count, 2);
}
}