use std::collections::{HashMap, HashSet};
use std::path::{Path, PathBuf};
use cairo_lang_filesystem::db::files_group_input;
use cairo_lang_filesystem::ids::CrateInput;
use salsa::Setter;
pub use self::configs_registry::{ConfigsRegistry, PackageConfig};
use crate::lang::db::AnalysisDatabase;
use crate::lang::proc_macros::controller::ProcMacroClientController;
use crate::project::Crate;
use crate::project::crate_data::CrateInfo;
use crate::state::{Owned, Snapshot};
mod configs_registry;
type WorkspaceRoot = PathBuf;
pub struct ProjectModel {
loaded_workspaces: HashMap<WorkspaceRoot, HashMap<CrateInput, Crate>>,
loaded_crates: HashMap<CrateInput, HashSet<WorkspaceRoot>>,
manifests_of_members_from_loaded_workspaces: Owned<HashSet<PathBuf>>,
configs_registry: Owned<ConfigsRegistry>,
remove_crates_from_db_on_next_update: bool,
}
impl ProjectModel {
pub fn new() -> Self {
Self {
loaded_workspaces: Default::default(),
loaded_crates: Default::default(),
manifests_of_members_from_loaded_workspaces: Default::default(),
configs_registry: Default::default(),
remove_crates_from_db_on_next_update: false,
}
}
pub fn configs_registry(&self) -> Snapshot<ConfigsRegistry> {
self.configs_registry.snapshot()
}
pub fn loaded_manifests(&self) -> Snapshot<HashSet<PathBuf>> {
self.manifests_of_members_from_loaded_workspaces.snapshot()
}
pub fn clear_loaded_workspaces(&mut self) {
self.loaded_workspaces.clear();
self.loaded_crates.clear();
self.manifests_of_members_from_loaded_workspaces.clear();
self.configs_registry.clear();
self.remove_crates_from_db_on_next_update = true;
}
pub fn load_workspace(
&mut self,
db: &mut AnalysisDatabase,
workspace_crates: Vec<CrateInfo>,
workspace_dir: PathBuf,
proc_macro_controller: &ProcMacroClientController,
) {
if self.remove_crates_from_db_on_next_update {
self.remove_crates_from_db_on_next_update = false;
files_group_input(db).set_crate_configs(db).to(Some(Default::default()));
}
let workspace_crates = workspace_crates
.into_iter()
.map(|cr_info| {
if cr_info.is_member {
self.manifests_of_members_from_loaded_workspaces
.insert(cr_info.manifest_path.clone());
}
self.configs_registry.insert(cr_info.manifest_path, cr_info.package_config);
(cr_info.cr.input(), cr_info.cr)
})
.collect();
if let Some(old_crates) = self.loaded_workspaces.get(&workspace_dir) {
if old_crates == &workspace_crates {
return;
}
ProjectModel::remove_crates(&mut self.loaded_crates, &workspace_dir, old_crates);
};
self.add_crates(workspace_crates, &workspace_dir);
self.apply_changes_to_db(db, proc_macro_controller);
}
pub fn apply_changes_to_db(
&self,
db: &mut AnalysisDatabase,
proc_macro_controller: &ProcMacroClientController,
) {
for (cr, workspaces) in &self.loaded_crates {
let same_crates: Vec<_> = workspaces
.iter()
.map(|ws| {
self.loaded_workspaces
.get(ws)
.expect("loaded_crates and loaded_workspaces are expected to be synchronised at this point")
.get(cr)
.expect("loaded_crates and loaded_workspaces are expected to be synchronised at this point")
})
.collect();
let merged_builtin_plugins = same_crates
.iter()
.map(|cr| cr.builtin_plugins.clone())
.reduce(|mut x, y| {
x.extend(y);
x
})
.expect("same_crates cannot be empty")
.clone();
let merged_settings = same_crates
.iter()
.map(|cr| cr.settings.clone())
.reduce(|mut x, y| {
x.cfg_set =
x.cfg_set.map(|cfg_set| cfg_set.union(&y.cfg_set.unwrap_or_default()));
x.dependencies.extend(y.dependencies);
x
})
.expect("same_crates cannot be empty");
let cr = Crate {
settings: merged_settings,
builtin_plugins: merged_builtin_plugins,
..same_crates.into_iter().next().expect("same_crates cannot be empty").clone()
};
let cr_input = cr.input();
let proc_macro_plugin_suite =
proc_macro_controller.proc_macro_plugin_suite_for_crate(&cr_input);
cr.apply(db, proc_macro_plugin_suite.cloned());
}
}
fn remove_crates(
loaded_crates: &mut HashMap<CrateInput, HashSet<PathBuf>>,
workspace_dir: &Path,
old_crates: &HashMap<CrateInput, Crate>,
) {
for old_cr in old_crates.keys() {
loaded_crates.entry(old_cr.clone()).and_modify(|paths| {
paths.remove(workspace_dir);
});
}
loaded_crates.retain(|_, paths| !paths.is_empty());
}
fn add_crates(&mut self, workspace_crates: HashMap<CrateInput, Crate>, workspace_dir: &Path) {
for cr in workspace_crates.keys() {
self.loaded_crates.entry(cr.clone()).or_default().insert(workspace_dir.to_path_buf());
}
self.loaded_workspaces.insert(workspace_dir.to_path_buf(), workspace_crates);
}
}