use std::collections::HashMap;
use std::sync::Arc;
use tower_lsp::lsp_types::Url;
use crate::index::file_index::FileIndex;
#[derive(Debug, Clone, Copy)]
pub struct ClassRef {
pub file: u32,
pub class: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeclKind {
Function,
Class,
Method,
Property,
Constant,
EnumCase,
}
#[derive(Debug, Clone, Copy)]
pub struct DeclRef {
pub file: u32,
pub line: u32,
pub kind: DeclKind,
}
pub struct WorkspaceIndexData {
pub files: Vec<(Url, Arc<FileIndex>)>,
pub classes_by_name: HashMap<String, Vec<ClassRef>>,
pub subtypes_of: HashMap<Arc<str>, Vec<ClassRef>>,
pub decls_by_name: HashMap<String, Vec<DeclRef>>,
}
pub(crate) type BuildMapsResult = (
HashMap<String, Vec<ClassRef>>,
HashMap<Arc<str>, Vec<ClassRef>>,
HashMap<String, Vec<DeclRef>>,
);
pub(crate) fn build_maps(files: &[(Url, Arc<FileIndex>)]) -> BuildMapsResult {
let mut classes_by_name: HashMap<String, Vec<ClassRef>> = HashMap::new();
let mut subtypes_of: HashMap<Arc<str>, Vec<ClassRef>> = HashMap::new();
let mut decls_by_name: HashMap<String, Vec<DeclRef>> = HashMap::new();
let push_decl = |map: &mut HashMap<String, Vec<DeclRef>>,
name: &str,
file: u32,
line: u32,
kind: DeclKind| {
map.entry(name.to_string())
.or_default()
.push(DeclRef { file, line, kind });
};
for (file_idx, (_, idx)) in files.iter().enumerate() {
let file_idx = file_idx as u32;
for f in &idx.functions {
push_decl(
&mut decls_by_name,
&f.name,
file_idx,
f.start_line,
DeclKind::Function,
);
}
for (cls_idx, cls) in idx.classes.iter().enumerate() {
let cr = ClassRef {
file: file_idx,
class: cls_idx as u32,
};
classes_by_name
.entry(cls.name.as_ref().to_string())
.or_default()
.push(cr);
if let Some(parent) = &cls.parent {
subtypes_of.entry(Arc::clone(parent)).or_default().push(cr);
}
for iface in &cls.implements {
subtypes_of.entry(Arc::clone(iface)).or_default().push(cr);
if let Some((_, fqn)) = idx
.use_imports
.iter()
.find(|(alias, _)| alias.as_ref() == iface.as_ref())
{
let short = crate::text::fqn_short_name(fqn);
if short != iface.as_ref() {
subtypes_of.entry(Arc::from(short)).or_default().push(cr);
}
}
}
for trt in &cls.traits {
subtypes_of.entry(Arc::clone(trt)).or_default().push(cr);
}
push_decl(
&mut decls_by_name,
&cls.name,
file_idx,
cls.start_line,
DeclKind::Class,
);
for m in &cls.methods {
push_decl(
&mut decls_by_name,
&m.name,
file_idx,
m.start_line,
DeclKind::Method,
);
}
for p in &cls.properties {
push_decl(
&mut decls_by_name,
&p.name,
file_idx,
p.start_line,
DeclKind::Property,
);
}
for cc in &cls.constants {
push_decl(
&mut decls_by_name,
cc,
file_idx,
cls.start_line,
DeclKind::Constant,
);
}
for case in &cls.cases {
push_decl(
&mut decls_by_name,
case,
file_idx,
cls.start_line,
DeclKind::EnumCase,
);
}
}
}
(classes_by_name, subtypes_of, decls_by_name)
}
impl WorkspaceIndexData {
pub fn at(&self, r: ClassRef) -> Option<(&Url, &crate::index::file_index::ClassDef)> {
let (uri, idx) = self.files.get(r.file as usize)?;
let cls = idx.classes.get(r.class as usize)?;
Some((uri, cls))
}
pub fn find_declaration(
&self,
name: &str,
exclude: Option<&Url>,
) -> Option<tower_lsp::lsp_types::Location> {
let bare = crate::text::strip_variable_sigil(name);
let sigil = bare != name;
let refs = self.decls_by_name.get(bare)?;
for r in refs {
if sigil
&& !matches!(
r.kind,
DeclKind::Function | DeclKind::Class | DeclKind::Property
)
{
continue;
}
let (uri, _) = self.files.get(r.file as usize)?;
if exclude.is_some_and(|e| e == uri) {
continue;
}
return Some(tower_lsp::lsp_types::Location {
uri: uri.clone(),
range: crate::text::zero_width_range(r.line),
});
}
None
}
pub fn from_files(files: Vec<(Url, Arc<FileIndex>)>) -> Self {
let (classes_by_name, subtypes_of, decls_by_name) = build_maps(&files);
Self {
files,
classes_by_name,
subtypes_of,
decls_by_name,
}
}
}
#[derive(Clone)]
pub struct WorkspaceIndexArc(pub Arc<WorkspaceIndexData>);
impl WorkspaceIndexArc {
#[cfg(test)]
pub fn get(&self) -> &WorkspaceIndexData {
&self.0
}
}
crate::impl_arc_update!(WorkspaceIndexArc);