prettier_support.rs

  1use std::{
  2    ops::ControlFlow,
  3    path::{Path, PathBuf},
  4    sync::Arc,
  5};
  6
  7use anyhow::Context;
  8use collections::HashSet;
  9use fs::Fs;
 10use futures::{
 11    future::{self, Shared},
 12    FutureExt,
 13};
 14use gpui::{AsyncAppContext, Model, ModelContext, Task, WeakModel};
 15use language::{
 16    language_settings::{Formatter, LanguageSettings},
 17    Buffer, Language, LanguageServerName, LocalFile,
 18};
 19use lsp::{LanguageServer, LanguageServerId};
 20use node_runtime::NodeRuntime;
 21use prettier::Prettier;
 22use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt};
 23
 24use crate::{
 25    Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
 26};
 27
 28pub fn prettier_plugins_for_language(
 29    language: &Language,
 30    language_settings: &LanguageSettings,
 31) -> Option<HashSet<&'static str>> {
 32    match &language_settings.formatter {
 33        Formatter::Prettier { .. } | Formatter::Auto => {}
 34        Formatter::LanguageServer | Formatter::External { .. } => return None,
 35    };
 36    let mut prettier_plugins = None;
 37    if language.prettier_parser_name().is_some() {
 38        prettier_plugins
 39            .get_or_insert_with(|| HashSet::default())
 40            .extend(
 41                language
 42                    .lsp_adapters()
 43                    .iter()
 44                    .flat_map(|adapter| adapter.prettier_plugins()),
 45            )
 46    }
 47
 48    prettier_plugins
 49}
 50
 51pub(super) async fn format_with_prettier(
 52    project: &WeakModel<Project>,
 53    buffer: &Model<Buffer>,
 54    cx: &mut AsyncAppContext,
 55) -> Option<FormatOperation> {
 56    if let Some((prettier_path, prettier_task)) = project
 57        .update(cx, |project, cx| {
 58            project.prettier_instance_for_buffer(buffer, cx)
 59        })
 60        .ok()?
 61        .await
 62    {
 63        match prettier_task.await {
 64            Ok(prettier) => {
 65                let buffer_path = buffer
 66                    .update(cx, |buffer, cx| {
 67                        File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
 68                    })
 69                    .ok()?;
 70                match prettier.format(buffer, buffer_path, cx).await {
 71                    Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
 72                    Err(e) => {
 73                        match prettier_path {
 74                            Some(prettier_path) => log::error!(
 75                                "Prettier instance from path {prettier_path:?} failed to format a buffer: {e:#}"
 76                            ),
 77                            None => log::error!(
 78                                "Default prettier instance failed to format a buffer: {e:#}"
 79                            ),
 80                        }
 81                    }
 82                }
 83            }
 84            Err(e) => project
 85                .update(cx, |project, _| {
 86                    let instance_to_update = match prettier_path {
 87                        Some(prettier_path) => {
 88                            log::error!(
 89                            "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
 90                        );
 91                            project.prettier_instances.get_mut(&prettier_path)
 92                        }
 93                        None => {
 94                            log::error!("Default prettier instance failed to spawn: {e:#}");
 95                            match &mut project.default_prettier.prettier {
 96                                PrettierInstallation::NotInstalled { .. } => None,
 97                                PrettierInstallation::Installed(instance) => Some(instance),
 98                            }
 99                        }
100                    };
101
102                    if let Some(instance) = instance_to_update {
103                        instance.attempt += 1;
104                        instance.prettier = None;
105                    }
106                })
107                .ok()?,
108        }
109    }
110
111    None
112}
113
114pub struct DefaultPrettier {
115    prettier: PrettierInstallation,
116    installed_plugins: HashSet<&'static str>,
117}
118
119pub enum PrettierInstallation {
120    NotInstalled {
121        attempts: usize,
122        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
123        not_installed_plugins: HashSet<&'static str>,
124    },
125    Installed(PrettierInstance),
126}
127
128pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
129
130#[derive(Clone)]
131pub struct PrettierInstance {
132    attempt: usize,
133    prettier: Option<PrettierTask>,
134}
135
136impl Default for DefaultPrettier {
137    fn default() -> Self {
138        Self {
139            prettier: PrettierInstallation::NotInstalled {
140                attempts: 0,
141                installation_task: None,
142                not_installed_plugins: HashSet::default(),
143            },
144            installed_plugins: HashSet::default(),
145        }
146    }
147}
148
149impl DefaultPrettier {
150    pub fn instance(&self) -> Option<&PrettierInstance> {
151        if let PrettierInstallation::Installed(instance) = &self.prettier {
152            Some(instance)
153        } else {
154            None
155        }
156    }
157
158    pub fn prettier_task(
159        &mut self,
160        node: &Arc<dyn NodeRuntime>,
161        worktree_id: Option<WorktreeId>,
162        cx: &mut ModelContext<'_, Project>,
163    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
164        match &mut self.prettier {
165            PrettierInstallation::NotInstalled { .. } => {
166                Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
167            }
168            PrettierInstallation::Installed(existing_instance) => {
169                existing_instance.prettier_task(node, None, worktree_id, cx)
170            }
171        }
172    }
173}
174
175impl PrettierInstance {
176    pub fn prettier_task(
177        &mut self,
178        node: &Arc<dyn NodeRuntime>,
179        prettier_dir: Option<&Path>,
180        worktree_id: Option<WorktreeId>,
181        cx: &mut ModelContext<'_, Project>,
182    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
183        if self.attempt > prettier::FAIL_THRESHOLD {
184            match prettier_dir {
185                Some(prettier_dir) => log::warn!(
186                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
187                ),
188                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
189            }
190            return None;
191        }
192        Some(match &self.prettier {
193            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
194            None => match prettier_dir {
195                Some(prettier_dir) => {
196                    let new_task = start_prettier(
197                        Arc::clone(node),
198                        prettier_dir.to_path_buf(),
199                        worktree_id,
200                        cx,
201                    );
202                    self.attempt += 1;
203                    self.prettier = Some(new_task.clone());
204                    Task::ready(Ok(new_task))
205                }
206                None => {
207                    self.attempt += 1;
208                    let node = Arc::clone(node);
209                    cx.spawn(|project, mut cx| async move {
210                        project
211                            .update(&mut cx, |_, cx| {
212                                start_default_prettier(node, worktree_id, cx)
213                            })?
214                            .await
215                    })
216                }
217            },
218        })
219    }
220
221    pub async fn server(&self) -> Option<Arc<LanguageServer>> {
222        self.prettier.clone()?.await.ok()?.server().cloned()
223    }
224}
225
226fn start_default_prettier(
227    node: Arc<dyn NodeRuntime>,
228    worktree_id: Option<WorktreeId>,
229    cx: &mut ModelContext<'_, Project>,
230) -> Task<anyhow::Result<PrettierTask>> {
231    cx.spawn(|project, mut cx| async move {
232        loop {
233            let installation_task = project.update(&mut cx, |project, _| {
234                match &project.default_prettier.prettier {
235                    PrettierInstallation::NotInstalled {
236                        installation_task, ..
237                    } => ControlFlow::Continue(installation_task.clone()),
238                    PrettierInstallation::Installed(default_prettier) => {
239                        ControlFlow::Break(default_prettier.clone())
240                    }
241                }
242            })?;
243            match installation_task {
244                ControlFlow::Continue(None) => {
245                    anyhow::bail!("Default prettier is not installed and cannot be started")
246                }
247                ControlFlow::Continue(Some(installation_task)) => {
248                    log::info!("Waiting for default prettier to install");
249                    if let Err(e) = installation_task.await {
250                        project.update(&mut cx, |project, _| {
251                            if let PrettierInstallation::NotInstalled {
252                                installation_task,
253                                attempts,
254                                ..
255                            } = &mut project.default_prettier.prettier
256                            {
257                                *installation_task = None;
258                                *attempts += 1;
259                            }
260                        })?;
261                        anyhow::bail!(
262                            "Cannot start default prettier due to its installation failure: {e:#}"
263                        );
264                    }
265                    let new_default_prettier = project.update(&mut cx, |project, cx| {
266                        let new_default_prettier =
267                            start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
268                        project.default_prettier.prettier =
269                            PrettierInstallation::Installed(PrettierInstance {
270                                attempt: 0,
271                                prettier: Some(new_default_prettier.clone()),
272                            });
273                        new_default_prettier
274                    })?;
275                    return Ok(new_default_prettier);
276                }
277                ControlFlow::Break(instance) => match instance.prettier {
278                    Some(instance) => return Ok(instance),
279                    None => {
280                        let new_default_prettier = project.update(&mut cx, |project, cx| {
281                            let new_default_prettier =
282                                start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
283                            project.default_prettier.prettier =
284                                PrettierInstallation::Installed(PrettierInstance {
285                                    attempt: instance.attempt + 1,
286                                    prettier: Some(new_default_prettier.clone()),
287                                });
288                            new_default_prettier
289                        })?;
290                        return Ok(new_default_prettier);
291                    }
292                },
293            }
294        }
295    })
296}
297
298fn start_prettier(
299    node: Arc<dyn NodeRuntime>,
300    prettier_dir: PathBuf,
301    worktree_id: Option<WorktreeId>,
302    cx: &mut ModelContext<'_, Project>,
303) -> PrettierTask {
304    cx.spawn(|project, mut cx| async move {
305        log::info!("Starting prettier at path {prettier_dir:?}");
306        let new_server_id = project.update(&mut cx, |project, _| {
307            project.languages.next_language_server_id()
308        })?;
309
310        let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
311            .await
312            .context("default prettier spawn")
313            .map(Arc::new)
314            .map_err(Arc::new)?;
315        register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
316        Ok(new_prettier)
317    })
318    .shared()
319}
320
321fn register_new_prettier(
322    project: &WeakModel<Project>,
323    prettier: &Prettier,
324    worktree_id: Option<WorktreeId>,
325    new_server_id: LanguageServerId,
326    cx: &mut AsyncAppContext,
327) {
328    let prettier_dir = prettier.prettier_dir();
329    let is_default = prettier.is_default();
330    if is_default {
331        log::info!("Started default prettier in {prettier_dir:?}");
332    } else {
333        log::info!("Started prettier in {prettier_dir:?}");
334    }
335    if let Some(prettier_server) = prettier.server() {
336        project
337            .update(cx, |project, cx| {
338                let name = if is_default {
339                    LanguageServerName(Arc::from("prettier (default)"))
340                } else {
341                    let worktree_path = worktree_id
342                        .and_then(|id| project.worktree_for_id(id, cx))
343                        .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
344                    let name = match worktree_path {
345                        Some(worktree_path) => {
346                            if prettier_dir == worktree_path.as_ref() {
347                                let name = prettier_dir
348                                    .file_name()
349                                    .and_then(|name| name.to_str())
350                                    .unwrap_or_default();
351                                format!("prettier ({name})")
352                            } else {
353                                let dir_to_display = prettier_dir
354                                    .strip_prefix(worktree_path.as_ref())
355                                    .ok()
356                                    .unwrap_or(prettier_dir);
357                                format!("prettier ({})", dir_to_display.display())
358                            }
359                        }
360                        None => format!("prettier ({})", prettier_dir.display()),
361                    };
362                    LanguageServerName(Arc::from(name))
363                };
364                project
365                    .supplementary_language_servers
366                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
367                cx.emit(Event::LanguageServerAdded(new_server_id));
368            })
369            .ok();
370    }
371}
372
373async fn install_prettier_packages(
374    fs: &dyn Fs,
375    plugins_to_install: HashSet<&'static str>,
376    node: Arc<dyn NodeRuntime>,
377) -> anyhow::Result<()> {
378    let packages_to_versions =
379        future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
380            |package_name| async {
381                let returned_package_name = package_name.to_string();
382                let latest_version = node
383                    .npm_package_latest_version(package_name)
384                    .await
385                    .with_context(|| {
386                        format!("fetching latest npm version for package {returned_package_name}")
387                    })?;
388                anyhow::Ok((returned_package_name, latest_version))
389            },
390        ))
391        .await
392        .context("fetching latest npm versions")?;
393
394    let default_prettier_dir = DEFAULT_PRETTIER_DIR.as_path();
395    match fs.metadata(default_prettier_dir).await.with_context(|| {
396        format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
397    })? {
398        Some(prettier_dir_metadata) => anyhow::ensure!(
399            prettier_dir_metadata.is_dir,
400            "default prettier dir {default_prettier_dir:?} is not a directory"
401        ),
402        None => fs
403            .create_dir(default_prettier_dir)
404            .await
405            .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
406    }
407
408    log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
409    let borrowed_packages = packages_to_versions
410        .iter()
411        .map(|(package, version)| (package.as_str(), version.as_str()))
412        .collect::<Vec<_>>();
413    node.npm_install_packages(default_prettier_dir, &borrowed_packages)
414        .await
415        .context("fetching formatter packages")?;
416    anyhow::Ok(())
417}
418
419async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
420    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
421    fs.save(
422        &prettier_wrapper_path,
423        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
424        text::LineEnding::Unix,
425    )
426    .await
427    .with_context(|| {
428        format!(
429            "writing {} file at {prettier_wrapper_path:?}",
430            prettier::PRETTIER_SERVER_FILE
431        )
432    })?;
433    Ok(())
434}
435
436async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
437    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
438    if !fs.is_file(&prettier_wrapper_path).await {
439        return true;
440    }
441    let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
442        return true;
443    };
444    prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
445}
446
447impl Project {
448    pub fn update_prettier_settings(
449        &self,
450        worktree: &Model<Worktree>,
451        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
452        cx: &mut ModelContext<'_, Project>,
453    ) {
454        let prettier_config_files = Prettier::CONFIG_FILE_NAMES
455            .iter()
456            .map(Path::new)
457            .collect::<HashSet<_>>();
458
459        let prettier_config_file_changed = changes
460            .iter()
461            .filter(|(_, _, change)| !matches!(change, PathChange::Loaded))
462            .filter(|(path, _, _)| {
463                !path
464                    .components()
465                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")
466            })
467            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
468        let current_worktree_id = worktree.read(cx).id();
469        if let Some((config_path, _, _)) = prettier_config_file_changed {
470            log::info!(
471                "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
472            );
473            let prettiers_to_reload =
474                self.prettiers_per_worktree
475                    .get(&current_worktree_id)
476                    .iter()
477                    .flat_map(|prettier_paths| prettier_paths.iter())
478                    .flatten()
479                    .filter_map(|prettier_path| {
480                        Some((
481                            current_worktree_id,
482                            Some(prettier_path.clone()),
483                            self.prettier_instances.get(prettier_path)?.clone(),
484                        ))
485                    })
486                    .chain(self.default_prettier.instance().map(|default_prettier| {
487                        (current_worktree_id, None, default_prettier.clone())
488                    }))
489                    .collect::<Vec<_>>();
490
491            cx.background_executor()
492                .spawn(async move {
493                    let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
494                        async move {
495                            if let Some(instance) = prettier_instance.prettier {
496                                match instance.await {
497                                    Ok(prettier) => {
498                                        prettier.clear_cache().log_err().await;
499                                    },
500                                    Err(e) => {
501                                        match prettier_path {
502                                            Some(prettier_path) => log::error!(
503                                                "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
504                                            ),
505                                            None => log::error!(
506                                                "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
507                                            ),
508                                        }
509                                    },
510                                }
511                            }
512                        }
513                    }))
514                    .await;
515                })
516                .detach();
517        }
518    }
519
520    fn prettier_instance_for_buffer(
521        &mut self,
522        buffer: &Model<Buffer>,
523        cx: &mut ModelContext<Self>,
524    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
525        if !self.is_local() {
526            return Task::ready(None);
527        }
528        let buffer = buffer.read(cx);
529        let buffer_file = buffer.file();
530        let Some(buffer_language) = buffer.language() else {
531            return Task::ready(None);
532        };
533        if buffer_language.prettier_parser_name().is_none() {
534            return Task::ready(None);
535        }
536        let Some(node) = self.node.as_ref().map(Arc::clone) else {
537            return Task::ready(None);
538        };
539        match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
540            Some((worktree_id, buffer_path)) => {
541                let fs = Arc::clone(&self.fs);
542                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
543                cx.spawn(|project, mut cx| async move {
544                    match cx
545                        .background_executor()
546                        .spawn(async move {
547                            Prettier::locate_prettier_installation(
548                                fs.as_ref(),
549                                &installed_prettiers,
550                                &buffer_path,
551                            )
552                            .await
553                        })
554                        .await
555                    {
556                        Ok(ControlFlow::Break(())) => None,
557                        Ok(ControlFlow::Continue(None)) => {
558                            let default_instance = project
559                                .update(&mut cx, |project, cx| {
560                                    project
561                                        .prettiers_per_worktree
562                                        .entry(worktree_id)
563                                        .or_default()
564                                        .insert(None);
565                                    project.default_prettier.prettier_task(
566                                        &node,
567                                        Some(worktree_id),
568                                        cx,
569                                    )
570                                })
571                                .ok()?;
572                            Some((None, default_instance?.log_err().await?))
573                        }
574                        Ok(ControlFlow::Continue(Some(prettier_dir))) => {
575                            project
576                                .update(&mut cx, |project, _| {
577                                    project
578                                        .prettiers_per_worktree
579                                        .entry(worktree_id)
580                                        .or_default()
581                                        .insert(Some(prettier_dir.clone()))
582                                })
583                                .ok()?;
584                            if let Some(prettier_task) = project
585                                .update(&mut cx, |project, cx| {
586                                    project.prettier_instances.get_mut(&prettier_dir).map(
587                                        |existing_instance| {
588                                            existing_instance.prettier_task(
589                                                &node,
590                                                Some(&prettier_dir),
591                                                Some(worktree_id),
592                                                cx,
593                                            )
594                                        },
595                                    )
596                                })
597                                .ok()?
598                            {
599                                log::debug!("Found already started prettier in {prettier_dir:?}");
600                                return Some((Some(prettier_dir), prettier_task?.await.log_err()?));
601                            }
602
603                            log::info!("Found prettier in {prettier_dir:?}, starting.");
604                            let new_prettier_task = project
605                                .update(&mut cx, |project, cx| {
606                                    let new_prettier_task = start_prettier(
607                                        node,
608                                        prettier_dir.clone(),
609                                        Some(worktree_id),
610                                        cx,
611                                    );
612                                    project.prettier_instances.insert(
613                                        prettier_dir.clone(),
614                                        PrettierInstance {
615                                            attempt: 0,
616                                            prettier: Some(new_prettier_task.clone()),
617                                        },
618                                    );
619                                    new_prettier_task
620                                })
621                                .ok()?;
622                            Some((Some(prettier_dir), new_prettier_task))
623                        }
624                        Err(e) => {
625                            log::error!("Failed to determine prettier path for buffer: {e:#}");
626                            None
627                        }
628                    }
629                })
630            }
631            None => {
632                let new_task = self.default_prettier.prettier_task(&node, None, cx);
633                cx.spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) })
634            }
635        }
636    }
637
638    #[cfg(any(test, feature = "test-support"))]
639    pub fn install_default_prettier(
640        &mut self,
641        _worktree: Option<WorktreeId>,
642        plugins: HashSet<&'static str>,
643        _cx: &mut ModelContext<Self>,
644    ) {
645        // suppress unused code warnings
646        let _ = should_write_prettier_server_file;
647        let _ = install_prettier_packages;
648        let _ = save_prettier_server_file;
649
650        self.default_prettier.installed_plugins.extend(plugins);
651        self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
652            attempt: 0,
653            prettier: None,
654        });
655    }
656
657    #[cfg(not(any(test, feature = "test-support")))]
658    pub fn install_default_prettier(
659        &mut self,
660        worktree: Option<WorktreeId>,
661        mut new_plugins: HashSet<&'static str>,
662        cx: &mut ModelContext<Self>,
663    ) {
664        let Some(node) = self.node.as_ref().cloned() else {
665            return;
666        };
667        let fs = Arc::clone(&self.fs);
668        let locate_prettier_installation = match worktree.and_then(|worktree_id| {
669            self.worktree_for_id(worktree_id, cx)
670                .map(|worktree| worktree.read(cx).abs_path())
671        }) {
672            Some(locate_from) => {
673                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
674                cx.background_executor().spawn(async move {
675                    Prettier::locate_prettier_installation(
676                        fs.as_ref(),
677                        &installed_prettiers,
678                        locate_from.as_ref(),
679                    )
680                    .await
681                })
682            }
683            None => Task::ready(Ok(ControlFlow::Continue(None))),
684        };
685        new_plugins.retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
686        let mut installation_attempt = 0;
687        let previous_installation_task = match &mut self.default_prettier.prettier {
688            PrettierInstallation::NotInstalled {
689                installation_task,
690                attempts,
691                not_installed_plugins,
692            } => {
693                installation_attempt = *attempts;
694                if installation_attempt > prettier::FAIL_THRESHOLD {
695                    *installation_task = None;
696                    log::warn!(
697                        "Default prettier installation had failed {installation_attempt} times, not attempting again",
698                    );
699                    return;
700                }
701                new_plugins.extend(not_installed_plugins.iter());
702                installation_task.clone()
703            }
704            PrettierInstallation::Installed { .. } => {
705                if new_plugins.is_empty() {
706                    return;
707                }
708                None
709            }
710        };
711
712        log::info!("Initializing default prettier with plugins {new_plugins:?}");
713        let plugins_to_install = new_plugins.clone();
714        let fs = Arc::clone(&self.fs);
715        let new_installation_task = cx
716            .spawn(|project, mut cx| async move {
717                match locate_prettier_installation
718                    .await
719                    .context("locate prettier installation")
720                    .map_err(Arc::new)?
721                {
722                    ControlFlow::Break(()) => return Ok(()),
723                    ControlFlow::Continue(prettier_path) => {
724                        if prettier_path.is_some() {
725                            new_plugins.clear();
726                        }
727                        let mut needs_install = should_write_prettier_server_file(fs.as_ref()).await;
728                        if let Some(previous_installation_task) = previous_installation_task {
729                            if let Err(e) = previous_installation_task.await {
730                                log::error!("Failed to install default prettier: {e:#}");
731                                project.update(&mut cx, |project, _| {
732                                    if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut project.default_prettier.prettier {
733                                        *attempts += 1;
734                                        new_plugins.extend(not_installed_plugins.iter());
735                                        installation_attempt = *attempts;
736                                        needs_install = true;
737                                    };
738                                })?;
739                            }
740                        };
741                        if installation_attempt > prettier::FAIL_THRESHOLD {
742                            project.update(&mut cx, |project, _| {
743                                if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut project.default_prettier.prettier {
744                                    *installation_task = None;
745                                };
746                            })?;
747                            log::warn!(
748                                "Default prettier installation had failed {installation_attempt} times, not attempting again",
749                            );
750                            return Ok(());
751                        }
752                        project.update(&mut cx, |project, _| {
753                            new_plugins.retain(|plugin| {
754                                !project.default_prettier.installed_plugins.contains(plugin)
755                            });
756                            if let PrettierInstallation::NotInstalled { not_installed_plugins, .. } = &mut project.default_prettier.prettier {
757                                not_installed_plugins.retain(|plugin| {
758                                    !project.default_prettier.installed_plugins.contains(plugin)
759                                });
760                                not_installed_plugins.extend(new_plugins.iter());
761                            }
762                            needs_install |= !new_plugins.is_empty();
763                        })?;
764                        if needs_install {
765                            let installed_plugins = new_plugins.clone();
766                            cx.background_executor()
767                                .spawn(async move {
768                                    install_prettier_packages(fs.as_ref(), new_plugins, node).await?;
769                                    // Save the server file last, so the reinstall need could be determined by the absence of the file.
770                                    save_prettier_server_file(fs.as_ref()).await?;
771                                    anyhow::Ok(())
772                                })
773                                .await
774                                .context("prettier & plugins install")
775                                .map_err(Arc::new)?;
776                            log::info!("Initialized prettier with plugins: {installed_plugins:?}");
777                            project.update(&mut cx, |project, _| {
778                                project.default_prettier.prettier =
779                                    PrettierInstallation::Installed(PrettierInstance {
780                                        attempt: 0,
781                                        prettier: None,
782                                    });
783                                project.default_prettier
784                                    .installed_plugins
785                                    .extend(installed_plugins);
786                            })?;
787                        }
788                    }
789                }
790                Ok(())
791            })
792            .shared();
793        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
794            attempts: installation_attempt,
795            installation_task: Some(new_installation_task),
796            not_installed_plugins: plugins_to_install,
797        };
798    }
799}