prettier_store.rs

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