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