diff --git a/crates/prettier2/src/prettier2.rs b/crates/prettier2/src/prettier2.rs index 6b13429eec..a71bf1a8b0 100644 --- a/crates/prettier2/src/prettier2.rs +++ b/crates/prettier2/src/prettier2.rs @@ -44,6 +44,9 @@ pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js"); const PRETTIER_PACKAGE_NAME: &str = "prettier"; const TAILWIND_PRETTIER_PLUGIN_PACKAGE_NAME: &str = "prettier-plugin-tailwindcss"; +#[cfg(any(test, feature = "test-support"))] +pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; + impl Prettier { pub const CONFIG_FILE_NAMES: &'static [&'static str] = &[ ".prettierrc", @@ -60,9 +63,6 @@ impl Prettier { ".editorconfig", ]; - #[cfg(any(test, feature = "test-support"))] - pub const FORMAT_SUFFIX: &str = "\nformatted by test prettier"; - pub async fn locate( starting_path: Option, fs: Arc, @@ -328,7 +328,7 @@ impl Prettier { #[cfg(any(test, feature = "test-support"))] Self::Test(_) => Ok(buffer .update(cx, |buffer, cx| { - let formatted_text = buffer.text() + Self::FORMAT_SUFFIX; + let formatted_text = buffer.text() + FORMAT_SUFFIX; buffer.diff(formatted_text, cx) })? .await), diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index fdc1a2a0eb..fe062a78a4 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -54,7 +54,7 @@ use lsp_command::*; use node_runtime::NodeRuntime; use parking_lot::Mutex; use postage::watch; -use prettier2::{LocateStart, Prettier, PRETTIER_SERVER_FILE, PRETTIER_SERVER_JS}; +use prettier2::{LocateStart, Prettier}; use project_settings::{LspSettings, ProjectSettings}; use rand::prelude::*; use search::SearchQuery; @@ -80,16 +80,15 @@ use std::{ time::{Duration, Instant}, }; use terminals::Terminals; -use text::{Anchor, LineEnding, Rope}; +use text::Anchor; use util::{ - debug_panic, defer, - http::HttpClient, - merge_json_value_into, - paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, - post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, http::HttpClient, merge_json_value_into, + paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, }; pub use fs2::*; +#[cfg(any(test, feature = "test-support"))] +pub use prettier2::FORMAT_SUFFIX as TEST_PRETTIER_FORMAT_SUFFIX; pub use worktree::*; const MAX_SERVER_REINSTALL_ATTEMPT_COUNT: u64 = 4; @@ -163,12 +162,22 @@ pub struct Project { copilot_log_subscription: Option, current_lsp_settings: HashMap, LspSettings>, node: Option>, + // TODO kb uncomment + // #[cfg(not(any(test, feature = "test-support")))] + default_prettier: Option, prettier_instances: HashMap< (Option, PathBuf), Shared, Arc>>>, >, } +// TODO kb uncomment +// #[cfg(not(any(test, feature = "test-support")))] +struct DefaultPrettier { + installation_process: Option>>, + installed_plugins: HashSet<&'static str>, +} + struct DelayedDebounced { task: Option>, cancel_channel: Option>, @@ -679,6 +688,9 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(), node: Some(node), + // TODO kb uncomment + // #[cfg(not(any(test, feature = "test-support")))] + default_prettier: None, prettier_instances: HashMap::default(), } }) @@ -780,6 +792,9 @@ impl Project { copilot_log_subscription: None, current_lsp_settings: ProjectSettings::get_global(cx).lsp.clone(), node: None, + // TODO kb uncomment + // #[cfg(not(any(test, feature = "test-support")))] + default_prettier: None, prettier_instances: HashMap::default(), }; for worktree in worktrees { @@ -8553,8 +8568,21 @@ impl Project { } } + // TODO kb uncomment + // #[cfg(any(test, feature = "test-support"))] + // fn install_default_formatters( + // &mut self, + // _: Option, + // _: &Language, + // _: &LanguageSettings, + // _: &mut ModelContext, + // ) -> Task> { + // Task::ready(Ok(())) + // } + + // #[cfg(not(any(test, feature = "test-support")))] fn install_default_formatters( - &self, + &mut self, worktree: Option, new_language: &Language, language_settings: &LanguageSettings, @@ -8583,22 +8611,76 @@ impl Project { return Task::ready(Ok(())); }; - let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); + let mut plugins_to_install = prettier_plugins; + let (mut install_success_tx, mut install_success_rx) = + futures::channel::mpsc::channel::>(1); + let new_installation_process = cx + .spawn(|this, mut cx| async move { + if let Some(installed_plugins) = install_success_rx.next().await { + this.update(&mut cx, |this, _| { + let default_prettier = + this.default_prettier + .get_or_insert_with(|| DefaultPrettier { + installation_process: None, + installed_plugins: HashSet::default(), + }); + if !installed_plugins.is_empty() { + log::info!("Installed new prettier plugins: {installed_plugins:?}"); + default_prettier.installed_plugins.extend(installed_plugins); + } + }) + .ok(); + } + }) + .shared(); + let previous_installation_process = + if let Some(default_prettier) = &mut self.default_prettier { + plugins_to_install + .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); + if plugins_to_install.is_empty() { + return Task::ready(Ok(())); + } + std::mem::replace( + &mut default_prettier.installation_process, + Some(new_installation_process.clone()), + ) + } else { + None + }; + + let default_prettier_dir = util::paths::DEFAULT_PRETTIER_DIR.as_path(); let already_running_prettier = self .prettier_instances .get(&(worktree, default_prettier_dir.to_path_buf())) .cloned(); - let fs = Arc::clone(&self.fs); - cx.executor() - .spawn(async move { - let prettier_wrapper_path = default_prettier_dir.join(PRETTIER_SERVER_FILE); + cx.spawn_on_main(move |this, mut cx| async move { + if let Some(previous_installation_process) = previous_installation_process { + previous_installation_process.await; + } + let mut everything_was_installed = false; + this.update(&mut cx, |this, _| { + match &mut this.default_prettier { + Some(default_prettier) => { + plugins_to_install + .retain(|plugin| !default_prettier.installed_plugins.contains(plugin)); + everything_was_installed = plugins_to_install.is_empty(); + }, + None => this.default_prettier = Some(DefaultPrettier { installation_process: Some(new_installation_process), installed_plugins: HashSet::default() }), + } + })?; + if everything_was_installed { + return Ok(()); + } + + cx.spawn(move |_| async move { + let prettier_wrapper_path = default_prettier_dir.join(prettier2::PRETTIER_SERVER_FILE); // method creates parent directory if it doesn't exist - fs.save(&prettier_wrapper_path, &Rope::from(PRETTIER_SERVER_JS), LineEnding::Unix).await - .with_context(|| format!("writing {PRETTIER_SERVER_FILE} file at {prettier_wrapper_path:?}"))?; + fs.save(&prettier_wrapper_path, &text::Rope::from(prettier2::PRETTIER_SERVER_JS), text::LineEnding::Unix).await + .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier2::PRETTIER_SERVER_FILE))?; let packages_to_versions = future::try_join_all( - prettier_plugins + plugins_to_install .iter() .chain(Some(&"prettier")) .map(|package_name| async { @@ -8619,15 +8701,18 @@ impl Project { (package.as_str(), version.as_str()) }).collect::>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + let installed_packages = !plugins_to_install.is_empty(); + install_success_tx.try_send(plugins_to_install).ok(); - if !prettier_plugins.is_empty() { + if !installed_packages { if let Some(prettier) = already_running_prettier { prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?; } } anyhow::Ok(()) - }) + }).await + }) } } diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index d73769cc00..045fd7953e 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -2659,12 +2659,12 @@ impl language2::File for File { impl language2::LocalFile for File { fn abs_path(&self, cx: &AppContext) -> PathBuf { - self.worktree - .read(cx) - .as_local() - .unwrap() - .abs_path - .join(&self.path) + let worktree_path = &self.worktree.read(cx).as_local().unwrap().abs_path; + if self.path.as_ref() == Path::new("") { + worktree_path.to_path_buf() + } else { + worktree_path.join(&self.path) + } } fn load(&self, cx: &AppContext) -> Task> {