use std::{ ops::ControlFlow, path::{Path, PathBuf}, sync::Arc, time::Duration,}; use anyhow::{Context as _, Result, anyhow};use collections::{HashMap, HashSet};use fs::Fs;use futures::{ FutureExt, future::{self, Shared}, stream::FuturesUnordered,};use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};use language::{ Buffer, LanguageRegistry, LocalFile, language_settings::{Formatter, LanguageSettings},};use lsp::{LanguageServer, LanguageServerId, LanguageServerName};use node_runtime::NodeRuntime;use paths::default_prettier_dir;use prettier::Prettier;use settings::Settings;use smol::stream::StreamExt;use util::{ResultExt, TryFutureExt, rel_path::RelPath}; use crate::{ File, PathChange, ProjectEntryId, Worktree, lsp_store::WorktreeId, project_settings::ProjectSettings, worktree_store::WorktreeStore,}; pub struct PrettierStore { node: NodeRuntime, fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>, worktree_store: Entity<WorktreeStore>, default_prettier: DefaultPrettier, prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>, prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>, prettier_instances: HashMap<PathBuf, PrettierInstance>,} pub(crate) enum PrettierStoreEvent { LanguageServerRemoved(LanguageServerId), LanguageServerAdded { new_server_id: LanguageServerId, name: LanguageServerName, prettier_server: Arc<LanguageServer>, },} impl EventEmitter<PrettierStoreEvent> for PrettierStore {} impl PrettierStore { pub fn new( node: NodeRuntime, fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>, worktree_store: Entity<WorktreeStore>, _: &mut Context<Self>, ) -> Self { Self { node, fs, languages, worktree_store, default_prettier: DefaultPrettier::default(), prettiers_per_worktree: HashMap::default(), prettier_ignores_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), } } pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) { self.prettier_ignores_per_worktree.remove(&id_to_remove); let mut prettier_instances_to_clean = FuturesUnordered::new(); if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) { for path in prettier_paths.iter().flatten() { if let Some(prettier_instance) = self.prettier_instances.remove(path) { prettier_instances_to_clean.push(async move { prettier_instance .server() .await .map(|server| server.server_id()) }); } } } cx.spawn(async move |prettier_store, cx| { while let Some(prettier_server_id) = prettier_instances_to_clean.next().await { if let Some(prettier_server_id) = prettier_server_id { prettier_store .update(cx, |_, cx| { cx.emit(PrettierStoreEvent::LanguageServerRemoved( prettier_server_id, )); }) .ok(); } } }) .detach(); } fn prettier_instance_for_buffer( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); if buffer.language().is_none() { return Task::ready(None); } let node = self.node.clone(); match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) { Some((worktree_id, buffer_path)) => { let fs = Arc::clone(&self.fs); let installed_prettiers = self.prettier_instances.keys().cloned().collect(); cx.spawn(async move |lsp_store, cx| { match cx .background_spawn(async move { Prettier::locate_prettier_installation( fs.as_ref(), &installed_prettiers, &buffer_path, ) .await }) .await { Ok(ControlFlow::Break(())) => None, Ok(ControlFlow::Continue(None)) => { let default_task = lsp_store .update(cx, |lsp_store, cx| { lsp_store .prettiers_per_worktree .entry(worktree_id) .or_default() .insert(None); lsp_store.default_prettier.prettier_task( &node, Some(worktree_id), cx, ) }) .ok()??; let default_instance = default_task.await.ok()?; Some((None, default_instance)) } Ok(ControlFlow::Continue(Some(prettier_dir))) => { lsp_store .update(cx, |lsp_store, _| { lsp_store .prettiers_per_worktree .entry(worktree_id) .or_default() .insert(Some(prettier_dir.clone())) }) .ok()?; if let Some(prettier_task) = lsp_store .update(cx, |lsp_store, cx| { lsp_store .prettier_instances .get_mut(&prettier_dir) .and_then(|existing_instance| { existing_instance.prettier_task( &node, Some(&prettier_dir), Some(worktree_id), cx, ) }) }) .ok()? { log::debug!("Found already started prettier in {prettier_dir:?}"); return Some((Some(prettier_dir), prettier_task.await.log_err()?)); } log::info!("Found prettier in {prettier_dir:?}, starting."); let new_prettier_task = lsp_store .update(cx, |lsp_store, cx| { let new_prettier_task = Self::start_prettier( node, prettier_dir.clone(), Some(worktree_id), cx, ); lsp_store.prettier_instances.insert( prettier_dir.clone(), PrettierInstance { attempt: 0, prettier: Some(new_prettier_task.clone()), }, ); new_prettier_task }) .ok()?; Some((Some(prettier_dir), new_prettier_task)) } Err(e) => { log::error!("Failed to determine prettier path for buffer: {e:#}"); None } } }) } None => { let new_task = self.default_prettier.prettier_task(&node, None, cx); cx.spawn(async move |_, _| Some((None, new_task?.log_err().await?))) } } } fn prettier_ignore_for_buffer( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) -> Task<Option<PathBuf>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); if buffer.language().is_none() { return Task::ready(None); } match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) { Some((worktree_id, buffer_path)) => { let fs = Arc::clone(&self.fs); let prettier_ignores = self .prettier_ignores_per_worktree .get(&worktree_id) .cloned() .unwrap_or_default(); cx.spawn(async move |lsp_store, cx| { match cx .background_spawn(async move { Prettier::locate_prettier_ignore( fs.as_ref(), &prettier_ignores, &buffer_path, ) .await }) .await { Ok(ControlFlow::Break(())) => None, Ok(ControlFlow::Continue(None)) => None, Ok(ControlFlow::Continue(Some(ignore_dir))) => { log::debug!("Found prettier ignore in {ignore_dir:?}"); lsp_store .update(cx, |store, _| { store .prettier_ignores_per_worktree .entry(worktree_id) .or_default() .insert(ignore_dir.clone()); }) .ok(); Some(ignore_dir) } Err(e) => { log::error!( "Failed to determine prettier ignore path for buffer: {e:#}" ); None } } }) } None => Task::ready(None), } } fn start_prettier( node: NodeRuntime, prettier_dir: PathBuf, worktree_id: Option<WorktreeId>, cx: &mut Context<Self>, ) -> PrettierTask { let request_timeout = ProjectSettings::get_global(cx) .global_lsp_settings .get_request_timeout(); cx.spawn(async move |prettier_store, cx| { log::info!("Starting prettier at path {prettier_dir:?}"); let new_server_id = prettier_store.read_with(cx, |prettier_store, _| { prettier_store.languages.next_language_server_id() })?; let new_prettier = Prettier::start( new_server_id, prettier_dir, node, request_timeout, cx.clone(), ) .await .context("default prettier spawn") .map(Arc::new) .map_err(Arc::new)?; Self::register_new_prettier( &prettier_store, &new_prettier, worktree_id, new_server_id, cx, ); Ok(new_prettier) }) .shared() } fn start_default_prettier( node: NodeRuntime, worktree_id: Option<WorktreeId>, cx: &mut Context<PrettierStore>, ) -> Task<anyhow::Result<PrettierTask>> { cx.spawn(async move |prettier_store, cx| { let installation_task = prettier_store.read_with(cx, |prettier_store, _| { match &prettier_store.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 { prettier_store.update(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 = prettier_store.update(cx, |prettier_store, cx| { let new_default_prettier = Self::start_prettier( node, default_prettier_dir().clone(), worktree_id, cx, ); prettier_store.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: 0, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; Ok(new_default_prettier) } ControlFlow::Break(instance) => match instance.prettier { Some(instance) => Ok(instance), None => { let new_default_prettier = prettier_store.update(cx, |prettier_store, cx| { let new_default_prettier = Self::start_prettier( node, default_prettier_dir().clone(), worktree_id, cx, ); prettier_store.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: instance.attempt + 1, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; Ok(new_default_prettier) } }, } }) } fn register_new_prettier( prettier_store: &WeakEntity<Self>, prettier: &Prettier, worktree_id: Option<WorktreeId>, new_server_id: LanguageServerId, cx: &mut AsyncApp, ) { 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() { prettier_store .update(cx, |prettier_store, cx| { let name = if is_default { LanguageServerName("prettier (default)".to_string().into()) } else { let worktree_path = worktree_id .and_then(|id| { prettier_store .worktree_store .read(cx) .worktree_for_id(id, cx) }) .map(|worktree| worktree.read(cx).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(name.into()) }; cx.emit(PrettierStoreEvent::LanguageServerAdded { new_server_id, name, prettier_server: prettier_server.clone(), }); }) .ok(); } } pub fn update_prettier_settings( &self, worktree: &Entity<Worktree>, changes: &[(Arc<RelPath>, ProjectEntryId, PathChange)], cx: &mut Context<Self>, ) { let prettier_config_files = Prettier::CONFIG_FILE_NAMES .iter() .map(|name| RelPath::unix(name).unwrap()) .collect::<HashSet<_>>(); let prettier_config_file_changed = changes .iter() .filter(|(path, _, change)| { !matches!(change, PathChange::Loaded) && !path .components() .any(|component| component == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let Some((config_path, _, _)) = prettier_config_file_changed else { return; }; let current_worktree_id = worktree.read(cx).id(); 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_prettierLines 1–500 of 994. Load more
use std::{ ops::ControlFlow, path::{Path, PathBuf}, sync::Arc, time::Duration, }; use anyhow::{Context as _, Result, anyhow}; use collections::{HashMap, HashSet}; use fs::Fs; use futures::{ FutureExt, future::{self, Shared}, stream::FuturesUnordered, }; use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity}; use language::{ Buffer, LanguageRegistry, LocalFile, language_settings::{Formatter, LanguageSettings}, }; use lsp::{LanguageServer, LanguageServerId, LanguageServerName}; use node_runtime::NodeRuntime; use paths::default_prettier_dir; use prettier::Prettier; use settings::Settings; use smol::stream::StreamExt; use util::{ResultExt, TryFutureExt, rel_path::RelPath}; use crate::{ File, PathChange, ProjectEntryId, Worktree, lsp_store::WorktreeId, project_settings::ProjectSettings, worktree_store::WorktreeStore, }; pub struct PrettierStore { node: NodeRuntime, fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>, worktree_store: Entity<WorktreeStore>, default_prettier: DefaultPrettier, prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>, prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>, prettier_instances: HashMap<PathBuf, PrettierInstance>, } pub(crate) enum PrettierStoreEvent { LanguageServerRemoved(LanguageServerId), LanguageServerAdded { new_server_id: LanguageServerId, name: LanguageServerName, prettier_server: Arc<LanguageServer>, }, } impl EventEmitter<PrettierStoreEvent> for PrettierStore {} impl PrettierStore { pub fn new( node: NodeRuntime, fs: Arc<dyn Fs>, languages: Arc<LanguageRegistry>, worktree_store: Entity<WorktreeStore>, _: &mut Context<Self>, ) -> Self { Self { node, fs, languages, worktree_store, default_prettier: DefaultPrettier::default(), prettiers_per_worktree: HashMap::default(), prettier_ignores_per_worktree: HashMap::default(), prettier_instances: HashMap::default(), } } pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) { self.prettier_ignores_per_worktree.remove(&id_to_remove); let mut prettier_instances_to_clean = FuturesUnordered::new(); if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) { for path in prettier_paths.iter().flatten() { if let Some(prettier_instance) = self.prettier_instances.remove(path) { prettier_instances_to_clean.push(async move { prettier_instance .server() .await .map(|server| server.server_id()) }); } } } cx.spawn(async move |prettier_store, cx| { while let Some(prettier_server_id) = prettier_instances_to_clean.next().await { if let Some(prettier_server_id) = prettier_server_id { prettier_store .update(cx, |_, cx| { cx.emit(PrettierStoreEvent::LanguageServerRemoved( prettier_server_id, )); }) .ok(); } } }) .detach(); } fn prettier_instance_for_buffer( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); if buffer.language().is_none() { return Task::ready(None); } let node = self.node.clone(); match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) { Some((worktree_id, buffer_path)) => { let fs = Arc::clone(&self.fs); let installed_prettiers = self.prettier_instances.keys().cloned().collect(); cx.spawn(async move |lsp_store, cx| { match cx .background_spawn(async move { Prettier::locate_prettier_installation( fs.as_ref(), &installed_prettiers, &buffer_path, ) .await }) .await { Ok(ControlFlow::Break(())) => None, Ok(ControlFlow::Continue(None)) => { let default_task = lsp_store .update(cx, |lsp_store, cx| { lsp_store .prettiers_per_worktree .entry(worktree_id) .or_default() .insert(None); lsp_store.default_prettier.prettier_task( &node, Some(worktree_id), cx, ) }) .ok()??; let default_instance = default_task.await.ok()?; Some((None, default_instance)) } Ok(ControlFlow::Continue(Some(prettier_dir))) => { lsp_store .update(cx, |lsp_store, _| { lsp_store .prettiers_per_worktree .entry(worktree_id) .or_default() .insert(Some(prettier_dir.clone())) }) .ok()?; if let Some(prettier_task) = lsp_store .update(cx, |lsp_store, cx| { lsp_store .prettier_instances .get_mut(&prettier_dir) .and_then(|existing_instance| { existing_instance.prettier_task( &node, Some(&prettier_dir), Some(worktree_id), cx, ) }) }) .ok()? { log::debug!("Found already started prettier in {prettier_dir:?}"); return Some((Some(prettier_dir), prettier_task.await.log_err()?)); } log::info!("Found prettier in {prettier_dir:?}, starting."); let new_prettier_task = lsp_store .update(cx, |lsp_store, cx| { let new_prettier_task = Self::start_prettier( node, prettier_dir.clone(), Some(worktree_id), cx, ); lsp_store.prettier_instances.insert( prettier_dir.clone(), PrettierInstance { attempt: 0, prettier: Some(new_prettier_task.clone()), }, ); new_prettier_task }) .ok()?; Some((Some(prettier_dir), new_prettier_task)) } Err(e) => { log::error!("Failed to determine prettier path for buffer: {e:#}"); None } } }) } None => { let new_task = self.default_prettier.prettier_task(&node, None, cx); cx.spawn(async move |_, _| Some((None, new_task?.log_err().await?))) } } } fn prettier_ignore_for_buffer( &mut self, buffer: &Entity<Buffer>, cx: &mut Context<Self>, ) -> Task<Option<PathBuf>> { let buffer = buffer.read(cx); let buffer_file = buffer.file(); if buffer.language().is_none() { return Task::ready(None); } match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) { Some((worktree_id, buffer_path)) => { let fs = Arc::clone(&self.fs); let prettier_ignores = self .prettier_ignores_per_worktree .get(&worktree_id) .cloned() .unwrap_or_default(); cx.spawn(async move |lsp_store, cx| { match cx .background_spawn(async move { Prettier::locate_prettier_ignore( fs.as_ref(), &prettier_ignores, &buffer_path, ) .await }) .await { Ok(ControlFlow::Break(())) => None, Ok(ControlFlow::Continue(None)) => None, Ok(ControlFlow::Continue(Some(ignore_dir))) => { log::debug!("Found prettier ignore in {ignore_dir:?}"); lsp_store .update(cx, |store, _| { store .prettier_ignores_per_worktree .entry(worktree_id) .or_default() .insert(ignore_dir.clone()); }) .ok(); Some(ignore_dir) } Err(e) => { log::error!( "Failed to determine prettier ignore path for buffer: {e:#}" ); None } } }) } None => Task::ready(None), } } fn start_prettier( node: NodeRuntime, prettier_dir: PathBuf, worktree_id: Option<WorktreeId>, cx: &mut Context<Self>, ) -> PrettierTask { let request_timeout = ProjectSettings::get_global(cx) .global_lsp_settings .get_request_timeout(); cx.spawn(async move |prettier_store, cx| { log::info!("Starting prettier at path {prettier_dir:?}"); let new_server_id = prettier_store.read_with(cx, |prettier_store, _| { prettier_store.languages.next_language_server_id() })?; let new_prettier = Prettier::start( new_server_id, prettier_dir, node, request_timeout, cx.clone(), ) .await .context("default prettier spawn") .map(Arc::new) .map_err(Arc::new)?; Self::register_new_prettier( &prettier_store, &new_prettier, worktree_id, new_server_id, cx, ); Ok(new_prettier) }) .shared() } fn start_default_prettier( node: NodeRuntime, worktree_id: Option<WorktreeId>, cx: &mut Context<PrettierStore>, ) -> Task<anyhow::Result<PrettierTask>> { cx.spawn(async move |prettier_store, cx| { let installation_task = prettier_store.read_with(cx, |prettier_store, _| { match &prettier_store.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 { prettier_store.update(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 = prettier_store.update(cx, |prettier_store, cx| { let new_default_prettier = Self::start_prettier( node, default_prettier_dir().clone(), worktree_id, cx, ); prettier_store.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: 0, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; Ok(new_default_prettier) } ControlFlow::Break(instance) => match instance.prettier { Some(instance) => Ok(instance), None => { let new_default_prettier = prettier_store.update(cx, |prettier_store, cx| { let new_default_prettier = Self::start_prettier( node, default_prettier_dir().clone(), worktree_id, cx, ); prettier_store.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance { attempt: instance.attempt + 1, prettier: Some(new_default_prettier.clone()), }); new_default_prettier })?; Ok(new_default_prettier) } }, } }) } fn register_new_prettier( prettier_store: &WeakEntity<Self>, prettier: &Prettier, worktree_id: Option<WorktreeId>, new_server_id: LanguageServerId, cx: &mut AsyncApp, ) { 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() { prettier_store .update(cx, |prettier_store, cx| { let name = if is_default { LanguageServerName("prettier (default)".to_string().into()) } else { let worktree_path = worktree_id .and_then(|id| { prettier_store .worktree_store .read(cx) .worktree_for_id(id, cx) }) .map(|worktree| worktree.read(cx).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(name.into()) }; cx.emit(PrettierStoreEvent::LanguageServerAdded { new_server_id, name, prettier_server: prettier_server.clone(), }); }) .ok(); } } pub fn update_prettier_settings( &self, worktree: &Entity<Worktree>, changes: &[(Arc<RelPath>, ProjectEntryId, PathChange)], cx: &mut Context<Self>, ) { let prettier_config_files = Prettier::CONFIG_FILE_NAMES .iter() .map(|name| RelPath::unix(name).unwrap()) .collect::<HashSet<_>>(); let prettier_config_file_changed = changes .iter() .filter(|(path, _, change)| { !matches!(change, PathChange::Loaded) && !path .components() .any(|component| component == "node_modules") }) .find(|(path, _, _)| prettier_config_files.contains(path.as_ref())); let Some((config_path, _, _)) = prettier_config_file_changed else { return; }; let current_worktree_id = worktree.read(cx).id(); 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