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