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