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