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