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::{
17 AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, Task, WeakEntity,
18};
19use language::{
20 Buffer, LanguageRegistry, LocalFile,
21 language_settings::{Formatter, LanguageSettings},
22};
23use lsp::{LanguageServer, LanguageServerId, LanguageServerName};
24use node_runtime::NodeRuntime;
25use paths::default_prettier_dir;
26use prettier::Prettier;
27use smol::stream::StreamExt;
28use util::{ResultExt, TryFutureExt, rel_path::RelPath};
29
30use crate::{
31 File, PathChange, ProjectEntryId, Worktree, lsp_store::WorktreeId,
32 worktree_store::WorktreeStore,
33};
34
35pub struct PrettierStore {
36 node: NodeRuntime,
37 fs: Arc<dyn Fs>,
38 languages: Arc<LanguageRegistry>,
39 worktree_store: Entity<WorktreeStore>,
40 default_prettier: DefaultPrettier,
41 prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
42 prettier_ignores_per_worktree: HashMap<WorktreeId, HashSet<PathBuf>>,
43 prettier_instances: HashMap<PathBuf, PrettierInstance>,
44}
45
46pub(crate) enum PrettierStoreEvent {
47 LanguageServerRemoved(LanguageServerId),
48 LanguageServerAdded {
49 new_server_id: LanguageServerId,
50 name: LanguageServerName,
51 prettier_server: Arc<LanguageServer>,
52 },
53}
54
55impl EventEmitter<PrettierStoreEvent> for PrettierStore {}
56
57impl PrettierStore {
58 pub fn new(
59 node: NodeRuntime,
60 fs: Arc<dyn Fs>,
61 languages: Arc<LanguageRegistry>,
62 worktree_store: Entity<WorktreeStore>,
63 _: &mut Context<Self>,
64 ) -> Self {
65 Self {
66 node,
67 fs,
68 languages,
69 worktree_store,
70 default_prettier: DefaultPrettier::default(),
71 prettiers_per_worktree: HashMap::default(),
72 prettier_ignores_per_worktree: HashMap::default(),
73 prettier_instances: HashMap::default(),
74 }
75 }
76
77 pub fn remove_worktree(&mut self, id_to_remove: WorktreeId, cx: &mut Context<Self>) {
78 self.prettier_ignores_per_worktree.remove(&id_to_remove);
79 let mut prettier_instances_to_clean = FuturesUnordered::new();
80 if let Some(prettier_paths) = self.prettiers_per_worktree.remove(&id_to_remove) {
81 for path in prettier_paths.iter().flatten() {
82 if let Some(prettier_instance) = self.prettier_instances.remove(path) {
83 prettier_instances_to_clean.push(async move {
84 prettier_instance
85 .server()
86 .await
87 .map(|server| server.server_id())
88 });
89 }
90 }
91 }
92 cx.spawn(async move |prettier_store, cx| {
93 while let Some(prettier_server_id) = prettier_instances_to_clean.next().await {
94 if let Some(prettier_server_id) = prettier_server_id {
95 prettier_store
96 .update(cx, |_, cx| {
97 cx.emit(PrettierStoreEvent::LanguageServerRemoved(
98 prettier_server_id,
99 ));
100 })
101 .ok();
102 }
103 }
104 })
105 .detach();
106 }
107
108 fn prettier_instance_for_buffer(
109 &mut self,
110 buffer: &Entity<Buffer>,
111 cx: &mut Context<Self>,
112 ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
113 let buffer = buffer.read(cx);
114 let buffer_file = buffer.file();
115 if buffer.language().is_none() {
116 return Task::ready(None);
117 }
118
119 let node = self.node.clone();
120
121 match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx))) {
122 Some((worktree_id, buffer_path)) => {
123 let fs = Arc::clone(&self.fs);
124 let installed_prettiers = self.prettier_instances.keys().cloned().collect();
125 cx.spawn(async move |lsp_store, cx| {
126 match cx
127 .background_spawn(async move {
128 Prettier::locate_prettier_installation(
129 fs.as_ref(),
130 &installed_prettiers,
131 &buffer_path,
132 )
133 .await
134 })
135 .await
136 {
137 Ok(ControlFlow::Break(())) => None,
138 Ok(ControlFlow::Continue(None)) => {
139 let default_instance = lsp_store
140 .update(cx, |lsp_store, cx| {
141 lsp_store
142 .prettiers_per_worktree
143 .entry(worktree_id)
144 .or_default()
145 .insert(None);
146 lsp_store.default_prettier.prettier_task(
147 &node,
148 Some(worktree_id),
149 cx,
150 )
151 })
152 .ok()?;
153 Some((None, default_instance?.log_err().await?))
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.prettier_instances.get_mut(&prettier_dir).map(
168 |existing_instance| {
169 existing_instance.prettier_task(
170 &node,
171 Some(&prettier_dir),
172 Some(worktree_id),
173 cx,
174 )
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()
565 .timer(Duration::from_millis(30))
566 .await;
567 let location_data = prettier_store.update(cx, |prettier_store, cx| {
568 worktree
569 .and_then(|worktree_id| {
570 prettier_store
571 .worktree_store
572 .read(cx)
573 .worktree_for_id(worktree_id, cx)
574 .map(|worktree| worktree.read(cx).abs_path())
575 })
576 .map(|locate_from| {
577 let installed_prettiers =
578 prettier_store.prettier_instances.keys().cloned().collect();
579 (locate_from, installed_prettiers)
580 })
581 })?;
582 let locate_prettier_installation = match location_data {
583 Some((locate_from, installed_prettiers)) => {
584 Prettier::locate_prettier_installation(
585 fs.as_ref(),
586 &installed_prettiers,
587 locate_from.as_ref(),
588 )
589 .await
590 .context("locate prettier installation")
591 .map_err(Arc::new)?
592 }
593 None => ControlFlow::Continue(None),
594 };
595
596 match locate_prettier_installation {
597 ControlFlow::Break(()) => return Ok(()),
598 ControlFlow::Continue(prettier_path) => {
599 if prettier_path.is_some() {
600 new_plugins.clear();
601 }
602 let mut needs_install =
603 should_write_prettier_server_file(fs.as_ref()).await;
604 if let Some(previous_installation_task) = previous_installation_task
605 && let Err(e) = previous_installation_task.await
606 {
607 log::error!("Failed to install default prettier: {e:#}");
608 prettier_store.update(cx, |prettier_store, _| {
609 if let PrettierInstallation::NotInstalled {
610 attempts,
611 not_installed_plugins,
612 ..
613 } = &mut prettier_store.default_prettier.prettier
614 {
615 *attempts += 1;
616 new_plugins.extend(not_installed_plugins.iter().cloned());
617 installation_attempt = *attempts;
618 needs_install = true;
619 };
620 })?;
621 };
622 if installation_attempt > prettier::FAIL_THRESHOLD {
623 prettier_store.update(cx, |prettier_store, _| {
624 if let PrettierInstallation::NotInstalled {
625 installation_task,
626 ..
627 } = &mut prettier_store.default_prettier.prettier
628 {
629 *installation_task = None;
630 };
631 })?;
632 log::warn!(
633 "Default prettier installation had failed {installation_attempt} \
634 times, not attempting again",
635 );
636 return Ok(());
637 }
638 prettier_store.update(cx, |prettier_store, _| {
639 new_plugins.retain(|plugin| {
640 !prettier_store
641 .default_prettier
642 .installed_plugins
643 .contains(plugin)
644 });
645 if let PrettierInstallation::NotInstalled {
646 not_installed_plugins,
647 ..
648 } = &mut prettier_store.default_prettier.prettier
649 {
650 not_installed_plugins.retain(|plugin| {
651 !prettier_store
652 .default_prettier
653 .installed_plugins
654 .contains(plugin)
655 });
656 not_installed_plugins.extend(new_plugins.iter().cloned());
657 }
658 needs_install |= !new_plugins.is_empty();
659 })?;
660 if needs_install {
661 log::info!(
662 "Initializing default prettier with plugins {new_plugins:?}"
663 );
664 let installed_plugins = new_plugins.clone();
665 let executor = cx.background_executor().clone();
666 cx.background_spawn(async move {
667 install_prettier_packages(fs.as_ref(), new_plugins, node).await?;
668 // Save the server file last, so the reinstall need could be determined by the absence of the file.
669 save_prettier_server_file(fs.as_ref(), &executor).await?;
670 anyhow::Ok(())
671 })
672 .await
673 .context("prettier & plugins install")
674 .map_err(Arc::new)?;
675 log::info!(
676 "Initialized default prettier with plugins: {installed_plugins:?}"
677 );
678 prettier_store.update(cx, |prettier_store, _| {
679 prettier_store.default_prettier.prettier =
680 PrettierInstallation::Installed(PrettierInstance {
681 attempt: 0,
682 prettier: None,
683 });
684 prettier_store
685 .default_prettier
686 .installed_plugins
687 .extend(installed_plugins);
688 })?;
689 } else {
690 prettier_store.update(cx, |prettier_store, _| {
691 if let PrettierInstallation::NotInstalled { .. } =
692 &mut prettier_store.default_prettier.prettier
693 {
694 prettier_store.default_prettier.prettier =
695 PrettierInstallation::Installed(PrettierInstance {
696 attempt: 0,
697 prettier: None,
698 });
699 }
700 })?;
701 }
702 }
703 }
704 Ok(())
705 })
706 .shared();
707 self.default_prettier.prettier = PrettierInstallation::NotInstalled {
708 attempts: installation_attempt,
709 installation_task: Some(new_installation_task),
710 not_installed_plugins: plugins_to_install,
711 };
712 }
713
714 pub fn on_settings_changed(
715 &mut self,
716 language_formatters_to_check: Vec<(Option<WorktreeId>, LanguageSettings)>,
717 cx: &mut Context<Self>,
718 ) {
719 let mut prettier_plugins_by_worktree = HashMap::default();
720 for (worktree, language_settings) in language_formatters_to_check {
721 if language_settings.prettier.allowed
722 && let Some(plugins) = prettier_plugins_for_language(&language_settings)
723 {
724 prettier_plugins_by_worktree
725 .entry(worktree)
726 .or_insert_with(HashSet::default)
727 .extend(plugins.iter().cloned());
728 }
729 }
730 for (worktree, prettier_plugins) in prettier_plugins_by_worktree {
731 self.install_default_prettier(
732 worktree,
733 prettier_plugins.into_iter().map(Arc::from),
734 cx,
735 );
736 }
737 }
738}
739
740pub fn prettier_plugins_for_language(
741 language_settings: &LanguageSettings,
742) -> Option<&HashSet<String>> {
743 let formatters = language_settings.formatter.as_ref();
744 if formatters.contains(&Formatter::Prettier) || formatters.contains(&Formatter::Auto) {
745 return Some(&language_settings.prettier.plugins);
746 }
747 None
748}
749
750pub(super) async fn format_with_prettier(
751 prettier_store: &WeakEntity<PrettierStore>,
752 buffer: &Entity<Buffer>,
753 cx: &mut AsyncApp,
754) -> Option<Result<language::Diff>> {
755 let prettier_instance = prettier_store
756 .update(cx, |prettier_store, cx| {
757 prettier_store.prettier_instance_for_buffer(buffer, cx)
758 })
759 .ok()?
760 .await;
761
762 let ignore_dir = prettier_store
763 .update(cx, |prettier_store, cx| {
764 prettier_store.prettier_ignore_for_buffer(buffer, cx)
765 })
766 .ok()?
767 .await;
768
769 let (prettier_path, prettier_task) = prettier_instance?;
770
771 let prettier_description = match prettier_path.as_ref() {
772 Some(path) => format!("prettier at {path:?}"),
773 None => "default prettier instance".to_string(),
774 };
775
776 match prettier_task.await {
777 Ok(prettier) => {
778 let buffer_path = buffer
779 .update(cx, |buffer, cx| {
780 File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
781 })
782 .ok()
783 .flatten();
784
785 let format_result = prettier
786 .format(buffer, buffer_path, ignore_dir, cx)
787 .await
788 .with_context(|| format!("{} failed to format buffer", prettier_description));
789
790 Some(format_result)
791 }
792 Err(error) => {
793 prettier_store
794 .update(cx, |project, _| {
795 let instance_to_update = match prettier_path {
796 Some(prettier_path) => project.prettier_instances.get_mut(&prettier_path),
797 None => match &mut project.default_prettier.prettier {
798 PrettierInstallation::NotInstalled { .. } => None,
799 PrettierInstallation::Installed(instance) => Some(instance),
800 },
801 };
802
803 if let Some(instance) = instance_to_update {
804 instance.attempt += 1;
805 instance.prettier = None;
806 }
807 })
808 .log_err();
809
810 Some(Err(anyhow!(
811 "{prettier_description} failed to spawn: {error:#}"
812 )))
813 }
814 }
815}
816
817#[derive(Debug)]
818pub struct DefaultPrettier {
819 prettier: PrettierInstallation,
820 installed_plugins: HashSet<Arc<str>>,
821}
822
823#[derive(Debug)]
824pub enum PrettierInstallation {
825 NotInstalled {
826 attempts: usize,
827 installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
828 not_installed_plugins: HashSet<Arc<str>>,
829 },
830 Installed(PrettierInstance),
831}
832
833pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
834
835#[derive(Debug, Clone)]
836pub struct PrettierInstance {
837 attempt: usize,
838 prettier: Option<PrettierTask>,
839}
840
841impl Default for DefaultPrettier {
842 fn default() -> Self {
843 Self {
844 prettier: PrettierInstallation::NotInstalled {
845 attempts: 0,
846 installation_task: None,
847 not_installed_plugins: HashSet::default(),
848 },
849 installed_plugins: HashSet::default(),
850 }
851 }
852}
853
854impl DefaultPrettier {
855 pub fn instance(&self) -> Option<&PrettierInstance> {
856 if let PrettierInstallation::Installed(instance) = &self.prettier {
857 Some(instance)
858 } else {
859 None
860 }
861 }
862
863 pub fn prettier_task(
864 &mut self,
865 node: &NodeRuntime,
866 worktree_id: Option<WorktreeId>,
867 cx: &mut Context<PrettierStore>,
868 ) -> Option<Task<anyhow::Result<PrettierTask>>> {
869 match &mut self.prettier {
870 PrettierInstallation::NotInstalled { .. } => Some(
871 PrettierStore::start_default_prettier(node.clone(), worktree_id, cx),
872 ),
873 PrettierInstallation::Installed(existing_instance) => {
874 existing_instance.prettier_task(node, None, worktree_id, cx)
875 }
876 }
877 }
878}
879
880impl PrettierInstance {
881 pub fn prettier_task(
882 &mut self,
883 node: &NodeRuntime,
884 prettier_dir: Option<&Path>,
885 worktree_id: Option<WorktreeId>,
886 cx: &mut Context<PrettierStore>,
887 ) -> Option<Task<anyhow::Result<PrettierTask>>> {
888 if self.attempt > prettier::FAIL_THRESHOLD {
889 match prettier_dir {
890 Some(prettier_dir) => log::warn!(
891 "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
892 ),
893 None => log::warn!("Default prettier exceeded launch threshold, not starting"),
894 }
895 return None;
896 }
897 Some(match &self.prettier {
898 Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
899 None => match prettier_dir {
900 Some(prettier_dir) => {
901 let new_task = PrettierStore::start_prettier(
902 node.clone(),
903 prettier_dir.to_path_buf(),
904 worktree_id,
905 cx,
906 );
907 self.attempt += 1;
908 self.prettier = Some(new_task.clone());
909 Task::ready(Ok(new_task))
910 }
911 None => {
912 self.attempt += 1;
913 let node = node.clone();
914 cx.spawn(async move |prettier_store, cx| {
915 prettier_store
916 .update(cx, |_, cx| {
917 PrettierStore::start_default_prettier(node, worktree_id, cx)
918 })?
919 .await
920 })
921 }
922 },
923 })
924 }
925
926 pub async fn server(&self) -> Option<Arc<LanguageServer>> {
927 self.prettier.clone()?.await.ok()?.server().cloned()
928 }
929}
930
931async fn install_prettier_packages(
932 fs: &dyn Fs,
933 plugins_to_install: HashSet<Arc<str>>,
934 node: NodeRuntime,
935) -> anyhow::Result<()> {
936 let packages_to_versions = future::try_join_all(
937 plugins_to_install
938 .iter()
939 .chain(Some(&"prettier".into()))
940 .map(|package_name| async {
941 let returned_package_name = package_name.to_string();
942 let latest_version = node
943 .npm_package_latest_version(package_name)
944 .await
945 .with_context(|| {
946 format!("fetching latest npm version for package {returned_package_name}")
947 })?;
948 anyhow::Ok((returned_package_name, latest_version))
949 }),
950 )
951 .await
952 .context("fetching latest npm versions")?;
953
954 let default_prettier_dir = default_prettier_dir().as_path();
955 match fs.metadata(default_prettier_dir).await.with_context(|| {
956 format!("fetching FS metadata for default prettier dir {default_prettier_dir:?}")
957 })? {
958 Some(prettier_dir_metadata) => anyhow::ensure!(
959 prettier_dir_metadata.is_dir,
960 "default prettier dir {default_prettier_dir:?} is not a directory"
961 ),
962 None => fs
963 .create_dir(default_prettier_dir)
964 .await
965 .with_context(|| format!("creating default prettier dir {default_prettier_dir:?}"))?,
966 }
967
968 log::info!("Installing default prettier and plugins: {packages_to_versions:?}");
969 let borrowed_packages = packages_to_versions
970 .iter()
971 .map(|(package, version)| (package.as_str(), version.as_str()))
972 .collect::<Vec<_>>();
973 node.npm_install_packages(default_prettier_dir, &borrowed_packages)
974 .await
975 .context("fetching formatter packages")?;
976 anyhow::Ok(())
977}
978
979async fn save_prettier_server_file(
980 fs: &dyn Fs,
981 executor: &BackgroundExecutor,
982) -> anyhow::Result<()> {
983 let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
984 fs.save(
985 &prettier_wrapper_path,
986 &text::Rope::from_str(prettier::PRETTIER_SERVER_JS, executor),
987 text::LineEnding::Unix,
988 )
989 .await
990 .with_context(|| {
991 format!(
992 "writing {} file at {prettier_wrapper_path:?}",
993 prettier::PRETTIER_SERVER_FILE
994 )
995 })?;
996 Ok(())
997}
998
999async fn should_write_prettier_server_file(fs: &dyn Fs) -> bool {
1000 let prettier_wrapper_path = default_prettier_dir().join(prettier::PRETTIER_SERVER_FILE);
1001 if !fs.is_file(&prettier_wrapper_path).await {
1002 return true;
1003 }
1004 let Ok(prettier_server_file_contents) = fs.load(&prettier_wrapper_path).await else {
1005 return true;
1006 };
1007 prettier_server_file_contents != prettier::PRETTIER_SERVER_JS
1008}