use std::{ ops::ControlFlow, path::{Path, PathBuf}, sync::Arc,}; use anyhow::{anyhow, Context, Result};use collections::HashSet;use fs::Fs;use futures::{ future::{self, Shared}, FutureExt,};use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};use language::{ language_settings::{Formatter, LanguageSettings}, Buffer, LanguageServerName, LocalFile,};use lsp::{LanguageServer, LanguageServerId};use node_runtime::NodeRuntime;use prettier::Prettier;use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt}; use crate::{ Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,}; pub fn prettier_plugins_for_language( language_settings: &LanguageSettings,) -> Option<&HashSet<String>> { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => Some(&language_settings.prettier.plugins), Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => None, }} pub(super) async fn format_with_prettier( project: &WeakModel<Project>, buffer: &Model<Buffer>, cx: &mut AsyncAppContext,) -> Option<Result<FormatOperation>> { let prettier_instance = project .update(cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) }) .ok()? .await; let Some((prettier_path, prettier_task)) = prettier_instance else { return None; }; let prettier_description = match prettier_path.as_ref() { Some(path) => format!("prettier at {path:?}"), None => "default prettier instance".to_string(), }; match prettier_task.await { Ok(prettier) => { let buffer_path = buffer .update(cx, |buffer, cx| { File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) }) .ok()?; let format_result = prettier .format(buffer, buffer_path, cx) .await .map(FormatOperation::Prettier) .with_context(|| format!("{} failed to format buffer", prettier_description)); Some(format_result) } Err(error) => { project .update(cx, |project, _| { let instance_to_update = match prettier_path { Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path), None => match &mut project.default_prettier.prettier { PrettierInstallation::NotInstalled { .. } => None, PrettierInstallation::Installed(instance) => Some(instance), }, }; if let Some(instance) = instance_to_update { instance.attempt += 1; instance.prettier = None; } }) .log_err(); Some(Err(anyhow!( "{} failed to spawn: {error:#}", prettier_description ))) } }} pub struct DefaultPrettier { prettier: PrettierInstallation, installed_plugins: HashSet<Arc<str>>,} #[derive(Debug)]pub enum PrettierInstallation { NotInstalled { attempts: usize, installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>, not_installed_plugins: HashSet<Arc<str>>, }, Installed(PrettierInstance),} pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>; #[derive(Debug, Clone)]pub struct PrettierInstance { attempt: usize, prettier: Option<PrettierTask>,} impl Default for DefaultPrettier { fn default() -> Self { Self { prettier: PrettierInstallation::NotInstalled { attempts: 0, installation_task: None, not_installed_plugins: HashSet::default(), }, installed_plugins: HashSet::default(), } }} impl DefaultPrettier { pub fn instance(&self) -> Option<&PrettierInstance> { if let PrettierInstallation::Installed(instance) = &self.prettier { Some(instance) } else { None } } pub fn prettier_task( &mut self, node: &Arc<dyn NodeRuntime>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> Option<Task<anyhow::Result<PrettierTask>>> { match &mut self.prettier { PrettierInstallation::NotInstalled { .. } => { Some(start_default_prettier(Arc::clone(node), worktree_id, cx)) } PrettierInstallation::Installed(existing_instance) => { existing_instance.prettier_task(node, None, worktree_id, cx) } } }} impl PrettierInstance { pub fn prettier_task( &mut self, node: &Arc<dyn NodeRuntime>, prettier_dir: Option<&Path>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> Option<Task<anyhow::Result<PrettierTask>>> { if self.attempt > prettier::FAIL_THRESHOLD { match prettier_dir { Some(prettier_dir) => log::warn!( "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting" ), None => log::warn!("Default prettier exceeded launch threshold, not starting"), } return None; } Some(match &self.prettier { Some(prettier_task) => Task::ready(Ok(prettier_task.clone())), None => match prettier_dir { Some(prettier_dir) => { let new_task = start_prettier( Arc::clone(node), prettier_dir.to_path_buf(), worktree_id, cx, ); self.attempt += 1; self.prettier = Some(new_task.clone()); Task::ready(Ok(new_task)) } None => { self.attempt += 1; let node = Arc::clone(node); cx.spawn(|project, mut cx| async move { project .update(&mut cx, |_, cx| { start_default_prettier(node, worktree_id, cx) })? .await }) } }, }) } pub async fn server(&self) -> Option<Arc<LanguageServer>> { self.prettier.clone()?.await.ok()?.server().cloned() }} fn start_default_prettier( node: Arc<dyn NodeRuntime>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>,) -> Task<anyhow::Result<PrettierTask>> { cx.spawn(|project, mut cx| async move { let installation_task = project.update(&mut cx, |project, _| { match &project.default_prettier.prettier { PrettierInstallation::NotInstalled { installation_task, .. } => ControlFlow::Continue(installation_task.clone()), PrettierInstallation::Installed(default_prettier) => { ControlFlow::Break(default_prettier.clone()) } } })?; match installation_task { ControlFlow::Continue(None) => { anyhow::bail!("Default prettier is not installed and cannot be started") } ControlFlow::Continue(Some(installation_task)) => { log::info!("Waiting for default prettier to install"); if let Err(e) = installation_task.await { project.update(&mut cx, |project, _| { if let PrettierInstallation::NotInstalled { installation_task, attempts, .. } = &mut project.default_prettier.prettier { *installation_task = None; *attempts += 1; } })?; anyhow::bail!( "Cannot start default prettier due to its installation failure: {e:#}" ); } let new_default_prettier = project.update(&mut cx, |project, cx| { let new_default_prettier = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); project.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: 0, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; return Ok(new_default_prettier); } ControlFlow::Break(instance) => match instance.prettier { Some(instance) => return Ok(instance), None => { let new_default_prettier = project.update(&mut cx, |project, cx| { let new_default_prettier = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); project.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: instance.attempt + 1, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; return Ok(new_default_prettier); } }, } })} fn start_prettier( node: Arc<dyn NodeRuntime>, prettier_dir: PathBuf, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>,) -> PrettierTask { cx.spawn(|project, mut cx| async move { log::info!("Starting prettier at path {prettier_dir:?}"); let new_server_id = project.update(&mut cx, |project, _| { project.languages.next_language_server_id() })?; let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone()) .await .context("default prettier spawn") .map(Arc::new) .map_err(Arc::new)?; register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); Ok(new_prettier) }) .shared()} fn register_new_prettier( project: &WeakModel<Project>, prettier: &Prettier, worktree_id: Option<WorktreeId>, new_server_id: LanguageServerId, cx: &mut AsyncAppContext,) { let prettier_dir = prettier.prettier_dir(); let is_default = prettier.is_default(); if is_default { log::info!("Started default prettier in {prettier_dir:?}"); } else { log::info!("Started prettier in {prettier_dir:?}"); } if let Some(prettier_server) = prettier.server() { project .update(cx, |project, cx| { let name = if is_default { LanguageServerName(Arc::from("prettier (default)")) } else { let worktree_path = worktree_id .and_then(|id| project.worktree_for_id(id, cx)) .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path())); let name = match worktree_path { Some(worktree_path) => { if prettier_dir == worktree_path.as_ref() { let name = prettier_dir .file_name() .and_then(|name| name.to_str()) .unwrap_or_default(); format!("prettier ({name})") } else { let dir_to_display = prettier_dir .strip_prefix(worktree_path.as_ref()) .ok() .unwrap_or(prettier_dir); format!("prettier ({})", dir_to_display.display()) } } None => format!("prettier ({})", prettier_dir.display()), }; LanguageServerName(Arc::from(name)) }; project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier_server))); cx.emit(Event::LanguageServerAdded(new_server_id)); }) .ok(); }} async fn install_prettier_packages( fs: &dyn Fs, plugins_to_install: HashSet<Arc<str>>, node: Arc<dyn NodeRuntime>,) -> anyhow::Result<()> { let packages_to_versions = future::try_join_all( plugins_to_install .iter() .chain(Some(&"prettier".into())) .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 default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); match fs.metadata(default_prettier_dir).await.with_context(|| { format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}") })? { Some(prettier_dir_metadata) => anyhow::ensure!( prettier_dir_metadata.is_dir, "default prettier dir {default_prettier_dir:?} is not a directory" ), None => fs .create_dir(default_prettier_dir) .await .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?, } log::info!("Installing default prettier and plugins: {packages_to_versions:?}"); let borrowed_packages = packages_to_versions .iter() .map(|(package, version)| (package.as_str(), version.as_str())) .collect::<Vec<_>>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages) .await .context("fetching formatter packages")?; anyhow::Ok(())} async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> { let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE); fs.save( &prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix, ) .await .with_context(|| { format!( "writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE ) })?; Ok(())} async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool { let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE); if !fs.is_file(&prettier_wrapper_path).await { return true; } let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else { return true; }; prettier_server_file_contents != prettier::PRETTIER_SERVER_JS} impl Project { pub fn update_prettier_settings( &self, worktree: &Model<Worktree>, changes: &[(Arc<Path>, ProjectEntryId, PathChange)], cx: &mut ModelContext<'_, Project>, ) { let prettier_config_files = Prettier::CONFIG_FILE_NAMES .iter() .map(Path::new) .collect::<HashSet<_>>(); let prettier_config_file_changed = changes .iter() .filter(|(_, _, change)| !matches!(change, PathChange::Loaded)) .filter(|(path, _, _)| { !path .components() .any(|component| component.as_os_str().to_string_lossy() == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let current_worktree_id = worktree.read(cx).id(); if let Some((config_path, _, _)) = prettier_config_file_changed { log::info!( "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); let prettiers_to_reload = self.prettiers_per_worktree .get(¤t_worktree_id) .iter() .flat_map(|prettier_paths| prettier_paths.iter()) .flatten() .filter_map(|prettier_path| { Some(( current_worktree_id, Some(prettier_path.clone()), self.prettier_instances.get(prettier_path)?.clone(), )) }) .chain(self.default_prettier.instance().map(|default_prettier| { (current_worktree_id, None, default_prettier.clone()) })) .collect::<Vec<_>>(); cx.background_executor() .spawn(async move { let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| { async move { if let Some(instance) = prettier_instance.prettier { match instance.await { Ok(prettier) => { prettier.clear_cache().log_err().await; }, Err(e) => { match prettier_path { Some(prettier_path) => log::error!( "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}" ), None => log::error!( "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}" ), } }, } } } }))Lines 1–500 of 773. Load more
use std::{ ops::ControlFlow, path::{Path, PathBuf}, sync::Arc, }; use anyhow::{anyhow, Context, Result}; use collections::HashSet; use fs::Fs; use futures::{ future::{self, Shared}, FutureExt, }; use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel}; use language::{ language_settings::{Formatter, LanguageSettings}, Buffer, LanguageServerName, LocalFile, }; use lsp::{LanguageServer, LanguageServerId}; use node_runtime::NodeRuntime; use prettier::Prettier; use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt}; use crate::{ Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId, }; pub fn prettier_plugins_for_language( language_settings: &LanguageSettings, ) -> Option<&HashSet<String>> { match &language_settings.formatter { Formatter::Prettier { .. } | Formatter::Auto => Some(&language_settings.prettier.plugins), Formatter::LanguageServer | Formatter::External { .. } | Formatter::CodeActions(_) => None, } } pub(super) async fn format_with_prettier( project: &WeakModel<Project>, buffer: &Model<Buffer>, cx: &mut AsyncAppContext, ) -> Option<Result<FormatOperation>> { let prettier_instance = project .update(cx, |project, cx| { project.prettier_instance_for_buffer(buffer, cx) }) .ok()? .await; let Some((prettier_path, prettier_task)) = prettier_instance else { return None; }; let prettier_description = match prettier_path.as_ref() { Some(path) => format!("prettier at {path:?}"), None => "default prettier instance".to_string(), }; match prettier_task.await { Ok(prettier) => { let buffer_path = buffer .update(cx, |buffer, cx| { File::from_dyn(buffer.file()).map(|file| file.abs_path(cx)) }) .ok()?; let format_result = prettier .format(buffer, buffer_path, cx) .await .map(FormatOperation::Prettier) .with_context(|| format!("{} failed to format buffer", prettier_description)); Some(format_result) } Err(error) => { project .update(cx, |project, _| { let instance_to_update = match prettier_path { Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path), None => match &mut project.default_prettier.prettier { PrettierInstallation::NotInstalled { .. } => None, PrettierInstallation::Installed(instance) => Some(instance), }, }; if let Some(instance) = instance_to_update { instance.attempt += 1; instance.prettier = None; } }) .log_err(); Some(Err(anyhow!( "{} failed to spawn: {error:#}", prettier_description ))) } } } pub struct DefaultPrettier { prettier: PrettierInstallation, installed_plugins: HashSet<Arc<str>>, } #[derive(Debug)] pub enum PrettierInstallation { NotInstalled { attempts: usize, installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>, not_installed_plugins: HashSet<Arc<str>>, }, Installed(PrettierInstance), } pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>; #[derive(Debug, Clone)] pub struct PrettierInstance { attempt: usize, prettier: Option<PrettierTask>, } impl Default for DefaultPrettier { fn default() -> Self { Self { prettier: PrettierInstallation::NotInstalled { attempts: 0, installation_task: None, not_installed_plugins: HashSet::default(), }, installed_plugins: HashSet::default(), } } } impl DefaultPrettier { pub fn instance(&self) -> Option<&PrettierInstance> { if let PrettierInstallation::Installed(instance) = &self.prettier { Some(instance) } else { None } } pub fn prettier_task( &mut self, node: &Arc<dyn NodeRuntime>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> Option<Task<anyhow::Result<PrettierTask>>> { match &mut self.prettier { PrettierInstallation::NotInstalled { .. } => { Some(start_default_prettier(Arc::clone(node), worktree_id, cx)) } PrettierInstallation::Installed(existing_instance) => { existing_instance.prettier_task(node, None, worktree_id, cx) } } } } impl PrettierInstance { pub fn prettier_task( &mut self, node: &Arc<dyn NodeRuntime>, prettier_dir: Option<&Path>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> Option<Task<anyhow::Result<PrettierTask>>> { if self.attempt > prettier::FAIL_THRESHOLD { match prettier_dir { Some(prettier_dir) => log::warn!( "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting" ), None => log::warn!("Default prettier exceeded launch threshold, not starting"), } return None; } Some(match &self.prettier { Some(prettier_task) => Task::ready(Ok(prettier_task.clone())), None => match prettier_dir { Some(prettier_dir) => { let new_task = start_prettier( Arc::clone(node), prettier_dir.to_path_buf(), worktree_id, cx, ); self.attempt += 1; self.prettier = Some(new_task.clone()); Task::ready(Ok(new_task)) } None => { self.attempt += 1; let node = Arc::clone(node); cx.spawn(|project, mut cx| async move { project .update(&mut cx, |_, cx| { start_default_prettier(node, worktree_id, cx) })? .await }) } }, }) } pub async fn server(&self) -> Option<Arc<LanguageServer>> { self.prettier.clone()?.await.ok()?.server().cloned() } } fn start_default_prettier( node: Arc<dyn NodeRuntime>, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> Task<anyhow::Result<PrettierTask>> { cx.spawn(|project, mut cx| async move { let installation_task = project.update(&mut cx, |project, _| { match &project.default_prettier.prettier { PrettierInstallation::NotInstalled { installation_task, .. } => ControlFlow::Continue(installation_task.clone()), PrettierInstallation::Installed(default_prettier) => { ControlFlow::Break(default_prettier.clone()) } } })?; match installation_task { ControlFlow::Continue(None) => { anyhow::bail!("Default prettier is not installed and cannot be started") } ControlFlow::Continue(Some(installation_task)) => { log::info!("Waiting for default prettier to install"); if let Err(e) = installation_task.await { project.update(&mut cx, |project, _| { if let PrettierInstallation::NotInstalled { installation_task, attempts, .. } = &mut project.default_prettier.prettier { *installation_task = None; *attempts += 1; } })?; anyhow::bail!( "Cannot start default prettier due to its installation failure: {e:#}" ); } let new_default_prettier = project.update(&mut cx, |project, cx| { let new_default_prettier = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); project.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: 0, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; return Ok(new_default_prettier); } ControlFlow::Break(instance) => match instance.prettier { Some(instance) => return Ok(instance), None => { let new_default_prettier = project.update(&mut cx, |project, cx| { let new_default_prettier = start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx); project.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: instance.attempt + 1, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; return Ok(new_default_prettier); } }, } }) } fn start_prettier( node: Arc<dyn NodeRuntime>, prettier_dir: PathBuf, worktree_id: Option<WorktreeId>, cx: &mut ModelContext<'_, Project>, ) -> PrettierTask { cx.spawn(|project, mut cx| async move { log::info!("Starting prettier at path {prettier_dir:?}"); let new_server_id = project.update(&mut cx, |project, _| { project.languages.next_language_server_id() })?; let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone()) .await .context("default prettier spawn") .map(Arc::new) .map_err(Arc::new)?; register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx); Ok(new_prettier) }) .shared() } fn register_new_prettier( project: &WeakModel<Project>, prettier: &Prettier, worktree_id: Option<WorktreeId>, new_server_id: LanguageServerId, cx: &mut AsyncAppContext, ) { let prettier_dir = prettier.prettier_dir(); let is_default = prettier.is_default(); if is_default { log::info!("Started default prettier in {prettier_dir:?}"); } else { log::info!("Started prettier in {prettier_dir:?}"); } if let Some(prettier_server) = prettier.server() { project .update(cx, |project, cx| { let name = if is_default { LanguageServerName(Arc::from("prettier (default)")) } else { let worktree_path = worktree_id .and_then(|id| project.worktree_for_id(id, cx)) .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path())); let name = match worktree_path { Some(worktree_path) => { if prettier_dir == worktree_path.as_ref() { let name = prettier_dir .file_name() .and_then(|name| name.to_str()) .unwrap_or_default(); format!("prettier ({name})") } else { let dir_to_display = prettier_dir .strip_prefix(worktree_path.as_ref()) .ok() .unwrap_or(prettier_dir); format!("prettier ({})", dir_to_display.display()) } } None => format!("prettier ({})", prettier_dir.display()), }; LanguageServerName(Arc::from(name)) }; project .supplementary_language_servers .insert(new_server_id, (name, Arc::clone(prettier_server))); cx.emit(Event::LanguageServerAdded(new_server_id)); }) .ok(); } } async fn install_prettier_packages( fs: &dyn Fs, plugins_to_install: HashSet<Arc<str>>, node: Arc<dyn NodeRuntime>, ) -> anyhow::Result<()> { let packages_to_versions = future::try_join_all( plugins_to_install .iter() .chain(Some(&"prettier".into())) .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 default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path(); match fs.metadata(default_prettier_dir).await.with_context(|| { format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}") })? { Some(prettier_dir_metadata) => anyhow::ensure!( prettier_dir_metadata.is_dir, "default prettier dir {default_prettier_dir:?} is not a directory" ), None => fs .create_dir(default_prettier_dir) .await .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?, } log::info!("Installing default prettier and plugins: {packages_to_versions:?}"); let borrowed_packages = packages_to_versions .iter() .map(|(package, version)| (package.as_str(), version.as_str())) .collect::<Vec<_>>(); node.npm_install_packages(default_prettier_dir, &borrowed_packages) .await .context("fetching formatter packages")?; anyhow::Ok(()) } async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> { let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE); fs.save( &prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix, ) .await .with_context(|| { format!( "writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE ) })?; Ok(()) } async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool { let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE); if !fs.is_file(&prettier_wrapper_path).await { return true; } let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else { return true; }; prettier_server_file_contents != prettier::PRETTIER_SERVER_JS } impl Project { pub fn update_prettier_settings( &self, worktree: &Model<Worktree>, changes: &[(Arc<Path>, ProjectEntryId, PathChange)], cx: &mut ModelContext<'_, Project>, ) { let prettier_config_files = Prettier::CONFIG_FILE_NAMES .iter() .map(Path::new) .collect::<HashSet<_>>(); let prettier_config_file_changed = changes .iter() .filter(|(_, _, change)| !matches!(change, PathChange::Loaded)) .filter(|(path, _, _)| { !path .components() .any(|component| component.as_os_str().to_string_lossy() == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let current_worktree_id = worktree.read(cx).id(); if let Some((config_path, _, _)) = prettier_config_file_changed { log::info!( "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}" ); let prettiers_to_reload = self.prettiers_per_worktree .get(¤t_worktree_id) .iter() .flat_map(|prettier_paths| prettier_paths.iter()) .flatten() .filter_map(|prettier_path| { Some(( current_worktree_id, Some(prettier_path.clone()), self.prettier_instances.get(prettier_path)?.clone(), )) }) .chain(self.default_prettier.instance().map(|default_prettier| { (current_worktree_id, None, default_prettier.clone()) })) .collect::<Vec<_>>(); cx.background_executor() .spawn(async move { let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| { async move { if let Some(instance) = prettier_instance.prettier { match instance.await { Ok(prettier) => { prettier.clear_cache().log_err().await; }, Err(e) => { match prettier_path { Some(prettier_path) => log::error!( "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}" ), None => log::error!( "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}" ), } }, } } } }))