use std::collections::HashSet;
use std::sync::Arc;
use serde_json::{Value, json};
use tower_lsp_server::ls_types::{Position, SymbolKind, TypeHierarchyItem, Uri};
use crate::document::ast::ParsedDoc;
use crate::text::zero_width_range;
fn make_item_from_index(
name: &str,
kind: SymbolKind,
uri: &Uri,
start_line: u32,
fqn: &str,
) -> TypeHierarchyItem {
let range = zero_width_range(start_line);
TypeHierarchyItem {
name: name.to_string(),
kind,
tags: None,
detail: None,
uri: uri.clone(),
range,
selection_range: range,
data: Some(json!({ "fqn": fqn.trim_start_matches('\\') })),
}
}
fn sort_items_stably(items: &mut [TypeHierarchyItem]) {
items.sort_by(|a, b| {
a.name
.cmp(&b.name)
.then_with(|| a.uri.as_str().cmp(b.uri.as_str()))
.then_with(|| a.range.start.line.cmp(&b.range.start.line))
.then_with(|| a.range.start.character.cmp(&b.range.start.character))
});
}
pub fn item_fqn(item: &TypeHierarchyItem) -> Option<&str> {
item.data
.as_ref()
.and_then(Value::as_object)
.and_then(|obj| obj.get("fqn"))
.and_then(Value::as_str)
}
pub fn prepare_type_hierarchy_from_workspace(
source: &str,
uri: &Uri,
wi: &crate::db::workspace_index::WorkspaceIndexData,
position: Position,
class_candidates_by_short_name: &dyn Fn(&str) -> Vec<crate::db::workspace_index::ClassRef>,
) -> Option<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
use crate::text::word_at_position;
let word = word_at_position(source, position)?;
let refs = class_candidates_by_short_name(&word);
let (uri, cls) = refs
.iter()
.filter_map(|r| wi.at(*r))
.find(|(u, _)| *u == uri)
.or_else(|| refs.first().and_then(|r| wi.at(*r)))?;
let kind = match cls.kind {
ClassKind::Class | ClassKind::Trait => SymbolKind::CLASS,
ClassKind::Interface => SymbolKind::INTERFACE,
ClassKind::Enum => SymbolKind::ENUM,
};
Some(make_item_from_index(
&cls.name,
kind,
uri,
cls.start_line,
&cls.fqn,
))
}
pub fn supertypes_of_from_workspace(
item: &TypeHierarchyItem,
wi: &crate::db::workspace_index::WorkspaceIndexData,
class_candidates_by_short_name: &dyn Fn(&str) -> Vec<crate::db::workspace_index::ClassRef>,
get_doc: &dyn Fn(&Uri) -> Option<Arc<ParsedDoc>>,
resolve_class_ref: &dyn Fn(&str) -> Option<crate::db::workspace_index::ClassRef>,
) -> Vec<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
let mut result = Vec::new();
let mut seen_fqns: HashSet<Box<str>> = HashSet::new();
for r in class_candidates_by_short_name(&item.name) {
let Some((uri, cls)) = wi.at(r) else {
continue;
};
let Some(doc) = get_doc(uri) else {
continue;
};
let imports = doc.file_imports();
let super_names = cls
.parent
.iter()
.cloned()
.chain(cls.implements.iter().cloned())
.chain(cls.traits.iter().cloned());
for name in super_names {
let resolved = crate::navigation::moniker::resolve_fqn(&doc, name.as_ref(), &imports);
let Some((super_uri, super_cls)) =
resolve_class_ref(&resolved).and_then(|class_ref| wi.at(class_ref))
else {
continue;
};
if seen_fqns.insert(super_cls.fqn.clone()) {
let kind = match super_cls.kind {
ClassKind::Class | ClassKind::Trait => SymbolKind::CLASS,
ClassKind::Interface => SymbolKind::INTERFACE,
ClassKind::Enum => SymbolKind::ENUM,
};
result.push(make_item_from_index(
&super_cls.name,
kind,
super_uri,
super_cls.start_line,
&super_cls.fqn,
));
}
}
}
result
}
pub fn subtypes_of_mir_backed(
item: &TypeHierarchyItem,
item_fqn: Option<&str>,
wi: &crate::db::workspace_index::WorkspaceIndexData,
subtype_urls: &[Uri],
mention_candidates: &dyn Fn(&str) -> Vec<Uri>,
get_doc: &dyn Fn(&Uri) -> Option<Arc<ParsedDoc>>,
) -> Vec<TypeHierarchyItem> {
if subtype_urls.is_empty() {
return subtypes_of_from_workspace(item, item_fqn, wi, mention_candidates, get_doc);
}
use crate::index::file_index::ClassKind;
let mut result = Vec::new();
wi.for_each_class_in_uris(subtype_urls, |uri, cls| {
let doc = get_doc(uri);
let imports = doc.as_ref().map(|doc| doc.file_imports());
let matches_name = |name: &str| {
if let (Some(target_fqn), Some(doc), Some(imports)) =
(item_fqn, doc.as_ref(), imports.as_ref())
{
crate::navigation::moniker::resolve_fqn(doc, name, imports)
.trim_start_matches('\\')
.eq_ignore_ascii_case(target_fqn)
} else {
name == item.name
|| (item_fqn.is_none()
&& !item.name.contains('\\')
&& name.trim_start_matches('\\') == item.name)
}
};
let extends_match = cls.parent.as_deref().is_some_and(matches_name);
let implements_match = cls
.implements
.iter()
.any(|iface| matches_name(iface.as_ref()));
let uses_match = cls.traits.iter().any(|t| matches_name(t.as_ref()));
if extends_match || implements_match || uses_match {
let kind = match cls.kind {
ClassKind::Class | ClassKind::Trait => SymbolKind::CLASS,
ClassKind::Interface => SymbolKind::INTERFACE,
ClassKind::Enum => SymbolKind::ENUM,
};
result.push(make_item_from_index(
&cls.name,
kind,
uri,
cls.start_line,
&cls.fqn,
));
}
});
sort_items_stably(&mut result);
result
}
pub fn subtypes_of_from_workspace(
item: &TypeHierarchyItem,
item_fqn: Option<&str>,
wi: &crate::db::workspace_index::WorkspaceIndexData,
mention_candidates: &dyn Fn(&str) -> Vec<Uri>,
get_doc: &dyn Fn(&Uri) -> Option<Arc<ParsedDoc>>,
) -> Vec<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
let mut results = Vec::new();
let candidate_uris = mention_candidates(&item.name);
wi.for_each_class_in_uris(&candidate_uris, |uri, cls| {
let doc = get_doc(uri);
let imports = doc.as_ref().map(|doc| doc.file_imports());
let matches = match item_fqn {
Some(f) => {
let named = |name: &str| {
let (Some(doc), Some(imports)) = (doc.as_ref(), imports.as_ref()) else {
return false;
};
crate::navigation::moniker::resolve_fqn(doc, name, imports)
.trim_start_matches('\\')
.eq_ignore_ascii_case(f)
};
cls.parent.as_deref().is_some_and(named)
|| cls.implements.iter().any(|iface| named(iface.as_ref()))
|| cls.traits.iter().any(|t| named(t.as_ref()))
}
None => {
let named = |name: &str| name == item.name;
cls.parent.as_deref().is_some_and(named)
|| cls.implements.iter().any(|iface| named(iface.as_ref()))
|| cls.traits.iter().any(|t| named(t.as_ref()))
}
};
if matches {
let kind = match cls.kind {
ClassKind::Class | ClassKind::Trait => SymbolKind::CLASS,
ClassKind::Interface => SymbolKind::INTERFACE,
ClassKind::Enum => SymbolKind::ENUM,
};
results.push(make_item_from_index(
&cls.name,
kind,
uri,
cls.start_line,
&cls.fqn,
));
}
});
sort_items_stably(&mut results);
results
}