prettier_support.rs

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