prettier_support.rs

  1use std::{
  2    ops::ControlFlow,
  3    path::{Path, PathBuf},
  4    sync::Arc,
  5};
  6
  7use anyhow::Context;
  8use collections::HashSet;
  9use fs::Fs;
 10use futures::{
 11    future::{self, Shared},
 12    FutureExt,
 13};
 14use gpui::{AsyncAppContext, ModelContext, ModelHandle, Task};
 15use language::{
 16    language_settings::{Formatter, LanguageSettings},
 17    Buffer, Language, LanguageServerName, LocalFile,
 18};
 19use lsp::LanguageServerId;
 20use node_runtime::NodeRuntime;
 21use prettier::Prettier;
 22use util::{paths::DEFAULT_PRETTIER_DIR, ResultExt, TryFutureExt};
 23
 24use crate::{
 25    Event, File, FormatOperation, PathChange, Project, ProjectEntryId, Worktree, WorktreeId,
 26};
 27
 28pub(super) async fn format_with_prettier(
 29    project: &ModelHandle<Project>,
 30    buffer: &ModelHandle<Buffer>,
 31    cx: &mut AsyncAppContext,
 32) -> Option<FormatOperation> {
 33    if let Some((prettier_path, prettier_task)) = project
 34        .update(cx, |project, cx| {
 35            project.prettier_instance_for_buffer(buffer, cx)
 36        })
 37        .await
 38    {
 39        match prettier_task.await {
 40            Ok(prettier) => {
 41                let buffer_path = buffer.update(cx, |buffer, cx| {
 42                    File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
 43                });
 44                match prettier.format(buffer, buffer_path, cx).await {
 45                    Ok(new_diff) => return Some(FormatOperation::Prettier(new_diff)),
 46                    Err(e) => {
 47                        log::error!(
 48                            "Prettier instance from {prettier_path:?} failed to format a buffer: {e:#}"
 49                        );
 50                    }
 51                }
 52            }
 53            Err(e) => project.update(cx, |project, _| {
 54                let instance_to_update = match prettier_path {
 55                    Some(prettier_path) => {
 56                        log::error!(
 57                            "Prettier instance from path {prettier_path:?} failed to spawn: {e:#}"
 58                        );
 59                        project.prettier_instances.get_mut(&prettier_path)
 60                    }
 61                    None => {
 62                        log::error!("Default prettier instance failed to spawn: {e:#}");
 63                        match &mut project.default_prettier.prettier {
 64                            PrettierInstallation::NotInstalled { .. } => None,
 65                            PrettierInstallation::Installed(instance) => Some(instance),
 66                        }
 67                    }
 68                };
 69
 70                if let Some(instance) = instance_to_update {
 71                    instance.attempt += 1;
 72                    instance.prettier = None;
 73                }
 74            }),
 75        }
 76    }
 77
 78    None
 79}
 80
 81pub struct DefaultPrettier {
 82    prettier: PrettierInstallation,
 83    installed_plugins: HashSet<&'static str>,
 84}
 85
 86pub enum PrettierInstallation {
 87    NotInstalled {
 88        attempts: usize,
 89        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
 90    },
 91    Installed(PrettierInstance),
 92}
 93
 94pub type PrettierTask = Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>;
 95
 96#[derive(Clone)]
 97pub struct PrettierInstance {
 98    attempt: usize,
 99    prettier: Option<PrettierTask>,
100}
101
102impl Default for DefaultPrettier {
103    fn default() -> Self {
104        Self {
105            prettier: PrettierInstallation::NotInstalled {
106                attempts: 0,
107                installation_task: None,
108            },
109            installed_plugins: HashSet::default(),
110        }
111    }
112}
113
114impl DefaultPrettier {
115    pub fn instance(&self) -> Option<&PrettierInstance> {
116        if let PrettierInstallation::Installed(instance) = &self.prettier {
117            Some(instance)
118        } else {
119            None
120        }
121    }
122
123    pub fn prettier_task(
124        &mut self,
125        node: &Arc<dyn NodeRuntime>,
126        worktree_id: Option<WorktreeId>,
127        cx: &mut ModelContext<'_, Project>,
128    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
129        match &mut self.prettier {
130            PrettierInstallation::NotInstalled { .. } => {
131                Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
132            }
133            PrettierInstallation::Installed(existing_instance) => {
134                existing_instance.prettier_task(node, None, worktree_id, cx)
135            }
136        }
137    }
138}
139
140impl PrettierInstance {
141    pub fn prettier_task(
142        &mut self,
143        node: &Arc<dyn NodeRuntime>,
144        prettier_dir: Option<&Path>,
145        worktree_id: Option<WorktreeId>,
146        cx: &mut ModelContext<'_, Project>,
147    ) -> Option<Task<anyhow::Result<PrettierTask>>> {
148        if self.attempt > prettier::LAUNCH_THRESHOLD {
149            match prettier_dir {
150                Some(prettier_dir) => log::warn!(
151                    "Prettier from path {prettier_dir:?} exceeded launch threshold, not starting"
152                ),
153                None => log::warn!("Default prettier exceeded launch threshold, not starting"),
154            }
155            return None;
156        }
157        Some(match &self.prettier {
158            Some(prettier_task) => Task::ready(Ok(prettier_task.clone())),
159            None => match prettier_dir {
160                Some(prettier_dir) => {
161                    let new_task = start_prettier(
162                        Arc::clone(node),
163                        prettier_dir.to_path_buf(),
164                        worktree_id,
165                        cx,
166                    );
167                    self.attempt += 1;
168                    self.prettier = Some(new_task.clone());
169                    Task::ready(Ok(new_task))
170                }
171                None => {
172                    self.attempt += 1;
173                    let node = Arc::clone(node);
174                    cx.spawn(|project, mut cx| async move {
175                        project
176                            .update(&mut cx, |_, cx| {
177                                start_default_prettier(node, worktree_id, cx)
178                            })
179                            .await
180                    })
181                }
182            },
183        })
184    }
185}
186
187fn start_default_prettier(
188    node: Arc<dyn NodeRuntime>,
189    worktree_id: Option<WorktreeId>,
190    cx: &mut ModelContext<'_, Project>,
191) -> Task<anyhow::Result<PrettierTask>> {
192    cx.spawn(|project, mut cx| async move {
193        loop {
194            let installation_task = project.update(&mut cx, |project, _| {
195                match &project.default_prettier.prettier {
196                    PrettierInstallation::NotInstalled {
197                        installation_task, ..
198                    } => ControlFlow::Continue(installation_task.clone()),
199                    PrettierInstallation::Installed(default_prettier) => {
200                        ControlFlow::Break(default_prettier.clone())
201                    }
202                }
203            });
204            match installation_task {
205                ControlFlow::Continue(None) => {
206                    anyhow::bail!("Default prettier is not installed and cannot be started")
207                }
208                ControlFlow::Continue(Some(installation_task)) => {
209                    log::info!("Waiting for default prettier to install");
210                    if let Err(e) = installation_task.await {
211                        project.update(&mut cx, |project, _| {
212                            if let PrettierInstallation::NotInstalled {
213                                installation_task, ..
214                            } = &mut project.default_prettier.prettier
215                            {
216                                *installation_task = None;
217                            }
218                        });
219                        anyhow::bail!(
220                            "Cannot start default prettier due to its installation failure: {e:#}"
221                        );
222                    }
223                    let new_default_prettier = project.update(&mut cx, |project, cx| {
224                        let new_default_prettier =
225                            start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
226                        project.default_prettier.prettier =
227                            PrettierInstallation::Installed(PrettierInstance {
228                                attempt: 0,
229                                prettier: Some(new_default_prettier.clone()),
230                            });
231                        new_default_prettier
232                    });
233                    return Ok(new_default_prettier);
234                }
235                ControlFlow::Break(instance) => match instance.prettier {
236                    Some(instance) => return Ok(instance),
237                    None => {
238                        let new_default_prettier = project.update(&mut cx, |project, cx| {
239                            let new_default_prettier =
240                                start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
241                            project.default_prettier.prettier =
242                                PrettierInstallation::Installed(PrettierInstance {
243                                    attempt: instance.attempt + 1,
244                                    prettier: Some(new_default_prettier.clone()),
245                                });
246                            new_default_prettier
247                        });
248                        return Ok(new_default_prettier);
249                    }
250                },
251            }
252        }
253    })
254}
255
256fn start_prettier(
257    node: Arc<dyn NodeRuntime>,
258    prettier_dir: PathBuf,
259    worktree_id: Option<WorktreeId>,
260    cx: &mut ModelContext<'_, Project>,
261) -> PrettierTask {
262    cx.spawn(|project, mut cx| async move {
263        log::info!("Starting prettier at path {prettier_dir:?}");
264        let new_server_id = project.update(&mut cx, |project, _| {
265            project.languages.next_language_server_id()
266        });
267
268        let new_prettier = Prettier::start(new_server_id, prettier_dir, node, cx.clone())
269            .await
270            .context("default prettier spawn")
271            .map(Arc::new)
272            .map_err(Arc::new)?;
273        register_new_prettier(&project, &new_prettier, worktree_id, new_server_id, &mut cx);
274        Ok(new_prettier)
275    })
276    .shared()
277}
278
279fn register_new_prettier(
280    project: &ModelHandle<Project>,
281    prettier: &Prettier,
282    worktree_id: Option<WorktreeId>,
283    new_server_id: LanguageServerId,
284    cx: &mut AsyncAppContext,
285) {
286    let prettier_dir = prettier.prettier_dir();
287    let is_default = prettier.is_default();
288    if is_default {
289        log::info!("Started default prettier in {prettier_dir:?}");
290    } else {
291        log::info!("Started prettier in {prettier_dir:?}");
292    }
293    if let Some(prettier_server) = prettier.server() {
294        project.update(cx, |project, cx| {
295            let name = if is_default {
296                LanguageServerName(Arc::from("prettier (default)"))
297            } else {
298                let worktree_path = worktree_id
299                    .and_then(|id| project.worktree_for_id(id, cx))
300                    .map(|worktree| worktree.update(cx, |worktree, _| worktree.abs_path()));
301                let name = match worktree_path {
302                    Some(worktree_path) => {
303                        if prettier_dir == worktree_path.as_ref() {
304                            let name = prettier_dir
305                                .file_name()
306                                .and_then(|name| name.to_str())
307                                .unwrap_or_default();
308                            format!("prettier ({name})")
309                        } else {
310                            let dir_to_display = prettier_dir
311                                .strip_prefix(worktree_path.as_ref())
312                                .ok()
313                                .unwrap_or(prettier_dir);
314                            format!("prettier ({})", dir_to_display.display())
315                        }
316                    }
317                    None => format!("prettier ({})", prettier_dir.display()),
318                };
319                LanguageServerName(Arc::from(name))
320            };
321            project
322                .supplementary_language_servers
323                .insert(new_server_id, (name, Arc::clone(prettier_server)));
324            cx.emit(Event::LanguageServerAdded(new_server_id));
325        });
326    }
327}
328
329async fn install_prettier_packages(
330    plugins_to_install: HashSet<&'static str>,
331    node: Arc<dyn NodeRuntime>,
332) -> anyhow::Result<()> {
333    let packages_to_versions =
334        future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
335            |package_name| async {
336                let returned_package_name = package_name.to_string();
337                let latest_version = node
338                    .npm_package_latest_version(package_name)
339                    .await
340                    .with_context(|| {
341                        format!("fetching latest npm version for package {returned_package_name}")
342                    })?;
343                anyhow::Ok((returned_package_name, latest_version))
344            },
345        ))
346        .await
347        .context("fetching latest npm versions")?;
348
349    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
350    let borrowed_packages = packages_to_versions
351        .iter()
352        .map(|(package, version)| (package.as_str(), version.as_str()))
353        .collect::<Vec<_>>();
354    node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages)
355        .await
356        .context("fetching formatter packages")?;
357    anyhow::Ok(())
358}
359
360async fn save_prettier_server_file(fs: &dyn Fs) -> Result<(), anyhow::Error> {
361    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
362    fs.save(
363        &prettier_wrapper_path,
364        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
365        text::LineEnding::Unix,
366    )
367    .await
368    .with_context(|| {
369        format!(
370            "writing {} file at {prettier_wrapper_path:?}",
371            prettier::PRETTIER_SERVER_FILE
372        )
373    })?;
374    Ok(())
375}
376
377impl Project {
378    pub fn update_prettier_settings(
379        &self,
380        worktree: &ModelHandle<Worktree>,
381        changes: &[(Arc<Path>, ProjectEntryId, PathChange)],
382        cx: &mut ModelContext<'_, Project>,
383    ) {
384        let prettier_config_files = Prettier::CONFIG_FILE_NAMES
385            .iter()
386            .map(Path::new)
387            .collect::<HashSet<_>>();
388
389        let prettier_config_file_changed = changes
390            .iter()
391            .filter(|(_, _, change)| !matches!(change, PathChange::Loaded))
392            .filter(|(path, _, _)| {
393                !path
394                    .components()
395                    .any(|component| component.as_os_str().to_string_lossy() == "node_modules")
396            })
397            .find(|(path, _, _)| prettier_config_files.contains(path.as_ref()));
398        let current_worktree_id = worktree.read(cx).id();
399        if let Some((config_path, _, _)) = prettier_config_file_changed {
400            log::info!(
401                "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
402            );
403            let prettiers_to_reload =
404                self.prettiers_per_worktree
405                    .get(&current_worktree_id)
406                    .iter()
407                    .flat_map(|prettier_paths| prettier_paths.iter())
408                    .flatten()
409                    .filter_map(|prettier_path| {
410                        Some((
411                            current_worktree_id,
412                            Some(prettier_path.clone()),
413                            self.prettier_instances.get(prettier_path)?.clone(),
414                        ))
415                    })
416                    .chain(self.default_prettier.instance().map(|default_prettier| {
417                        (current_worktree_id, None, default_prettier.clone())
418                    }))
419                    .collect::<Vec<_>>();
420
421            cx.background()
422                .spawn(async move {
423                    let _: Vec<()> = future::join_all(prettiers_to_reload.into_iter().map(|(worktree_id, prettier_path, prettier_instance)| {
424                        async move {
425                            if let Some(instance) = prettier_instance.prettier {
426                                match instance.await {
427                                    Ok(prettier) => {
428                                        prettier.clear_cache().log_err().await;
429                                    },
430                                    Err(e) => {
431                                        match prettier_path {
432                                            Some(prettier_path) => log::error!(
433                                                "Failed to clear prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
434                                            ),
435                                            None => log::error!(
436                                                "Failed to clear default prettier cache for worktree {worktree_id:?} on prettier settings update: {e:#}"
437                                            ),
438                                        }
439                                    },
440                                }
441                            }
442                        }
443                    }))
444                    .await;
445                })
446                .detach();
447        }
448    }
449
450    fn prettier_instance_for_buffer(
451        &mut self,
452        buffer: &ModelHandle<Buffer>,
453        cx: &mut ModelContext<Self>,
454    ) -> Task<Option<(Option<PathBuf>, PrettierTask)>> {
455        let buffer = buffer.read(cx);
456        let buffer_file = buffer.file();
457        let Some(buffer_language) = buffer.language() else {
458            return Task::ready(None);
459        };
460        if buffer_language.prettier_parser_name().is_none() {
461            return Task::ready(None);
462        }
463
464        if self.is_local() {
465            let Some(node) = self.node.as_ref().map(Arc::clone) else {
466                return Task::ready(None);
467            };
468            match File::from_dyn(buffer_file).map(|file| (file.worktree_id(cx), file.abs_path(cx)))
469            {
470                Some((worktree_id, buffer_path)) => {
471                    let fs = Arc::clone(&self.fs);
472                    let installed_prettiers = self.prettier_instances.keys().cloned().collect();
473                    return cx.spawn(|project, mut cx| async move {
474                        match cx
475                            .background()
476                            .spawn(async move {
477                                Prettier::locate_prettier_installation(
478                                    fs.as_ref(),
479                                    &installed_prettiers,
480                                    &buffer_path,
481                                )
482                                .await
483                            })
484                            .await
485                        {
486                            Ok(ControlFlow::Break(())) => {
487                                return None;
488                            }
489                            Ok(ControlFlow::Continue(None)) => {
490                                let default_instance = project.update(&mut cx, |project, cx| {
491                                    project
492                                        .prettiers_per_worktree
493                                        .entry(worktree_id)
494                                        .or_default()
495                                        .insert(None);
496                                    project.default_prettier.prettier_task(
497                                        &node,
498                                        Some(worktree_id),
499                                        cx,
500                                    )
501                                });
502                                Some((None, default_instance?.log_err().await?))
503                            }
504                            Ok(ControlFlow::Continue(Some(prettier_dir))) => {
505                                project.update(&mut cx, |project, _| {
506                                    project
507                                        .prettiers_per_worktree
508                                        .entry(worktree_id)
509                                        .or_default()
510                                        .insert(Some(prettier_dir.clone()))
511                                });
512                                if let Some(prettier_task) =
513                                    project.update(&mut cx, |project, cx| {
514                                        project.prettier_instances.get_mut(&prettier_dir).map(
515                                            |existing_instance| {
516                                                existing_instance.prettier_task(
517                                                    &node,
518                                                    Some(&prettier_dir),
519                                                    Some(worktree_id),
520                                                    cx,
521                                                )
522                                            },
523                                        )
524                                    })
525                                {
526                                    log::debug!(
527                                        "Found already started prettier in {prettier_dir:?}"
528                                    );
529                                    return Some((
530                                        Some(prettier_dir),
531                                        prettier_task?.await.log_err()?,
532                                    ));
533                                }
534
535                                log::info!("Found prettier in {prettier_dir:?}, starting.");
536                                let new_prettier_task = project.update(&mut cx, |project, cx| {
537                                    let new_prettier_task = start_prettier(
538                                        node,
539                                        prettier_dir.clone(),
540                                        Some(worktree_id),
541                                        cx,
542                                    );
543                                    project.prettier_instances.insert(
544                                        prettier_dir.clone(),
545                                        PrettierInstance {
546                                            attempt: 0,
547                                            prettier: Some(new_prettier_task.clone()),
548                                        },
549                                    );
550                                    new_prettier_task
551                                });
552                                Some((Some(prettier_dir), new_prettier_task))
553                            }
554                            Err(e) => {
555                                log::error!("Failed to determine prettier path for buffer: {e:#}");
556                                return None;
557                            }
558                        }
559                    });
560                }
561                None => {
562                    let new_task = self.default_prettier.prettier_task(&node, None, cx);
563                    return cx
564                        .spawn(|_, _| async move { Some((None, new_task?.log_err().await?)) });
565                }
566            }
567        } else {
568            return Task::ready(None);
569        }
570    }
571
572    // TODO kb uncomment
573    // #[cfg(any(test, feature = "test-support"))]
574    // pub fn install_default_prettier(
575    //     &mut self,
576    //     _worktree: Option<WorktreeId>,
577    //     _new_language: &Language,
578    //     language_settings: &LanguageSettings,
579    //     _cx: &mut ModelContext<Self>,
580    // ) {
581    //     // suppress unused code warnings
582    //     match &language_settings.formatter {
583    //         Formatter::Prettier { .. } | Formatter::Auto => {}
584    //         Formatter::LanguageServer | Formatter::External { .. } => return,
585    //     };
586    //     let _ = &self.default_prettier.installed_plugins;
587    // }
588
589    // #[cfg(not(any(test, feature = "test-support")))]
590    pub fn install_default_prettier(
591        &mut self,
592        worktree: Option<WorktreeId>,
593        new_language: &Language,
594        language_settings: &LanguageSettings,
595        cx: &mut ModelContext<Self>,
596    ) {
597        match &language_settings.formatter {
598            Formatter::Prettier { .. } | Formatter::Auto => {}
599            Formatter::LanguageServer | Formatter::External { .. } => return,
600        };
601        let Some(node) = self.node.as_ref().cloned() else {
602            return;
603        };
604
605        let mut prettier_plugins = None;
606        if new_language.prettier_parser_name().is_some() {
607            prettier_plugins
608                .get_or_insert_with(|| HashSet::<&'static str>::default())
609                .extend(
610                    new_language
611                        .lsp_adapters()
612                        .iter()
613                        .flat_map(|adapter| adapter.prettier_plugins()),
614                )
615        }
616        let Some(prettier_plugins) = prettier_plugins else {
617            return;
618        };
619
620        let fs = Arc::clone(&self.fs);
621        let locate_prettier_installation = match worktree.and_then(|worktree_id| {
622            self.worktree_for_id(worktree_id, cx)
623                .map(|worktree| worktree.read(cx).abs_path())
624        }) {
625            Some(locate_from) => {
626                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
627                cx.background().spawn(async move {
628                    Prettier::locate_prettier_installation(
629                        fs.as_ref(),
630                        &installed_prettiers,
631                        locate_from.as_ref(),
632                    )
633                    .await
634                })
635            }
636            None => Task::ready(Ok(ControlFlow::Break(()))),
637        };
638        let mut plugins_to_install = prettier_plugins;
639        plugins_to_install
640            .retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
641        let mut installation_attempts = 0;
642        let previous_installation_task = match &self.default_prettier.prettier {
643            PrettierInstallation::NotInstalled {
644                installation_task,
645                attempts,
646            } => {
647                installation_attempts = *attempts;
648                installation_task.clone()
649            }
650            PrettierInstallation::Installed { .. } => {
651                if plugins_to_install.is_empty() {
652                    return;
653                }
654                None
655            }
656        };
657
658        if installation_attempts > prettier::LAUNCH_THRESHOLD {
659            log::warn!(
660                "Default prettier installation has failed {installation_attempts} times, not attempting again",
661            );
662            return;
663        }
664
665        let fs = Arc::clone(&self.fs);
666        let new_installation_task = cx
667            .spawn(|this, mut cx| async move {
668                match locate_prettier_installation
669                    .await
670                    .context("locate prettier installation")
671                    .map_err(Arc::new)?
672                {
673                    ControlFlow::Break(()) => return Ok(()),
674                    ControlFlow::Continue(_) => {
675                        let mut needs_install = match previous_installation_task {
676                            Some(previous_installation_task) => {
677                                match previous_installation_task.await {
678                                    Ok(()) => false,
679                                    Err(e) => {
680                                        log::error!("Failed to install default prettier: {e:#}");
681                                        true
682                                    }
683                                }
684                            }
685                            None => true,
686                        };
687                        this.update(&mut cx, |this, _| {
688                            plugins_to_install.retain(|plugin| {
689                                !this.default_prettier.installed_plugins.contains(plugin)
690                            });
691                            needs_install |= !plugins_to_install.is_empty();
692                        });
693                        if needs_install {
694                            let installed_plugins = plugins_to_install.clone();
695                            cx.background()
696                                .spawn(async move {
697                                    save_prettier_server_file(fs.as_ref()).await?;
698                                    install_prettier_packages(plugins_to_install, node).await
699                                })
700                                .await
701                                .context("prettier & plugins install")
702                                .map_err(Arc::new)?;
703                            this.update(&mut cx, |this, _| {
704                                this.default_prettier.prettier =
705                                    PrettierInstallation::Installed(PrettierInstance {
706                                        attempt: 0,
707                                        prettier: None,
708                                    });
709                                this.default_prettier
710                                    .installed_plugins
711                                    .extend(installed_plugins);
712                            });
713                        }
714                    }
715                }
716                Ok(())
717            })
718            .shared();
719        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
720            attempts: installation_attempts + 1,
721            installation_task: Some(new_installation_task),
722        };
723    }
724}