Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T01:45:39.937Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

prettier_store.rs

991 lines · 40.8 KB · rust
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)".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::from_unix_str(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_install = plugins_to_install
934        .iter()
935        .map(|package_name| package_name.to_string())
936        .chain(Some("prettier".to_string()))
937        .collect::<Vec<_>>();
938
939    let default_prettier_dir = default_prettier_dir().as_path();
940    match fs.metadata(default_prettier_dir).await.with_context(|| {
941        format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
942    })? {
943        Some(prettier_dir_metadata) => anyhow::ensure!(
944            prettier_dir_metadata.is_dir,
945            "default prettier dir {default_prettier_dir:?} is not a directory"
946        ),
947        None => fs
948            .create_dir(default_prettier_dir)
949            .await
950            .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
951    }
952
953    log::info!("Installing default prettier and plugins: {packages_to_install:?}");
954    let borrowed_packages = packages_to_install
955        .iter()
956        .map(|package_name| package_name.as_str())
957        .collect::<Vec<_>>();
958    node.npm_install_latest_packages(default_prettier_dir, &borrowed_packages)
959        .await
960        .context("fetching formatter packages")?;
961    anyhow::Ok(())
962}
963
964async fn save_prettier_server_file(fs: &dyn Fs) -> anyhow::Result<()> {
965    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
966    fs.save(
967        &prettier_wrapper_path,
968        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
969        text::LineEnding::Unix,
970    )
971    .await
972    .with_context(|| {
973        format!(
974            "writing {} file at {prettier_wrapper_path:?}",
975            prettier::PRETTIER_SERVER_FILE
976        )
977    })?;
978    Ok(())
979}
980
981async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
982    let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
983    if !fs.is_file(&prettier_wrapper_path).await {
984        return true;
985    }
986    let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
987        return true;
988    };
989    prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
990}
991
Served at tenant.openagents/omega Member data and write actions are omitted.