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