use std::collections::HashSet;
use std::sync::Arc;
use tower_lsp::lsp_types::{Position, SymbolKind, TypeHierarchyItem, Url};
use crate::text::zero_width_range;
fn make_item_from_index(
name: &str,
kind: SymbolKind,
uri: &Url,
start_line: u32,
) -> 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: None,
}
}
pub fn prepare_type_hierarchy_from_workspace(
source: &str,
uri: &Url,
wi: &crate::db::workspace_index::WorkspaceIndexData,
position: Position,
) -> Option<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
use crate::text::word_at_position;
let word = word_at_position(source, position)?;
let refs = wi.classes_by_name.get(&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))
}
pub fn supertypes_of_from_workspace(
item: &TypeHierarchyItem,
wi: &crate::db::workspace_index::WorkspaceIndexData,
) -> Vec<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
let mut super_pairs: Vec<(Arc<str>, Option<&crate::index::file_index::FileIndex>)> = Vec::new();
if let Some(refs) = wi.classes_by_name.get(&item.name) {
for r in refs {
if let Some((_, cls)) = wi.at(*r) {
let file_idx = wi.files.get(r.file as usize).map(|(_, idx)| idx.as_ref());
if let Some(p) = &cls.parent {
super_pairs.push((Arc::clone(p), file_idx));
}
for iface in &cls.implements {
super_pairs.push((Arc::clone(iface), file_idx));
}
}
}
}
let mut result = Vec::new();
let mut seen_fqns: HashSet<Box<str>> = HashSet::new();
for (name, file_idx) in super_pairs {
let canonical = if wi.classes_by_name.contains_key(name.as_ref()) {
Some(name.as_ref().to_string())
} else {
file_idx.and_then(|idx| {
idx.use_imports
.iter()
.find(|(alias, _)| alias.as_ref() == name.as_ref())
.map(|(_, fqn)| crate::text::fqn_short_name(fqn).to_string())
})
};
let Some(canonical_name) = canonical else {
continue;
};
if let Some(refs) = wi.classes_by_name.get(&canonical_name)
&& let Some((uri, cls)) = refs.first().and_then(|r| wi.at(*r))
&& seen_fqns.insert(cls.fqn.clone())
{
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));
}
}
result
}
pub fn subtypes_of_mir_backed(
item: &TypeHierarchyItem,
item_fqn: Option<&str>,
wi: &crate::db::workspace_index::WorkspaceIndexData,
subtype_urls: &[Url],
) -> Vec<TypeHierarchyItem> {
if subtype_urls.is_empty() {
return subtypes_of_from_workspace(item, item_fqn, wi);
}
use crate::index::file_index::ClassKind;
use crate::navigation::implementation::{alias_resolves_to, name_matches};
let url_set: HashSet<&Url> = subtype_urls.iter().collect();
let mut result = Vec::new();
for (uri, idx) in &wi.files {
if !url_set.contains(uri) {
continue;
}
for cls in &idx.classes {
let extends_match = cls
.parent
.as_deref()
.map(|p| {
name_matches(p, &item.name, item_fqn)
|| item_fqn.is_some_and(|f| alias_resolves_to(p, f, &idx.use_imports))
})
.unwrap_or(false);
let implements_match = cls.implements.iter().any(|iface| {
name_matches(iface.as_ref(), &item.name, item_fqn)
|| item_fqn
.is_some_and(|f| alias_resolves_to(iface.as_ref(), f, &idx.use_imports))
});
let uses_match = cls.traits.iter().any(|t| {
name_matches(t.as_ref(), &item.name, item_fqn)
|| item_fqn.is_some_and(|f| alias_resolves_to(t.as_ref(), f, &idx.use_imports))
});
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));
}
}
}
result
}
pub fn subtypes_of_from_workspace(
item: &TypeHierarchyItem,
item_fqn: Option<&str>,
wi: &crate::db::workspace_index::WorkspaceIndexData,
) -> Vec<TypeHierarchyItem> {
use crate::index::file_index::ClassKind;
use crate::navigation::implementation::resolves_to_fqn;
let Some(refs) = wi.subtypes_of.get(item.name.as_str()) else {
return Vec::new();
};
refs.iter()
.filter_map(|r| {
let (uri, cls) = wi.at(*r)?;
let file_idx = wi.files.get(r.file as usize).map(|(_, idx)| idx.as_ref());
let matches = match item_fqn {
Some(f) => {
let named =
|name: &str| file_idx.is_some_and(|idx| resolves_to_fqn(name, f, idx));
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 => true,
};
matches.then_some((uri, cls))
})
.map(|(uri, cls)| {
let kind = match cls.kind {
ClassKind::Class | ClassKind::Trait => SymbolKind::CLASS,
ClassKind::Interface => SymbolKind::INTERFACE,
ClassKind::Enum => SymbolKind::ENUM,
};
make_item_from_index(&cls.name, kind, uri, cls.start_line)
})
.collect()
}