use lsp_types::{Range, Uri};
use rowan::TextSize;
use crate::incremental::{Analysis, SourceFile};
use crate::resolve::{OccurrenceKey, OccurrenceRec, Resolver};
use crate::text::{LineIndex, PositionEncoding};
use super::uri::from_path;
pub(crate) struct CrossFileSite {
pub uri: Uri,
pub range: Range,
pub is_def: bool,
pub access: crate::semantic::Access,
}
pub(crate) fn workspace_symbol_at(
snapshot: &Analysis,
path: &std::path::Path,
model: &crate::semantic::SemanticModel,
offset: TextSize,
) -> Option<OccurrenceKey> {
let workspace = snapshot.workspace_member(path);
Resolver::new(model, snapshot)
.with_workspace(workspace)
.workspace_symbol_at(offset)
}
pub(crate) fn gather_sites(
snapshot: &Analysis,
symbol: &OccurrenceKey,
encoding: PositionEncoding,
) -> Vec<CrossFileSite> {
let index = snapshot.workspace_reference_index();
let Some(bucket) = index.0.get(symbol) else {
return Vec::new();
};
let mut by_file: std::collections::HashMap<SourceFile, Vec<OccurrenceRec>> =
std::collections::HashMap::new();
for &(file, rec) in bucket {
by_file.entry(file).or_default().push(rec);
}
let mut out = Vec::new();
for (file, recs) in by_file {
let Some(path) = snapshot.file_path_of(file) else {
continue;
};
let Some(uri) = from_path(&path) else {
continue;
};
let line_index = LineIndex::new(snapshot.file_text_of(file));
for rec in recs {
out.push(CrossFileSite {
uri: uri.clone(),
range: Range {
start: line_index.byte_to_position(rec.range.start().into(), encoding),
end: line_index.byte_to_position(rec.range.end().into(), encoding),
},
is_def: rec.is_def,
access: rec.access,
});
}
}
out.sort_by(|a, b| {
(a.uri.as_str(), a.range.start.line, a.range.start.character).cmp(&(
b.uri.as_str(),
b.range.start.line,
b.range.start.character,
))
});
out
}
#[cfg(test)]
pub(crate) mod test_support {
use std::collections::BTreeMap;
use std::path::PathBuf;
use std::sync::Arc;
use crate::incremental::{IncrementalDatabase, SourceFile};
use crate::index::model::{DefLocation, Span};
use crate::index::{FunctionGroup, ModuleIndex, PackageIndex};
pub(crate) fn member_path(rel: &str) -> PathBuf {
crate::incremental::normalize_path(&PathBuf::from("/work/MyPkg/src").join(rel))
}
pub(crate) fn workspace_db(
functions: &[&str],
files: &[(&str, &str)],
) -> (IncrementalDatabase, Vec<SourceFile>) {
let loc = DefLocation {
file: "src/MyPkg.jl".into(),
range: Span { start: 0, end: 0 },
};
let root = ModuleIndex {
name: "MyPkg".to_string(),
bare: false,
loc: loc.clone(),
exports: Vec::new(),
functions: functions
.iter()
.map(|f| FunctionGroup {
name: f.to_string(),
owner: None,
methods: Vec::new(),
doc: None,
})
.collect(),
types: Vec::new(),
consts: Vec::new(),
macros: Vec::new(),
submodules: Vec::new(),
};
let members: Vec<PathBuf> = files.iter().map(|(rel, _)| member_path(rel)).collect();
let pkg = PackageIndex {
name: "MyPkg".to_string(),
root,
members,
member_modules: Default::default(),
diagnostics: Vec::new(),
};
let mut db = IncrementalDatabase::new();
let handles: Vec<SourceFile> = files
.iter()
.map(|(rel, text)| db.upsert_file(&member_path(rel), text.to_string()))
.collect();
let mut packages = BTreeMap::new();
packages.insert("MyPkg".to_string(), Arc::new(pkg));
let mut roots = BTreeMap::new();
roots.insert("MyPkg".to_string(), PathBuf::from("/work/MyPkg"));
db.set_library(packages, roots, vec!["MyPkg".to_string()]);
db.set_workspace_files(handles.clone());
(db, handles)
}
}