prettier_store.rs

   1use std::{
   2    ops::{ControlFlow, Range},
   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, OffsetUtf16,
  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    range_utf16: Option<Range<OffsetUtf16>>,
 740    cx: &mut AsyncApp,
 741) -> Option<Result<language::Diff>> {
 742    let prettier_instance = prettier_store
 743        .update(cx, |prettier_store, cx| {
 744            prettier_store.prettier_instance_for_buffer(buffer, cx)
 745        })
 746        .ok()?
 747        .await;
 748
 749    let ignore_dir = prettier_store
 750        .update(cx, |prettier_store, cx| {
 751            prettier_store.prettier_ignore_for_buffer(buffer, cx)
 752        })
 753        .ok()?
 754        .await;
 755
 756    let (prettier_path, prettier_task) = prettier_instance?;
 757
 758    let prettier_description = match prettier_path.as_ref() {
 759        Some(path) => format!("prettier at {path:?}"),
 760        None => "default prettier instance".to_string(),
 761    };
 762
 763    let request_timeout: Duration = cx.update(|app| {
 764        ProjectSettings::get_global(app)
 765            .global_lsp_settings
 766            .get_request_timeout()
 767    });
 768
 769    match prettier_task.await {
 770        Ok(prettier) => {
 771            let buffer_path = buffer.update(cx, |buffer, cx| {
 772                File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
 773            });
 774
 775            let format_result = prettier
 776                .format(
 777                    buffer,
 778                    buffer_path,
 779                    ignore_dir,
 780                    range_utf16,
 781                    request_timeout,
 782                    cx,
 783                )
 784                .await
 785                .with_context(|| format!("{} failed to format buffer", prettier_description));
 786
 787            Some(format_result)
 788        }
 789        Err(error) => {
 790            prettier_store
 791                .update(cx, |project, _| {
 792                    let instance_to_update = match prettier_path {
 793                        Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
 794                        None => match &mut project.default_prettier.prettier {
 795                            PrettierInstallation::NotInstalled { .. } => None,
 796                            PrettierInstallation::Installed(instance) => Some(instance),
 797                        },
 798                    };
 799
 800                    if let Some(instance) = instance_to_update {
 801                        instance.attempt += 1;
 802                        instance.prettier = None;
 803                    }
 804                })
 805                .log_err();
 806
 807            Some(Err(anyhow!(
 808                "{prettier_description} failed to spawn: {error:#}"
 809            )))
 810        }
 811    }
 812}
 813
 814#[derive(Debug)]
 815pub struct DefaultPrettier {
 816    prettier: PrettierInstallation,
 817    installed_plugins: HashSet<Arc<str>>,
 818}
 819
 820#[derive(Debug)]
 821pub enum PrettierInstallation {
 822    NotInstalled {
 823        attempts: usize,
 824        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
 825        not_installed_plugins: HashSet<Arc<str>>,
 826    },
 827    Installed(PrettierInstance),
 828}
 829
 830pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
 831
 832#[derive(Debug, Clone)]
 833pub struct PrettierInstance {
 834    attempt: usize,
 835    prettier: Option<PrettierTask>,
 836}
 837
 838impl Default for DefaultPrettier {
 839    fn default() -> Self {
 840        Self {
 841            prettier: PrettierInstallation::NotInstalled {
 842                attempts: 0,
 843                installation_task: None,
 844                not_installed_plugins: HashSet::default(),
 845            },
 846            installed_plugins: HashSet::default(),
 847        }
 848    }
 849}
 850
 851impl DefaultPrettier {
 852    pub fn instance(&self) -> Option<&PrettierInstance> {
 853        if let PrettierInstallation::Installed(instance) = &self.prettier {
 854            Some(instance)
 855        } else {
 856            None
 857        }
 858    }
 859
 860    pub fn prettier_task(
 861        &mut self,
 862        node: &NodeRuntime,
 863        worktree_id: Option<WorktreeId>,
 864        cx: &mut Context<PrettierStore>,
 865    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
 866        match &mut self.prettier {
 867            PrettierInstallation::NotInstalled { .. } => Some(
 868                PrettierStore::start_default_prettier(node.clone(), worktree_id, cx),
 869            ),
 870            PrettierInstallation::Installed(existing_instance) => {
 871                existing_instance.prettier_task(node, None, worktree_id, cx)
 872            }
 873        }
 874    }
 875}
 876
 877impl PrettierInstance {
 878    pub fn prettier_task(
 879        &mut self,
 880        node: &NodeRuntime,
 881        prettier_dir: Option<&Path>,
 882        worktree_id: Option<WorktreeId>,
 883        cx: &mut Context<PrettierStore>,
 884    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
 885        if self.attempt > prettier::FAIL_THRESHOLD {
 886            match prettier_dir {
 887                Some(prettier_dir) => log::warn!(
 888                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
 889                ),
 890                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
 891            }
 892            return None;
 893        }
 894        Some(match &self.prettier {
 895            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
 896            None => match prettier_dir {
 897                Some(prettier_dir) => {
 898                    let new_task = PrettierStore::start_prettier(
 899                        node.clone(),
 900                        prettier_dir.to_path_buf(),
 901                        worktree_id,
 902                        cx,
 903                    );
 904                    self.attempt += 1;
 905                    self.prettier = Some(new_task.clone());
 906                    Task::ready(Ok(new_task))
 907                }
 908                None => {
 909                    self.attempt += 1;
 910                    let node = node.clone();
 911                    cx.spawn(async move |prettier_store, cx| {
 912                        prettier_store
 913                            .update(cx, |_, cx| {
 914                                PrettierStore::start_default_prettier(node, worktree_id, cx)
 915                            })?
 916                            .await
 917                    })
 918                }
 919            },
 920        })
 921    }
 922
 923    pub async fn server(&self) -> Option<Arc<LanguageServer>> {
 924        self.prettier.clone()?.await.ok()?.server().cloned()
 925    }
 926}
 927
 928async fn install_prettier_packages(
 929    fs: &dyn Fs,
 930    plugins_to_install: HashSet<Arc<str>>,
 931    node: NodeRuntime,
 932) -> anyhow::Result<()> {
 933    let packages_to_versions = future::try_join_all(
 934        plugins_to_install
 935            .iter()
 936            .chain(Some(&"prettier".into()))
 937            .map(|package_name| async {
 938                let returned_package_name = package_name.to_string();
 939                let latest_version = node
 940                    .npm_package_latest_version(package_name)
 941                    .await
 942                    .with_context(|| {
 943                        format!("fetching latest npm version for package {returned_package_name}")
 944                    })?;
 945                anyhow::Ok((returned_package_name, latest_version.to_string()))
 946            }),
 947    )
 948    .await
 949    .context("fetching latest npm versions")?;
 950
 951    let default_prettier_dir = default_prettier_dir().as_path();
 952    match fs.metadata(default_prettier_dir).await.with_context(|| {
 953        format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
 954    })? {
 955        Some(prettier_dir_metadata) => anyhow::ensure!(
 956            prettier_dir_metadata.is_dir,
 957            "default prettier dir {default_prettier_dir:?} is not a directory"
 958        ),
 959        None => fs
 960            .create_dir(default_prettier_dir)
 961            .await
 962            .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
 963    }
 964
 965    log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
 966    let borrowed_packages = packages_to_versions
 967        .iter()
 968        .map(|(package, version)| (package.as_str(), version.as_str()))
 969        .collect::<Vec<_>>();
 970    node.npm_install_packages(default_prettier_dir, &borrowed_packages)
 971        .await
 972        .context("fetching formatter packages")?;
 973    anyhow::Ok(())
 974}
 975
 976async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
 977    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
 978    fs.save(
 979        &prettier_wrapper_path,
 980        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
 981        text::LineEnding::Unix,
 982    )
 983    .await
 984    .with_context(|| {
 985        format!(
 986            "writing {} file at {prettier_wrapper_path:?}",
 987            prettier::PRETTIER_SERVER_FILE
 988        )
 989    })?;
 990    Ok(())
 991}
 992
 993async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
 994    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
 995    if !fs.is_file(&prettier_wrapper_path).await {
 996        return true;
 997    }
 998    let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
 999        return true;
1000    };
1001    prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
1002}