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