prettier_store.rs

  1use std::{
  2    ops::ControlFlow,
  3    path::{Path, PathBuf},
  4    sync::Arc,
  5    time::Duration,
  6};
  7
  8use anyhow::{Context as _, Result, anyhow};
  9use collections::{HashMap, HashSet};
 10use fs::Fs;
 11use futures::{
 12    FutureExt,
 13    future::{self, Shared},
 14    stream::FuturesUnordered,
 15};
 16use gpui::{AppContext as _, AsyncApp, Context, Entity, EventEmitter, Task, WeakEntity};
 17use language::{
 18    Buffer, LanguageRegistry, LocalFile,
 19    language_settings::{Formatter, LanguageSettings},
 20};
 21use lsp::{LanguageServer, LanguageServerId, LanguageServerName};
 22use node_runtime::NodeRuntime;
 23use paths::default_prettier_dir;
 24use prettier::Prettier;
 25use settings::Settings;
 26use smol::stream::StreamExt;
 27use util::{ResultExt, TryFutureExt, rel_path::RelPath};
 28
 29use crate::{
 30    File, PathChange, ProjectEntryId, Worktree, lsp_store::WorktreeId,
 31    project_settings::ProjectSettings, worktree_store::WorktreeStore,
 32};
 33
 34pub struct PrettierStore {
 35    node: NodeRuntime,
 36    fs: Arc<dyn Fs>,
 37    languages: Arc<LanguageRegistry>,
 38    worktree_store: Entity<WorktreeStore>,
 39    default_prettier: DefaultPrettier,
 40    prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
 41    prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>,
 42    prettier_instances: HashMap<PathBuf, PrettierInstance>,
 43}
 44
 45pub(crate) enum PrettierStoreEvent {
 46    LanguageServerRemoved(LanguageServerId),
 47    LanguageServerAdded {
 48        new_server_id: LanguageServerId,
 49        name: LanguageServerName,
 50        prettier_server: Arc<LanguageServer>,
 51    },
 52}
 53
 54impl EventEmitter<PrettierStoreEvent> for PrettierStore {}
 55
 56impl PrettierStore {
 57    pub fn new(
 58        node: NodeRuntime,
 59        fs: Arc<dyn Fs>,
 60        languages: Arc<LanguageRegistry>,
 61        worktree_store: Entity<WorktreeStore>,
 62        _: &mut Context<Self>,
 63    ) -> Self {
 64        Self {
 65            node,
 66            fs,
 67            languages,
 68            worktree_store,
 69            default_prettier: DefaultPrettier::default(),
 70            prettiers_per_worktree: HashMap::default(),
 71            prettier_ignores_per_worktree: HashMap::default(),
 72            prettier_instances: HashMap::default(),
 73        }
 74    }
 75
 76    pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
 77        self.prettier_ignores_per_worktree.remove(&id_to_remove);
 78        let mut prettier_instances_to_clean = FuturesUnordered::new();
 79        if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
 80            for path in prettier_paths.iter().flatten() {
 81                if let Some(prettier_instance) = self.prettier_instances.remove(path) {
 82                    prettier_instances_to_clean.push(async move {
 83                        prettier_instance
 84                            .server()
 85                            .await
 86                            .map(|server| server.server_id())
 87                    });
 88                }
 89            }
 90        }
 91        cx.spawn(async move |prettier_store, cx| {
 92            while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
 93                if let Some(prettier_server_id) = prettier_server_id {
 94                    prettier_store
 95                        .update(cx, |_, cx| {
 96                            cx.emit(PrettierStoreEvent::LanguageServerRemoved(
 97                                prettier_server_id,
 98                            ));
 99                        })
100                        .ok();
101                }
102            }
103        })
104        .detach();
105    }
106
107    fn prettier_instance_for_buffer(
108        &mut self,
109        buffer: &Entity<Buffer>,
110        cx: &mut Context<Self>,
111    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
112        let buffer = buffer.read(cx);
113        let buffer_file = buffer.file();
114        if buffer.language().is_none() {
115            return Task::ready(None);
116        }
117
118        let node = self.node.clone();
119
120        match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
121            Some((worktree_id, buffer_path)) => {
122                let fs = Arc::clone(&self.fs);
123                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
124                cx.spawn(async move |lsp_store, cx| {
125                    match cx
126                        .background_spawn(async move {
127                            Prettier::locate_prettier_installation(
128                                fs.as_ref(),
129                                &installed_prettiers,
130                                &buffer_path,
131                            )
132                            .await
133                        })
134                        .await
135                    {
136                        Ok(ControlFlow::Break(())) => None,
137                        Ok(ControlFlow::Continue(None)) => {
138                            let default_task = lsp_store
139                                .update(cx, |lsp_store, cx| {
140                                    lsp_store
141                                        .prettiers_per_worktree
142                                        .entry(worktree_id)
143                                        .or_default()
144                                        .insert(None);
145                                    lsp_store.default_prettier.prettier_task(
146                                        &node,
147                                        Some(worktree_id),
148                                        cx,
149                                    )
150                                })
151                                .ok()??;
152                            let default_instance = default_task.await.ok()?;
153                            Some((None, default_instance))
154                        }
155                        Ok(ControlFlow::Continue(Some(prettier_dir))) => {
156                            lsp_store
157                                .update(cx, |lsp_store, _| {
158                                    lsp_store
159                                        .prettiers_per_worktree
160                                        .entry(worktree_id)
161                                        .or_default()
162                                        .insert(Some(prettier_dir.clone()))
163                                })
164                                .ok()?;
165                            if let Some(prettier_task) = lsp_store
166                                .update(cx, |lsp_store, cx| {
167                                    lsp_store
168                                        .prettier_instances
169                                        .get_mut(&prettier_dir)
170                                        .and_then(|existing_instance| {
171                                            existing_instance.prettier_task(
172                                                &node,
173                                                Some(&prettier_dir),
174                                                Some(worktree_id),
175                                                cx,
176                                            )
177                                        })
178                                })
179                                .ok()?
180                            {
181                                log::debug!("Found already started prettier in {prettier_dir:?}");
182                                return Some((Some(prettier_dir), prettier_task.await.log_err()?));
183                            }
184
185                            log::info!("Found prettier in {prettier_dir:?}, starting.");
186                            let new_prettier_task = lsp_store
187                                .update(cx, |lsp_store, cx| {
188                                    let new_prettier_task = Self::start_prettier(
189                                        node,
190                                        prettier_dir.clone(),
191                                        Some(worktree_id),
192                                        cx,
193                                    );
194                                    lsp_store.prettier_instances.insert(
195                                        prettier_dir.clone(),
196                                        PrettierInstance {
197                                            attempt: 0,
198                                            prettier: Some(new_prettier_task.clone()),
199                                        },
200                                    );
201                                    new_prettier_task
202                                })
203                                .ok()?;
204                            Some((Some(prettier_dir), new_prettier_task))
205                        }
206                        Err(e) => {
207                            log::error!("Failed to determine prettier path for buffer: {e:#}");
208                            None
209                        }
210                    }
211                })
212            }
213            None => {
214                let new_task = self.default_prettier.prettier_task(&node, None, cx);
215                cx.spawn(async move |_, _| Some((None, new_task?.log_err().await?)))
216            }
217        }
218    }
219
220    fn prettier_ignore_for_buffer(
221        &mut self,
222        buffer: &Entity<Buffer>,
223        cx: &mut Context<Self>,
224    ) -> Task<Option<PathBuf>> {
225        let buffer = buffer.read(cx);
226        let buffer_file = buffer.file();
227        if buffer.language().is_none() {
228            return Task::ready(None);
229        }
230        match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
231            Some((worktree_id, buffer_path)) => {
232                let fs = Arc::clone(&self.fs);
233                let prettier_ignores = self
234                    .prettier_ignores_per_worktree
235                    .get(&worktree_id)
236                    .cloned()
237                    .unwrap_or_default();
238                cx.spawn(async move |lsp_store, cx| {
239                    match cx
240                        .background_spawn(async move {
241                            Prettier::locate_prettier_ignore(
242                                fs.as_ref(),
243                                &prettier_ignores,
244                                &buffer_path,
245                            )
246                            .await
247                        })
248                        .await
249                    {
250                        Ok(ControlFlow::Break(())) => None,
251                        Ok(ControlFlow::Continue(None)) => None,
252                        Ok(ControlFlow::Continue(Some(ignore_dir))) => {
253                            log::debug!("Found prettier ignore in {ignore_dir:?}");
254                            lsp_store
255                                .update(cx, |store, _| {
256                                    store
257                                        .prettier_ignores_per_worktree
258                                        .entry(worktree_id)
259                                        .or_default()
260                                        .insert(ignore_dir.clone());
261                                })
262                                .ok();
263                            Some(ignore_dir)
264                        }
265                        Err(e) => {
266                            log::error!(
267                                "Failed to determine prettier ignore path for buffer: {e:#}"
268                            );
269                            None
270                        }
271                    }
272                })
273            }
274            None => Task::ready(None),
275        }
276    }
277
278    fn start_prettier(
279        node: NodeRuntime,
280        prettier_dir: PathBuf,
281        worktree_id: Option<WorktreeId>,
282        cx: &mut Context<Self>,
283    ) -> PrettierTask {
284        let request_timeout = ProjectSettings::get_global(cx)
285            .global_lsp_settings
286            .get_request_timeout();
287
288        cx.spawn(async move |prettier_store, cx| {
289            log::info!("Starting prettier at path {prettier_dir:?}");
290            let new_server_id = prettier_store.read_with(cx, |prettier_store, _| {
291                prettier_store.languages.next_language_server_id()
292            })?;
293
294            let new_prettier = Prettier::start(
295                new_server_id,
296                prettier_dir,
297                node,
298                request_timeout,
299                cx.clone(),
300            )
301            .await
302            .context("default prettier spawn")
303            .map(Arc::new)
304            .map_err(Arc::new)?;
305            Self::register_new_prettier(
306                &prettier_store,
307                &new_prettier,
308                worktree_id,
309                new_server_id,
310                cx,
311            );
312            Ok(new_prettier)
313        })
314        .shared()
315    }
316
317    fn start_default_prettier(
318        node: NodeRuntime,
319        worktree_id: Option<WorktreeId>,
320        cx: &mut Context<PrettierStore>,
321    ) -> Task<anyhow::Result<PrettierTask>> {
322        cx.spawn(async move |prettier_store, cx| {
323            let installation_task = prettier_store.read_with(cx, |prettier_store, _| {
324                match &prettier_store.default_prettier.prettier {
325                    PrettierInstallation::NotInstalled {
326                        installation_task, ..
327                    } => ControlFlow::Continue(installation_task.clone()),
328                    PrettierInstallation::Installed(default_prettier) => {
329                        ControlFlow::Break(default_prettier.clone())
330                    }
331                }
332            })?;
333            match installation_task {
334                ControlFlow::Continue(None) => {
335                    anyhow::bail!("Default prettier is not installed and cannot be started")
336                }
337                ControlFlow::Continue(Some(installation_task)) => {
338                    log::info!("Waiting for default prettier to install");
339                    if let Err(e) = installation_task.await {
340                        prettier_store.update(cx, |project, _| {
341                            if let PrettierInstallation::NotInstalled {
342                                installation_task,
343                                attempts,
344                                ..
345                            } = &mut project.default_prettier.prettier
346                            {
347                                *installation_task = None;
348                                *attempts += 1;
349                            }
350                        })?;
351                        anyhow::bail!(
352                            "Cannot start default prettier due to its installation failure: {e:#}"
353                        );
354                    }
355                    let new_default_prettier =
356                        prettier_store.update(cx, |prettier_store, cx| {
357                            let new_default_prettier = Self::start_prettier(
358                                node,
359                                default_prettier_dir().clone(),
360                                worktree_id,
361                                cx,
362                            );
363                            prettier_store.default_prettier.prettier =
364                                PrettierInstallation::Installed(PrettierInstance {
365                                    attempt: 0,
366                                    prettier: Some(new_default_prettier.clone()),
367                                });
368                            new_default_prettier
369                        })?;
370                    Ok(new_default_prettier)
371                }
372                ControlFlow::Break(instance) => match instance.prettier {
373                    Some(instance) => Ok(instance),
374                    None => {
375                        let new_default_prettier =
376                            prettier_store.update(cx, |prettier_store, cx| {
377                                let new_default_prettier = Self::start_prettier(
378                                    node,
379                                    default_prettier_dir().clone(),
380                                    worktree_id,
381                                    cx,
382                                );
383                                prettier_store.default_prettier.prettier =
384                                    PrettierInstallation::Installed(PrettierInstance {
385                                        attempt: instance.attempt + 1,
386                                        prettier: Some(new_default_prettier.clone()),
387                                    });
388                                new_default_prettier
389                            })?;
390                        Ok(new_default_prettier)
391                    }
392                },
393            }
394        })
395    }
396
397    fn register_new_prettier(
398        prettier_store: &WeakEntity<Self>,
399        prettier: &Prettier,
400        worktree_id: Option<WorktreeId>,
401        new_server_id: LanguageServerId,
402        cx: &mut AsyncApp,
403    ) {
404        let prettier_dir = prettier.prettier_dir();
405        let is_default = prettier.is_default();
406        if is_default {
407            log::info!("Started default prettier in {prettier_dir:?}");
408        } else {
409            log::info!("Started prettier in {prettier_dir:?}");
410        }
411        if let Some(prettier_server) = prettier.server() {
412            prettier_store
413                .update(cx, |prettier_store, cx| {
414                    let name = if is_default {
415                        LanguageServerName("prettier (default)".to_string().into())
416                    } else {
417                        let worktree_path = worktree_id
418                            .and_then(|id| {
419                                prettier_store
420                                    .worktree_store
421                                    .read(cx)
422                                    .worktree_for_id(id, cx)
423                            })
424                            .map(|worktree| worktree.read(cx).abs_path());
425                        let name = match worktree_path {
426                            Some(worktree_path) => {
427                                if prettier_dir == worktree_path.as_ref() {
428                                    let name = prettier_dir
429                                        .file_name()
430                                        .and_then(|name| name.to_str())
431                                        .unwrap_or_default();
432                                    format!("prettier ({name})")
433                                } else {
434                                    let dir_to_display = prettier_dir
435                                        .strip_prefix(worktree_path.as_ref())
436                                        .ok()
437                                        .unwrap_or(prettier_dir);
438                                    format!("prettier ({})", dir_to_display.display())
439                                }
440                            }
441                            None => format!("prettier ({})", prettier_dir.display()),
442                        };
443                        LanguageServerName(name.into())
444                    };
445                    cx.emit(PrettierStoreEvent::LanguageServerAdded {
446                        new_server_id,
447                        name,
448                        prettier_server: prettier_server.clone(),
449                    });
450                })
451                .ok();
452        }
453    }
454
455    pub fn update_prettier_settings(
456        &self,
457        worktree: &Entity<Worktree>,
458        changes: &[(Arc<RelPath>, ProjectEntryId, PathChange)],
459        cx: &mut Context<Self>,
460    ) {
461        let prettier_config_files = Prettier::CONFIG_FILE_NAMES
462            .iter()
463            .map(|name| RelPath::unix(name).unwrap())
464            .collect::<HashSet<_>>();
465
466        let prettier_config_file_changed = changes
467            .iter()
468            .filter(|(path, _, change)| {
469                !matches!(change, PathChange::Loaded)
470                    && !path
471                        .components()
472                        .any(|component| component == "node_modules")
473            })
474            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
475
476        let Some((config_path, _, _)) = prettier_config_file_changed else {
477            return;
478        };
479
480        let current_worktree_id = worktree.read(cx).id();
481
482        log::info!(
483            "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
484        );
485
486        let prettiers_to_reload = self
487            .prettiers_per_worktree
488            .get(&current_worktree_id)
489            .iter()
490            .flat_map(|prettier_paths| prettier_paths.iter())
491            .flatten()
492            .filter_map(|prettier_path| {
493                Some((
494                    current_worktree_id,
495                    Some(prettier_path.clone()),
496                    self.prettier_instances.get(prettier_path)?.clone(),
497                ))
498            })
499            .chain(
500                self.default_prettier
501                    .instance()
502                    .map(|default_prettier| (current_worktree_id, None, default_prettier.clone())),
503            )
504            .collect::<Vec<_>>();
505
506        let request_timeout = ProjectSettings::get_global(cx)
507            .global_lsp_settings
508            .get_request_timeout();
509
510        cx.background_spawn(async move {
511            let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
512                async move {
513                    let Some(instance) = prettier_instance.prettier else {
514                        return
515                    };
516
517                    match instance.await {
518                        Ok(prettier) => {
519                            prettier.clear_cache(request_timeout).log_err().await;
520                        },
521                        Err(e) => {
522                            match prettier_path {
523                                Some(prettier_path) => log::error!(
524                                    "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
525                                ),
526                                None => log::error!(
527                                    "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
528                                ),
529                            }
530                        },
531                    }
532                }
533            }))
534            .await;
535        })
536            .detach();
537    }
538
539    pub fn install_default_prettier(
540        &mut self,
541        worktree: Option<WorktreeId>,
542        plugins: impl Iterator<Item = Arc<str>>,
543        cx: &mut Context<Self>,
544    ) {
545        if cfg!(any(test, feature = "test-support")) {
546            self.default_prettier.installed_plugins.extend(plugins);
547            self.default_prettier.prettier = PrettierInstallation::Installed(PrettierInstance {
548                attempt: 0,
549                prettier: None,
550            });
551            return;
552        }
553
554        let mut new_plugins = plugins.collect::<HashSet<_>>();
555        let node = self.node.clone();
556
557        new_plugins.retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
558        let mut installation_attempt = 0;
559        let previous_installation_task = match &mut self.default_prettier.prettier {
560            PrettierInstallation::NotInstalled {
561                installation_task,
562                attempts,
563                not_installed_plugins,
564            } => {
565                installation_attempt = *attempts;
566                if installation_attempt > prettier::FAIL_THRESHOLD {
567                    *installation_task = None;
568                    log::warn!(
569                        "Default prettier installation had failed {installation_attempt} times, not attempting again",
570                    );
571                    return;
572                }
573                new_plugins.extend(not_installed_plugins.iter().cloned());
574                installation_task.clone()
575            }
576            PrettierInstallation::Installed { .. } => {
577                if new_plugins.is_empty() {
578                    return;
579                }
580                None
581            }
582        };
583
584        let plugins_to_install = new_plugins.clone();
585        let fs = Arc::clone(&self.fs);
586        let new_installation_task = cx
587            .spawn(async move  |prettier_store, cx| {
588                cx.background_executor().timer(Duration::from_millis(30)).await;
589                let location_data = prettier_store.update(cx, |prettier_store, cx| {
590                    worktree.and_then(|worktree_id| {
591                        prettier_store.worktree_store
592                            .read(cx)
593                            .worktree_for_id(worktree_id, cx)
594                            .map(|worktree| worktree.read(cx).abs_path())
595                    }).map(|locate_from| {
596                        let installed_prettiers = prettier_store.prettier_instances.keys().cloned().collect();
597                        (locate_from, installed_prettiers)
598                    })
599                })?;
600                let locate_prettier_installation = match location_data {
601                    Some((locate_from, installed_prettiers)) => Prettier::locate_prettier_installation(
602                        fs.as_ref(),
603                        &installed_prettiers,
604                        locate_from.as_ref(),
605                    )
606                    .await
607                    .context("locate prettier installation").map_err(Arc::new)?,
608                    None => ControlFlow::Continue(None),
609                };
610
611                match locate_prettier_installation
612                {
613                    ControlFlow::Break(()) => return Ok(()),
614                    ControlFlow::Continue(prettier_path) => {
615                        if prettier_path.is_some() {
616                            new_plugins.clear();
617                        }
618                        let mut needs_install = should_write_prettier_server_file(fs.as_ref()).await;
619                        if let Some(previous_installation_task) = previous_installation_task
620                            && let Err(e) = previous_installation_task.await {
621                                log::error!("Failed to install default prettier: {e:#}");
622                                prettier_store.update(cx, |prettier_store, _| {
623                                    if let PrettierInstallation::NotInstalled { attempts, not_installed_plugins, .. } = &mut prettier_store.default_prettier.prettier {
624                                        *attempts += 1;
625                                        new_plugins.extend(not_installed_plugins.iter().cloned());
626                                        installation_attempt = *attempts;
627                                        needs_install = true;
628                                    };
629                                })?;
630                            };
631                        if installation_attempt > prettier::FAIL_THRESHOLD {
632                            prettier_store.update(cx, |prettier_store, _| {
633                                if let PrettierInstallation::NotInstalled { installation_task, .. } = &mut prettier_store.default_prettier.prettier {
634                                    *installation_task = None;
635                                };
636                            })?;
637                            log::warn!(
638                                "Default prettier installation had failed {installation_attempt} times, not attempting again",
639                            );
640                            return Ok(());
641                        }
642                        prettier_store.update(cx, |prettier_store, _| {
643                            new_plugins.retain(|plugin| {
644                                !prettier_store.default_prettier.installed_plugins.contains(plugin)
645                            });
646                            if let PrettierInstallation::NotInstalled { not_installed_plugins, .. } = &mut prettier_store.default_prettier.prettier {
647                                not_installed_plugins.retain(|plugin| {
648                                    !prettier_store.default_prettier.installed_plugins.contains(plugin)
649                                });
650                                not_installed_plugins.extend(new_plugins.iter().cloned());
651                            }
652                            needs_install |= !new_plugins.is_empty();
653                        })?;
654                        if needs_install {
655                            log::info!("Initializing default prettier with plugins {new_plugins:?}");
656                            let installed_plugins = new_plugins.clone();
657                            cx.background_spawn(async move {
658                                install_prettier_packages(fs.as_ref(), new_plugins, node).await?;
659                                // Save the server file last, so the reinstall need could be determined by the absence of the file.
660                                save_prettier_server_file(fs.as_ref()).await?;
661                                anyhow::Ok(())
662                            })
663                                .await
664                                .context("prettier & plugins install")
665                                .map_err(Arc::new)?;
666                            log::info!("Initialized default prettier with plugins: {installed_plugins:?}");
667                            prettier_store.update(cx, |prettier_store, _| {
668                                prettier_store.default_prettier.prettier =
669                                    PrettierInstallation::Installed(PrettierInstance {
670                                        attempt: 0,
671                                        prettier: None,
672                                    });
673                                prettier_store.default_prettier
674                                    .installed_plugins
675                                    .extend(installed_plugins);
676                            })?;
677                        } else {
678                            prettier_store.update(cx, |prettier_store, _| {
679                                if let PrettierInstallation::NotInstalled { .. } = &mut prettier_store.default_prettier.prettier {
680                                    prettier_store.default_prettier.prettier =
681                                        PrettierInstallation::Installed(PrettierInstance {
682                                            attempt: 0,
683                                            prettier: None,
684                                        });
685                                }
686                            })?;
687                        }
688                    }
689                }
690                Ok(())
691            })
692            .shared();
693        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
694            attempts: installation_attempt,
695            installation_task: Some(new_installation_task),
696            not_installed_plugins: plugins_to_install,
697        };
698    }
699
700    pub fn on_settings_changed(
701        &mut self,
702        language_formatters_to_check: Vec<(Option<WorktreeId>, LanguageSettings)>,
703        cx: &mut Context<Self>,
704    ) {
705        let mut prettier_plugins_by_worktree = HashMap::default();
706        for (worktree, language_settings) in language_formatters_to_check {
707            if language_settings.prettier.allowed
708                && let Some(plugins) = prettier_plugins_for_language(&language_settings)
709            {
710                prettier_plugins_by_worktree
711                    .entry(worktree)
712                    .or_insert_with(HashSet::default)
713                    .extend(plugins.iter().cloned());
714            }
715        }
716        for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
717            self.install_default_prettier(
718                worktree,
719                prettier_plugins.into_iter().map(Arc::from),
720                cx,
721            );
722        }
723    }
724}
725
726pub fn prettier_plugins_for_language(
727    language_settings: &LanguageSettings,
728) -> Option<&HashSet<String>> {
729    let formatters = language_settings.formatter.as_ref();
730    if formatters.contains(&Formatter::Prettier) || formatters.contains(&Formatter::Auto) {
731        return Some(&language_settings.prettier.plugins);
732    }
733    None
734}
735
736pub(super) async fn format_with_prettier(
737    prettier_store: &WeakEntity<PrettierStore>,
738    buffer: &Entity<Buffer>,
739    cx: &mut AsyncApp,
740) -> Option<Result<language::Diff>> {
741    let prettier_instance = prettier_store
742        .update(cx, |prettier_store, cx| {
743            prettier_store.prettier_instance_for_buffer(buffer, cx)
744        })
745        .ok()?
746        .await;
747
748    let ignore_dir = prettier_store
749        .update(cx, |prettier_store, cx| {
750            prettier_store.prettier_ignore_for_buffer(buffer, cx)
751        })
752        .ok()?
753        .await;
754
755    let (prettier_path, prettier_task) = prettier_instance?;
756
757    let prettier_description = match prettier_path.as_ref() {
758        Some(path) => format!("prettier at {path:?}"),
759        None => "default prettier instance".to_string(),
760    };
761
762    let request_timeout: Duration = cx.update(|app| {
763        ProjectSettings::get_global(app)
764            .global_lsp_settings
765            .get_request_timeout()
766    });
767
768    match prettier_task.await {
769        Ok(prettier) => {
770            let buffer_path = buffer.update(cx, |buffer, cx| {
771                File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
772            });
773
774            let format_result = prettier
775                .format(buffer, buffer_path, ignore_dir, request_timeout, cx)
776                .await
777                .with_context(|| format!("{} failed to format buffer", prettier_description));
778
779            Some(format_result)
780        }
781        Err(error) => {
782            prettier_store
783                .update(cx, |project, _| {
784                    let instance_to_update = match prettier_path {
785                        Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
786                        None => match &mut project.default_prettier.prettier {
787                            PrettierInstallation::NotInstalled { .. } => None,
788                            PrettierInstallation::Installed(instance) => Some(instance),
789                        },
790                    };
791
792                    if let Some(instance) = instance_to_update {
793                        instance.attempt += 1;
794                        instance.prettier = None;
795                    }
796                })
797                .log_err();
798
799            Some(Err(anyhow!(
800                "{prettier_description} failed to spawn: {error:#}"
801            )))
802        }
803    }
804}
805
806#[derive(Debug)]
807pub struct DefaultPrettier {
808    prettier: PrettierInstallation,
809    installed_plugins: HashSet<Arc<str>>,
810}
811
812#[derive(Debug)]
813pub enum PrettierInstallation {
814    NotInstalled {
815        attempts: usize,
816        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
817        not_installed_plugins: HashSet<Arc<str>>,
818    },
819    Installed(PrettierInstance),
820}
821
822pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
823
824#[derive(Debug, Clone)]
825pub struct PrettierInstance {
826    attempt: usize,
827    prettier: Option<PrettierTask>,
828}
829
830impl Default for DefaultPrettier {
831    fn default() -> Self {
832        Self {
833            prettier: PrettierInstallation::NotInstalled {
834                attempts: 0,
835                installation_task: None,
836                not_installed_plugins: HashSet::default(),
837            },
838            installed_plugins: HashSet::default(),
839        }
840    }
841}
842
843impl DefaultPrettier {
844    pub fn instance(&self) -> Option<&PrettierInstance> {
845        if let PrettierInstallation::Installed(instance) = &self.prettier {
846            Some(instance)
847        } else {
848            None
849        }
850    }
851
852    pub fn prettier_task(
853        &mut self,
854        node: &NodeRuntime,
855        worktree_id: Option<WorktreeId>,
856        cx: &mut Context<PrettierStore>,
857    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
858        match &mut self.prettier {
859            PrettierInstallation::NotInstalled { .. } => Some(
860                PrettierStore::start_default_prettier(node.clone(), worktree_id, cx),
861            ),
862            PrettierInstallation::Installed(existing_instance) => {
863                existing_instance.prettier_task(node, None, worktree_id, cx)
864            }
865        }
866    }
867}
868
869impl PrettierInstance {
870    pub fn prettier_task(
871        &mut self,
872        node: &NodeRuntime,
873        prettier_dir: Option<&Path>,
874        worktree_id: Option<WorktreeId>,
875        cx: &mut Context<PrettierStore>,
876    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
877        if self.attempt > prettier::FAIL_THRESHOLD {
878            match prettier_dir {
879                Some(prettier_dir) => log::warn!(
880                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
881                ),
882                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
883            }
884            return None;
885        }
886        Some(match &self.prettier {
887            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
888            None => match prettier_dir {
889                Some(prettier_dir) => {
890                    let new_task = PrettierStore::start_prettier(
891                        node.clone(),
892                        prettier_dir.to_path_buf(),
893                        worktree_id,
894                        cx,
895                    );
896                    self.attempt += 1;
897                    self.prettier = Some(new_task.clone());
898                    Task::ready(Ok(new_task))
899                }
900                None => {
901                    self.attempt += 1;
902                    let node = node.clone();
903                    cx.spawn(async move |prettier_store, cx| {
904                        prettier_store
905                            .update(cx, |_, cx| {
906                                PrettierStore::start_default_prettier(node, worktree_id, cx)
907                            })?
908                            .await
909                    })
910                }
911            },
912        })
913    }
914
915    pub async fn server(&self) -> Option<Arc<LanguageServer>> {
916        self.prettier.clone()?.await.ok()?.server().cloned()
917    }
918}
919
920async fn install_prettier_packages(
921    fs: &dyn Fs,
922    plugins_to_install: HashSet<Arc<str>>,
923    node: NodeRuntime,
924) -> anyhow::Result<()> {
925    let packages_to_versions = future::try_join_all(
926        plugins_to_install
927            .iter()
928            .chain(Some(&"prettier".into()))
929            .map(|package_name| async {
930                let returned_package_name = package_name.to_string();
931                let latest_version = node
932                    .npm_package_latest_version(package_name)
933                    .await
934                    .with_context(|| {
935                        format!("fetching latest npm version for package {returned_package_name}")
936                    })?;
937                anyhow::Ok((returned_package_name, latest_version.to_string()))
938            }),
939    )
940    .await
941    .context("fetching latest npm versions")?;
942
943    let default_prettier_dir = default_prettier_dir().as_path();
944    match fs.metadata(default_prettier_dir).await.with_context(|| {
945        format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
946    })? {
947        Some(prettier_dir_metadata) => anyhow::ensure!(
948            prettier_dir_metadata.is_dir,
949            "default prettier dir {default_prettier_dir:?} is not a directory"
950        ),
951        None => fs
952            .create_dir(default_prettier_dir)
953            .await
954            .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
955    }
956
957    log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
958    let borrowed_packages = packages_to_versions
959        .iter()
960        .map(|(package, version)| (package.as_str(), version.as_str()))
961        .collect::<Vec<_>>();
962    node.npm_install_packages(default_prettier_dir, &borrowed_packages)
963        .await
964        .context("fetching formatter packages")?;
965    anyhow::Ok(())
966}
967
968async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
969    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
970    fs.save(
971        &prettier_wrapper_path,
972        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
973        text::LineEnding::Unix,
974    )
975    .await
976    .with_context(|| {
977        format!(
978            "writing {} file at {prettier_wrapper_path:?}",
979            prettier::PRETTIER_SERVER_FILE
980        )
981    })?;
982    Ok(())
983}
984
985async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
986    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
987    if !fs.is_file(&prettier_wrapper_path).await {
988        return true;
989    }
990    let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
991        return true;
992    };
993    prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
994}