diff --git a/crates/prettier/src/prettier.rs b/crates/prettier/src/prettier.rs index 454f845aa4..d68af349d9 100644 --- a/crates/prettier/src/prettier.rs +++ b/crates/prettier/src/prettier.rs @@ -204,10 +204,3 @@ async fn find_closest_prettier_dir( } Ok(None) } - -async fn prepare_default_prettier( - fs: Arc, - node: Arc, -) -> anyhow::Result { - todo!("TODO kb need to call per language that supports it, and have to use extra packages sometimes") -} diff --git a/crates/project/src/project.rs b/crates/project/src/project.rs index bfcce50c01..ffc15ee0d3 100644 --- a/crates/project/src/project.rs +++ b/crates/project/src/project.rs @@ -20,7 +20,7 @@ use futures::{ mpsc::{self, UnboundedReceiver}, oneshot, }, - future::{try_join_all, Shared}, + future::{self, try_join_all, Shared}, stream::FuturesUnordered, AsyncWriteExt, Future, FutureExt, StreamExt, TryFutureExt, }; @@ -31,7 +31,9 @@ use gpui::{ }; use itertools::Itertools; use language::{ - language_settings::{language_settings, FormatOnSave, Formatter, InlayHintKind}, + language_settings::{ + language_settings, FormatOnSave, Formatter, InlayHintKind, LanguageSettings, + }, point_to_lsp, proto::{ deserialize_anchor, deserialize_fingerprint, deserialize_line_ending, deserialize_version, @@ -79,8 +81,11 @@ use std::{ use terminals::Terminals; use text::Anchor; use util::{ - debug_panic, defer, http::HttpClient, merge_json_value_into, - paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _, + debug_panic, defer, + http::HttpClient, + merge_json_value_into, + paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH}, + post_inc, ResultExt, TryFutureExt as _, }; pub use fs::*; @@ -832,17 +837,28 @@ impl Project { fn on_settings_changed(&mut self, cx: &mut ModelContext) { let mut language_servers_to_start = Vec::new(); + let mut language_formatters_to_check = Vec::new(); for buffer in self.opened_buffers.values() { if let Some(buffer) = buffer.upgrade(cx) { let buffer = buffer.read(cx); - if let Some((file, language)) = buffer.file().zip(buffer.language()) { - let settings = language_settings(Some(language), Some(file), cx); + let buffer_file = buffer.file(); + let buffer_language = buffer.language(); + let settings = language_settings(buffer_language, buffer_file, cx); + if let Some(language) = buffer_language { if settings.enable_language_server { - if let Some(file) = File::from_dyn(Some(file)) { + if let Some(file) = File::from_dyn(buffer_file) { language_servers_to_start - .push((file.worktree.clone(), language.clone())); + .push((file.worktree.clone(), Arc::clone(language))); } } + let worktree = buffer_file + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + language_formatters_to_check.push(( + worktree, + Arc::clone(language), + settings.clone(), + )); } } } @@ -895,6 +911,11 @@ impl Project { .detach(); } + // TODO kb restart all formatters if settings change + for (worktree, language, settings) in language_formatters_to_check { + self.maybe_start_default_formatters(worktree, &language, &settings, cx); + } + // Start all the newly-enabled language servers. for (worktree, language) in language_servers_to_start { let worktree_path = worktree.read(cx).abs_path(); @@ -2643,7 +2664,15 @@ impl Project { } }); - if let Some(file) = File::from_dyn(buffer.read(cx).file()) { + let buffer_file = buffer.read(cx).file().cloned(); + let worktree = buffer_file + .as_ref() + .map(|f| f.worktree_id()) + .map(WorktreeId::from_usize); + let settings = language_settings(Some(&new_language), buffer_file.as_ref(), cx).clone(); + self.maybe_start_default_formatters(worktree, &new_language, &settings, cx); + + if let Some(file) = File::from_dyn(buffer_file.as_ref()) { let worktree = file.worktree.clone(); if let Some(tree) = worktree.read(cx).as_local() { self.start_language_servers(&worktree, tree.abs_path().clone(), new_language, cx); @@ -2651,8 +2680,6 @@ impl Project { } } - // TODO kb 2 usages of this method (buffer language select + settings change) should take care of - // `LspAdapter::enabled_formatters` collecting and initializing. Remove `Option` for prettier instances? fn start_language_servers( &mut self, worktree: &ModelHandle, @@ -8279,6 +8306,81 @@ impl Project { }); Some(task) } + + fn maybe_start_default_formatters( + &mut self, + worktree: Option, + new_language: &Language, + language_settings: &LanguageSettings, + cx: &mut ModelContext, + ) { + match &language_settings.formatter { + Formatter::Prettier { .. } | Formatter::Auto => {} + Formatter::LanguageServer | Formatter::External { .. } => return, + }; + let Some(node) = self.node.as_ref().cloned() else { + return; + }; + + let mut prettier_plugins = None; + for formatter in new_language + .lsp_adapters() + .into_iter() + .flat_map(|adapter| adapter.enabled_formatters()) + { + match formatter { + BundledFormatter::Prettier { plugin_names } => prettier_plugins + .get_or_insert_with(|| HashSet::default()) + .extend(plugin_names), + } + } + let Some(prettier_plugins) = prettier_plugins else { + return; + }; + + let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); + if let Some(_already_running) = self + .prettier_instances + .get(&(worktree, default_prettier_dir.to_path_buf())) + { + // TODO kb need to compare plugins, install missing and restart prettier + return; + } + + let fs = Arc::clone(&self.fs); + cx.background() + .spawn(async move { + let prettier_dir_metadata = fs.metadata(default_prettier_dir).await.with_context(|| format!("fetching FS metadata for prettier default dir {default_prettier_dir:?}"))?; + if prettier_dir_metadata.is_none() { + fs.create_dir(default_prettier_dir).await.with_context(|| format!("creating prettier default dir {default_prettier_dir:?}"))?; + } + + let packages_to_versions = future::try_join_all( + prettier_plugins + .iter() + .map(|s| s.as_str()) + .chain(Some("prettier")) + .map(|package_name| async { + let returned_package_name = package_name.to_string(); + let latest_version = node.npm_package_latest_version(package_name) + .await + .with_context(|| { + format!("fetching latest npm version for package {returned_package_name}") + })?; + anyhow::Ok((returned_package_name, latest_version)) + }), + ) + .await + .context("fetching latest npm versions")?; + + let borrowed_packages = packages_to_versions.iter().map(|(package, version)| { + (package.as_str(), version.as_str()) + }).collect::>(); + node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?; + anyhow::Ok(()) + }) + .detach_and_log_err(cx); + } } fn subscribe_for_copilot_events(