use std::path::Path;
use anyhow::{Context, Result};
use chrono::{DateTime, Utc};
use rayon::prelude::*;
use uuid::Uuid;
use ra_ap_ide::{Analysis, AnalysisHost, FileId};
use ra_ap_load_cargo::{load_workspace_at, LoadCargoConfig, ProcMacroServerChoice};
use ra_ap_project_model::CargoConfig;
use ra_ap_vfs::{Vfs, VfsPath};
use super::scip::{ScipRow, ScipScan};
struct DefSite {
file_id: FileId,
offset: ra_ap_ide::TextSize,
label: String,
kind: String,
file: String,
line: u32,
col: u32,
}
pub fn ingest_in_process(
root: &Path,
repo: &str,
git_sha: &str,
snapshot_id: Uuid,
ts: DateTime<Utc>,
) -> Result<ScipScan> {
let cargo_config = CargoConfig::default();
let load_config = LoadCargoConfig {
load_out_dirs_from_check: true,
with_proc_macro_server: ProcMacroServerChoice::Sysroot,
prefill_caches: false,
num_worker_threads: 0,
proc_macro_processes: 0,
};
let (db, vfs, _proc_macro) = load_workspace_at(
root,
&cargo_config,
&load_config,
&|_progress| {},
)
.with_context(|| format!("loading cargo workspace at {} into rust-analyzer", root.display()))?;
let host = AnalysisHost::with_database(db);
let analysis = host.analysis();
let root_str = root.to_string_lossy().replace('\\', "/");
let files: Vec<(FileId, String)> = vfs
.iter()
.filter_map(|(file_id, path)| vfs_rel_path(path, &root_str).map(|p| (file_id, p)))
.filter(|(_, p)| p.ends_with(".rs"))
.collect();
let mut defs: Vec<DefSite> = Vec::new();
for (file_id, file) in &files {
let struct_cfg = ra_ap_ide::FileStructureConfig { exclude_locals: true };
let Ok(nodes) = analysis.file_structure(&struct_cfg, *file_id) else { continue };
let Ok(line_index) = analysis.file_line_index(*file_id) else { continue };
for node in nodes {
let off = node.navigation_range.start();
let lc = line_index.line_col(off);
defs.push(DefSite {
file_id: *file_id,
offset: off,
label: node.label,
kind: format!("{:?}", node.kind),
file: file.clone(),
line: lc.line + 1,
col: lc.col + 1,
});
}
}
let n_threads = rayon::current_num_threads().max(1);
let chunk_len = defs.len().div_ceil(n_threads).max(1);
let work: Vec<(&[DefSite], Analysis)> = defs
.chunks(chunk_len)
.map(|chunk| (chunk, host.analysis()))
.collect();
let vfs_ref = &vfs;
let root_ref = root_str.as_str();
let rows: Vec<ScipRow> = work
.into_par_iter()
.flat_map_iter(move |(chunk, analysis)| {
chunk
.iter()
.flat_map(|def| def_and_refs(&analysis, vfs_ref, root_ref, def))
.collect::<Vec<_>>()
.into_iter()
})
.collect();
Ok(ScipScan {
snapshot_id,
ts,
repo: repo.to_string(),
git_sha: git_sha.to_string(),
rows,
})
}
fn def_and_refs(
analysis: &Analysis,
vfs: &Vfs,
root_str: &str,
def: &DefSite,
) -> Vec<ScipRow> {
let symbol = format!("{}#L{}:C{}", def.file, def.line, def.col);
let mut out = Vec::new();
out.push(ScipRow {
symbol: symbol.clone(),
role: "definition".to_string(),
is_definition: true,
display_name: def.label.clone(),
kind: def.kind.clone(),
file: def.file.clone(),
start_line: def.line,
start_col: def.col,
});
let pos = ra_ap_ide::FilePosition { file_id: def.file_id, offset: def.offset };
let refs_cfg = ra_ap_ide::FindAllRefsConfig {
search_scope: None,
ra_fixture: ra_ap_ide::RaFixtureConfig::default(),
exclude_imports: false,
exclude_tests: false,
};
let Ok(Some(refs)) = analysis.find_all_refs(pos, &refs_cfg) else {
return out;
};
for res in refs {
for (file_id, ranges) in res.references {
let Some(path) = vfs_rel_path(vfs.file_path(file_id), root_str) else { continue };
let Ok(line_index) = analysis.file_line_index(file_id) else { continue };
for (range, category) in ranges {
let lc = line_index.line_col(range.start());
out.push(ScipRow {
symbol: symbol.clone(),
role: ref_category_label(category),
is_definition: false,
display_name: def.label.clone(),
kind: String::new(),
file: path.clone(),
start_line: lc.line + 1,
start_col: lc.col + 1,
});
}
}
}
out
}
fn ref_category_label(cat: ra_ap_ide::ReferenceCategory) -> String {
use ra_ap_ide::ReferenceCategory as C;
let mut labels: Vec<&str> = Vec::new();
if cat.contains(C::WRITE) {
labels.push("write");
}
if cat.contains(C::READ) {
labels.push("read");
}
if cat.contains(C::IMPORT) {
labels.push("import");
}
if cat.contains(C::TEST) {
labels.push("test");
}
if labels.is_empty() {
labels.push("reference");
}
labels.join("+")
}
fn vfs_rel_path(path: &VfsPath, root_str: &str) -> Option<String> {
let p = path.as_path()?;
let s = p.to_string().replace('\\', "/");
let rel = s.strip_prefix(root_str).unwrap_or(&s);
Some(rel.trim_start_matches('/').to_string())
}