use crate::ide::AnalysisHost;
use crate::project::file_loader;
use rayon::prelude::*;
use std::path::PathBuf;
use std::sync::{Arc, LazyLock};
struct CachedHost {
host: Arc<AnalysisHost>,
}
impl CachedHost {
fn load(stdlib_path: &PathBuf) -> Self {
let mut host = AnalysisHost::new();
if !stdlib_path.exists() || !stdlib_path.is_dir() {
return Self {
host: Arc::new(host),
};
}
let file_paths = file_loader::collect_file_paths(stdlib_path).unwrap_or_default();
let files: Vec<_> = file_paths
.par_iter()
.filter_map(|path| {
file_loader::load_and_parse(path)
.ok()
.map(|file| (path.clone(), file))
})
.collect();
for (path, file) in files {
host.set_file(path, file);
}
host.mark_dirty();
let _ = host.analysis();
Self {
host: Arc::new(host),
}
}
}
fn discover_stdlib_path() -> PathBuf {
use crate::base::constants::STDLIB_DIR;
if let Some(exe_dir) = std::env::current_exe()
.ok()
.and_then(|exe| exe.parent().map(|p| p.to_path_buf()))
{
let stdlib_next_to_exe = exe_dir.join(STDLIB_DIR);
if stdlib_next_to_exe.exists() && stdlib_next_to_exe.is_dir() {
return stdlib_next_to_exe;
}
}
if let Ok(manifest_dir) = std::env::var("CARGO_MANIFEST_DIR") {
let stdlib_in_manifest = PathBuf::from(&manifest_dir).join(STDLIB_DIR);
if stdlib_in_manifest.exists() && stdlib_in_manifest.is_dir() {
return stdlib_in_manifest;
}
let manifest_path = PathBuf::from(&manifest_dir);
for ancestor in manifest_path.ancestors().skip(1).take(3) {
let stdlib_in_ancestor = ancestor.join("base").join(STDLIB_DIR);
if stdlib_in_ancestor.exists() && stdlib_in_ancestor.is_dir() {
return stdlib_in_ancestor;
}
}
}
PathBuf::from(STDLIB_DIR)
}
static CACHED_HOST: LazyLock<CachedHost> =
LazyLock::new(|| CachedHost::load(&discover_stdlib_path()));
pub struct CachedStdLib;
impl CachedStdLib {
pub fn analysis_host() -> AnalysisHost {
(*CACHED_HOST.host).clone()
}
pub fn analysis_host_arc() -> Arc<AnalysisHost> {
CACHED_HOST.host.clone()
}
pub fn load_into(host: &mut AnalysisHost) {
for (path, file) in CACHED_HOST.host.files() {
host.set_file(path.clone(), file.clone());
}
host.mark_dirty();
}
pub fn file_count() -> usize {
CACHED_HOST.host.file_count()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cached_stdlib_loads_files() {
let host = CachedStdLib::analysis_host();
assert!(
host.file_count() > 50,
"Expected 50+ stdlib files, got {}",
host.file_count()
);
}
#[test]
fn test_cached_stdlib_arc_is_fast() {
use std::time::Instant;
let _arc1 = CachedStdLib::analysis_host_arc();
let start = Instant::now();
let _arc2 = CachedStdLib::analysis_host_arc();
let elapsed = start.elapsed();
assert!(
elapsed.as_micros() < 1000,
"Arc clone took {:?}, expected <1ms",
elapsed
);
}
#[test]
fn test_cached_host_has_built_index() {
let mut host = CachedStdLib::analysis_host();
let analysis = host.analysis();
let symbol_count = analysis.symbol_index().all_symbols().count();
assert!(
symbol_count > 100,
"Expected 100+ symbols, got {}",
symbol_count
);
}
#[test]
fn test_cached_stdlib_file_count() {
assert!(CachedStdLib::file_count() > 50, "Expected 50+ files");
}
}