Do not attempt to run default prettier if it's not installed yet

Kirill Bulatov created

Change summary

crates/project/src/prettier_support.rs | 180 ++++++++++++++-------------
1 file changed, 96 insertions(+), 84 deletions(-)

Detailed changes

crates/project/src/prettier_support.rs 🔗

@@ -86,7 +86,7 @@ pub struct DefaultPrettier {
 pub enum PrettierInstallation {
     NotInstalled {
         attempts: usize,
-        installation_process: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
+        installation_task: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
     },
     Installed(PrettierInstance),
 }
@@ -104,7 +104,7 @@ impl Default for DefaultPrettier {
         Self {
             prettier: PrettierInstallation::NotInstalled {
                 attempts: 0,
-                installation_process: None,
+                installation_task: None,
             },
             installed_plugins: HashSet::default(),
         }
@@ -128,9 +128,7 @@ impl DefaultPrettier {
     ) -> Option<Task<anyhow::Result<PrettierTask>>> {
         match &mut self.prettier {
             PrettierInstallation::NotInstalled { .. } => {
-                // `start_default_prettier` will start the installation process if it's not already running and wait for it to finish
-                let new_task = start_default_prettier(Arc::clone(node), worktree_id, cx);
-                Some(cx.spawn(|_, _| async move { new_task.await }))
+                Some(start_default_prettier(Arc::clone(node), worktree_id, cx))
             }
             PrettierInstallation::Installed(existing_instance) => {
                 existing_instance.prettier_task(node, None, worktree_id, cx)
@@ -193,23 +191,31 @@ fn start_default_prettier(
 ) -> Task<anyhow::Result<PrettierTask>> {
     cx.spawn(|project, mut cx| async move {
         loop {
-            let installation_process = project.update(&mut cx, |project, _| {
+            let installation_task = project.update(&mut cx, |project, _| {
                 match &project.default_prettier.prettier {
                     PrettierInstallation::NotInstalled {
-                        installation_process,
-                        ..
-                    } => ControlFlow::Continue(installation_process.clone()),
+                        installation_task, ..
+                    } => ControlFlow::Continue(installation_task.clone()),
                     PrettierInstallation::Installed(default_prettier) => {
                         ControlFlow::Break(default_prettier.clone())
                     }
                 }
             });
-            match installation_process {
+            match installation_task {
                 ControlFlow::Continue(None) => {
                     anyhow::bail!("Default prettier is not installed and cannot be started")
                 }
-                ControlFlow::Continue(Some(installation_process)) => {
-                    if let Err(e) = installation_process.await {
+                ControlFlow::Continue(Some(installation_task)) => {
+                    log::info!("Waiting for default prettier to install");
+                    if let Err(e) = installation_task.await {
+                        project.update(&mut cx, |project, _| {
+                            if let PrettierInstallation::NotInstalled {
+                                installation_task, ..
+                            } = &mut project.default_prettier.prettier
+                            {
+                                *installation_task = None;
+                            }
+                        });
                         anyhow::bail!(
                             "Cannot start default prettier due to its installation failure: {e:#}"
                         );
@@ -254,6 +260,7 @@ fn start_prettier(
     cx: &mut ModelContext<'_, Project>,
 ) -> PrettierTask {
     cx.spawn(|project, mut cx| async move {
+        log::info!("Starting prettier at path {prettier_dir:?}");
         let new_server_id = project.update(&mut cx, |project, _| {
             project.languages.next_language_server_id()
         });
@@ -319,10 +326,9 @@ fn register_new_prettier(
     }
 }
 
-async fn install_default_prettier(
+async fn install_prettier_packages(
     plugins_to_install: HashSet<&'static str>,
     node: Arc<dyn NodeRuntime>,
-    fs: Arc<dyn Fs>,
 ) -> anyhow::Result<()> {
     let packages_to_versions =
         future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
@@ -563,23 +569,24 @@ impl Project {
         }
     }
 
-    #[cfg(any(test, feature = "test-support"))]
-    pub fn install_default_prettier(
-        &mut self,
-        _worktree: Option<WorktreeId>,
-        _new_language: &Language,
-        language_settings: &LanguageSettings,
-        _cx: &mut ModelContext<Self>,
-    ) {
-        // suppress unused code warnings
-        match &language_settings.formatter {
-            Formatter::Prettier { .. } | Formatter::Auto => {}
-            Formatter::LanguageServer | Formatter::External { .. } => return,
-        };
-        let _ = &self.default_prettier.installed_plugins;
-    }
-
-    #[cfg(not(any(test, feature = "test-support")))]
+    // TODO kb uncomment
+    // #[cfg(any(test, feature = "test-support"))]
+    // pub fn install_default_prettier(
+    //     &mut self,
+    //     _worktree: Option<WorktreeId>,
+    //     _new_language: &Language,
+    //     language_settings: &LanguageSettings,
+    //     _cx: &mut ModelContext<Self>,
+    // ) {
+    //     // suppress unused code warnings
+    //     match &language_settings.formatter {
+    //         Formatter::Prettier { .. } | Formatter::Auto => {}
+    //         Formatter::LanguageServer | Formatter::External { .. } => return,
+    //     };
+    //     let _ = &self.default_prettier.installed_plugins;
+    // }
+
+    // #[cfg(not(any(test, feature = "test-support")))]
     pub fn install_default_prettier(
         &mut self,
         worktree: Option<WorktreeId>,
@@ -632,13 +639,13 @@ impl Project {
         plugins_to_install
             .retain(|plugin| !self.default_prettier.installed_plugins.contains(plugin));
         let mut installation_attempts = 0;
-        let previous_installation_process = match &self.default_prettier.prettier {
+        let previous_installation_task = match &self.default_prettier.prettier {
             PrettierInstallation::NotInstalled {
-                installation_process,
+                installation_task,
                 attempts,
             } => {
                 installation_attempts = *attempts;
-                installation_process.clone()
+                installation_task.clone()
             }
             PrettierInstallation::Installed { .. } => {
                 if plugins_to_install.is_empty() {
@@ -656,61 +663,66 @@ impl Project {
         }
 
         let fs = Arc::clone(&self.fs);
-        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
-            attempts: installation_attempts + 1,
-            installation_process: Some(
-                cx.spawn(|this, mut cx| async move {
-                    match locate_prettier_installation
-                        .await
-                        .context("locate prettier installation")
-                        .map_err(Arc::new)?
-                    {
-                        ControlFlow::Break(()) => return Ok(()),
-                        ControlFlow::Continue(Some(_non_default_prettier)) => {
-                            save_prettier_server_file(fs.as_ref()).await?;
-                            return Ok(());
-                        }
-                        ControlFlow::Continue(None) => {
-                            let mut needs_install = match previous_installation_process {
-                                Some(previous_installation_process) => {
-                                    previous_installation_process.await.is_err()
+        let new_installation_task = cx
+            .spawn(|this, mut cx| async move {
+                match locate_prettier_installation
+                    .await
+                    .context("locate prettier installation")
+                    .map_err(Arc::new)?
+                {
+                    ControlFlow::Break(()) => return Ok(()),
+                    ControlFlow::Continue(Some(_non_default_prettier)) => {
+                        save_prettier_server_file(fs.as_ref()).await?;
+                        return Ok(());
+                    }
+                    ControlFlow::Continue(None) => {
+                        let mut needs_install = match previous_installation_task {
+                            Some(previous_installation_task) => {
+                                match previous_installation_task.await {
+                                    Ok(()) => false,
+                                    Err(e) => {
+                                        log::error!("Failed to install default prettier: {e:#}");
+                                        true
+                                    }
                                 }
-                                None => true,
-                            };
+                            }
+                            None => true,
+                        };
+                        this.update(&mut cx, |this, _| {
+                            plugins_to_install.retain(|plugin| {
+                                !this.default_prettier.installed_plugins.contains(plugin)
+                            });
+                            needs_install |= !plugins_to_install.is_empty();
+                        });
+                        if needs_install {
+                            let installed_plugins = plugins_to_install.clone();
+                            cx.background()
+                                .spawn(async move {
+                                    save_prettier_server_file(fs.as_ref()).await?;
+                                    install_prettier_packages(plugins_to_install, node).await
+                                })
+                                .await
+                                .context("prettier & plugins install")
+                                .map_err(Arc::new)?;
                             this.update(&mut cx, |this, _| {
-                                plugins_to_install.retain(|plugin| {
-                                    !this.default_prettier.installed_plugins.contains(plugin)
-                                });
-                                needs_install |= !plugins_to_install.is_empty();
+                                this.default_prettier.prettier =
+                                    PrettierInstallation::Installed(PrettierInstance {
+                                        attempt: 0,
+                                        prettier: None,
+                                    });
+                                this.default_prettier
+                                    .installed_plugins
+                                    .extend(installed_plugins);
                             });
-                            if needs_install {
-                                let installed_plugins = plugins_to_install.clone();
-                                cx.background()
-                                    // TODO kb instead of always installing, try to start the existing installation first?
-                                    .spawn(async move {
-                                        save_prettier_server_file(fs.as_ref()).await?;
-                                        install_default_prettier(plugins_to_install, node, fs).await
-                                    })
-                                    .await
-                                    .context("prettier & plugins install")
-                                    .map_err(Arc::new)?;
-                                this.update(&mut cx, |this, _| {
-                                    this.default_prettier.prettier =
-                                        PrettierInstallation::Installed(PrettierInstance {
-                                            attempt: 0,
-                                            prettier: None,
-                                        });
-                                    this.default_prettier
-                                        .installed_plugins
-                                        .extend(installed_plugins);
-                                });
-                            }
                         }
                     }
-                    Ok(())
-                })
-                .shared(),
-            ),
+                }
+                Ok(())
+            })
+            .shared();
+        self.default_prettier.prettier = PrettierInstallation::NotInstalled {
+            attempts: installation_attempts + 1,
+            installation_task: Some(new_installation_task),
         };
     }
 }