use std::cell::Cell;
use std::cell::RefCell;
use std::collections::HashMap;
use std::path::Path;
use std::path::PathBuf;
use std::rc::Rc;
use anyhow::Context;
use anyhow::Result;
use dprint_core::async_runtime::FutureExt;
use dprint_core::async_runtime::LocalBoxFuture;
use crate::configuration::ResolvedConfig;
use crate::configuration::ResolvedConfigPathWithText;
use crate::configuration::get_default_config_file_in_ancestor_directories;
use crate::configuration::inherit_config;
use crate::configuration::resolve_config_from_path_with_bytes;
use crate::configuration::resolve_global_config_path_and_text;
use crate::environment::Environment;
use crate::plugins;
use crate::resolution::PluginsScope;
use crate::resolution::resolve_plugins_scope;
use crate::utils::AsyncMutex;
use crate::utils::PathSource;
type ScopeCell<TEnvironment> = AsyncMutex<Option<CachedScope<TEnvironment>>>;
struct CachedScope<TEnvironment: Environment> {
scope: Rc<PluginsScope<TEnvironment>>,
plugins_generation: usize,
}
pub struct LspPluginsScopeContainer<TEnvironment: Environment> {
environment: TEnvironment,
plugin_resolver: Rc<plugins::PluginResolver<TEnvironment>>,
plugins_scope_by_config: RefCell<HashMap<String, Rc<ScopeCell<TEnvironment>>>>,
config_override: Option<PathBuf>,
plugins_generation: Cell<usize>,
}
impl<TEnvironment: Environment> LspPluginsScopeContainer<TEnvironment> {
pub fn new(environment: TEnvironment, plugin_resolver: Rc<plugins::PluginResolver<TEnvironment>>, config_override: Option<PathBuf>) -> Self {
Self {
environment,
plugin_resolver,
plugins_scope_by_config: Default::default(),
config_override,
plugins_generation: Default::default(),
}
}
pub async fn shutdown(&self) {
self.plugins_scope_by_config.borrow_mut().clear();
self.plugin_resolver.clear_and_shutdown_initialized().await;
}
pub async fn resolve_by_path(&self, dir_path: &Path, use_global_config: bool) -> Result<Option<Rc<PluginsScope<TEnvironment>>>> {
let config_file_bytes = if let Some(path) = &self.config_override {
let path = self.environment.canonicalize(path).context("failed resolving --config path")?;
let content = self.environment.read_file(&path).context("failed resolving --config path")?;
Some(ResolvedConfigPathWithText {
base_path: path.parent().unwrap_or_else(|| path.clone()),
source: PathSource::new_local(path),
is_first_download: false,
content,
is_global_config: false,
})
} else {
match get_default_config_file_in_ancestor_directories(&self.environment, dir_path)? {
Some(config) => Some(config),
None if use_global_config => self.resolve_global_config_file(dir_path)?,
None => None,
}
};
let Some(config_file_bytes) = config_file_bytes else {
return Ok(None);
};
let cell = {
let key = format!("{}\n{}", config_file_bytes.source.display(), config_file_bytes.base_path.display());
let mut plugins_scope_by_config = self.plugins_scope_by_config.borrow_mut();
plugins_scope_by_config.entry(key).or_default().clone()
};
let mut cell = cell.lock().await;
let config = self.resolve_config(&config_file_bytes, use_global_config).await?;
if let Some(cached_scope) = cell.as_ref() {
let is_same_config = cached_scope.scope.config.as_deref() == Some(&config);
if is_same_config && cached_scope.plugins_generation == self.plugins_generation.get() {
return Ok(Some(cached_scope.scope.clone()));
}
cell.take();
if !is_same_config {
self.plugins_generation.set(self.plugins_generation.get() + 1);
self.plugin_resolver.clear_and_shutdown_initialized().await;
}
}
let plugins_generation = self.plugins_generation.get();
let new_scope = Rc::new(resolve_plugins_scope(Rc::new(config), &self.environment, &self.plugin_resolver).await?);
let _ = cell.insert(CachedScope {
scope: new_scope.clone(),
plugins_generation,
});
Ok(Some(new_scope))
}
fn resolve_config<'a>(&'a self, config_file: &'a ResolvedConfigPathWithText, use_global_config: bool) -> LocalBoxFuture<'a, Result<ResolvedConfig>> {
async move {
let config = resolve_config_from_path_with_bytes(config_file, &self.environment).await?;
if config.inherit != Some(true) || config.is_global || self.config_override.is_some() {
return Ok(config);
}
let Some(parent_dir) = config.base_path.parent() else {
return Ok(config);
};
let ancestor_config_file = match get_default_config_file_in_ancestor_directories(&self.environment, parent_dir.as_ref())? {
Some(config_file) => Some(config_file),
None if use_global_config => self.resolve_global_config_file(parent_dir.as_ref())?,
None => None,
};
let Some(ancestor_config_file) = ancestor_config_file else {
return Ok(config);
};
let ancestor_config = self.resolve_config(&ancestor_config_file, use_global_config).await?;
inherit_config(config, &ancestor_config)
}
.boxed_local()
}
fn resolve_global_config_file(&self, dir_path: &Path) -> Result<Option<ResolvedConfigPathWithText>> {
let Some(config_file) = resolve_global_config_path_and_text(&self.environment)? else {
return Ok(None);
};
let root_dir = dir_path.ancestors().last().unwrap_or(dir_path);
Ok(Some(ResolvedConfigPathWithText {
base_path: self.environment.canonicalize(root_dir)?,
..config_file
}))
}
}