Properly order default prettier installations and startups

Kirill Bulatov created

Change summary

crates/project/src/project.rs       | 203 ++++++++++++++++++------------
crates/project/src/project_tests.rs |   2 
2 files changed, 122 insertions(+), 83 deletions(-)

Detailed changes

crates/project/src/project.rs 🔗

@@ -172,6 +172,7 @@ pub struct Project {
 
 struct DefaultPrettier {
     instance: Option<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>,
+    installation_process: Option<Shared<Task<Result<(), Arc<anyhow::Error>>>>>,
     #[cfg(not(any(test, feature = "test-support")))]
     installed_plugins: HashSet<&'static str>,
 }
@@ -924,8 +925,7 @@ impl Project {
         }
 
         for (worktree, language, settings) in language_formatters_to_check {
-            self.install_default_formatters(worktree, &language, &settings, cx)
-                .detach_and_log_err(cx);
+            self.install_default_formatters(worktree, &language, &settings, cx);
         }
 
         // Start all the newly-enabled language servers.
@@ -2681,8 +2681,7 @@ impl Project {
         let buffer_file = File::from_dyn(buffer_file.as_ref());
         let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
 
-        self.install_default_formatters(worktree, &new_language, &settings, cx)
-            .detach_and_log_err(cx);
+        self.install_default_formatters(worktree, &new_language, &settings, cx);
         if let Some(file) = buffer_file {
             let worktree = file.worktree.clone();
             if let Some(tree) = worktree.read(cx).as_local() {
@@ -8454,27 +8453,12 @@ impl Project {
                                 match started_default_prettier {
                                     Some(old_task) => return Some((None, old_task)),
                                     None => {
-                                        let new_task = project.update(&mut cx, |project, cx| {
-                                            let new_task = start_prettier(
-                                                node,
-                                                DEFAULT_PRETTIER_DIR.clone(),
-                                                Some(worktree_id),
-                                                cx,
-                                            );
-                                            project
-                                                .default_prettier
-                                                .get_or_insert_with(|| DefaultPrettier {
-                                                    instance: None,
-                                                    #[cfg(not(any(
-                                                        test,
-                                                        feature = "test-support"
-                                                    )))]
-                                                    installed_plugins: HashSet::default(),
-                                                })
-                                                .instance = Some(new_task.clone());
-                                            new_task
-                                        });
-                                        return Some((None, new_task));
+                                        let new_default_prettier = project
+                                            .update(&mut cx, |_, cx| {
+                                                start_default_prettier(node, Some(worktree_id), cx)
+                                            })
+                                            .await;
+                                        return Some((None, new_default_prettier));
                                     }
                                 }
                             }
@@ -8532,16 +8516,8 @@ impl Project {
                     match started_default_prettier {
                         Some(old_task) => return Task::ready(Some((None, old_task))),
                         None => {
-                            let new_task =
-                                start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), None, cx);
-                            self.default_prettier
-                                .get_or_insert_with(|| DefaultPrettier {
-                                    instance: None,
-                                    #[cfg(not(any(test, feature = "test-support")))]
-                                    installed_plugins: HashSet::default(),
-                                })
-                                .instance = Some(new_task.clone());
-                            return Task::ready(Some((None, new_task)));
+                            let new_task = start_default_prettier(node, None, cx);
+                            return cx.spawn(|_, _| async move { Some((None, new_task.await)) });
                         }
                     }
                 }
@@ -8563,8 +8539,7 @@ impl Project {
         _new_language: &Language,
         _language_settings: &LanguageSettings,
         _cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
-        return Task::ready(Ok(()));
+    ) {
     }
 
     #[cfg(not(any(test, feature = "test-support")))]
@@ -8574,13 +8549,13 @@ impl Project {
         new_language: &Language,
         language_settings: &LanguageSettings,
         cx: &mut ModelContext<Self>,
-    ) -> Task<anyhow::Result<()>> {
+    ) {
         match &language_settings.formatter {
             Formatter::Prettier { .. } | Formatter::Auto => {}
-            Formatter::LanguageServer | Formatter::External { .. } => return Task::ready(Ok(())),
+            Formatter::LanguageServer | Formatter::External { .. } => return,
         };
         let Some(node) = self.node.as_ref().cloned() else {
-            return Task::ready(Ok(()));
+            return;
         };
 
         let mut prettier_plugins = None;
@@ -8595,7 +8570,7 @@ impl Project {
                 )
         }
         let Some(prettier_plugins) = prettier_plugins else {
-            return Task::ready(Ok(()));
+            return;
         };
 
         let fs = Arc::clone(&self.fs);
@@ -8622,58 +8597,122 @@ impl Project {
                 plugins_to_install
                     .retain(|plugin| !default_prettier.installed_plugins.contains(plugin));
                 if plugins_to_install.is_empty() {
-                    return Task::ready(Ok(()));
+                    return;
                 }
-                default_prettier.instance.clone()
+                default_prettier.installation_process.clone()
             } else {
                 None
             };
         let fs = Arc::clone(&self.fs);
-        cx.spawn(|this, mut cx| async move {
-            match locate_prettier_installation
-                .await
-                .context("locate prettier installation")?
-            {
-                Some(_non_default_prettier) => return Ok(()),
-                None => {
-                    let mut needs_restart = match previous_installation_process {
-                        Some(previous_installation_process) => {
-                            previous_installation_process.await.is_err()
-                        }
-                        None => true,
-                    };
-                    this.update(&mut cx, |this, _| {
-                        if let Some(default_prettier) = &mut this.default_prettier {
-                            plugins_to_install.retain(|plugin| {
-                                !default_prettier.installed_plugins.contains(plugin)
-                            });
-                            needs_restart |= !plugins_to_install.is_empty();
-                        }
-                    });
-                    if needs_restart {
-                        let installed_plugins = plugins_to_install.clone();
-                        cx.background()
-                            .spawn(async move {
-                                install_default_prettier(plugins_to_install, node, fs).await
-                            })
-                            .await
-                            .context("prettier & plugins install")?;
+        let default_prettier = self
+            .default_prettier
+            .get_or_insert_with(|| DefaultPrettier {
+                instance: None,
+                installation_process: None,
+                installed_plugins: HashSet::default(),
+            });
+        default_prettier.installation_process = Some(
+            cx.spawn(|this, mut cx| async move {
+                match locate_prettier_installation
+                    .await
+                    .context("locate prettier installation")
+                    .map_err(Arc::new)?
+                {
+                    Some(_non_default_prettier) => return Ok(()),
+                    None => {
+                        let mut needs_install = match previous_installation_process {
+                            Some(previous_installation_process) => {
+                                previous_installation_process.await.is_err()
+                            }
+                            None => true,
+                        };
                         this.update(&mut cx, |this, _| {
-                            let default_prettier =
-                                this.default_prettier
-                                    .get_or_insert_with(|| DefaultPrettier {
-                                        instance: None,
-                                        installed_plugins: HashSet::default(),
-                                    });
-                            default_prettier.instance = None;
-                            default_prettier.installed_plugins.extend(installed_plugins);
+                            if let Some(default_prettier) = &mut this.default_prettier {
+                                plugins_to_install.retain(|plugin| {
+                                    !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 {
+                                    install_default_prettier(plugins_to_install, node, fs).await
+                                })
+                                .await
+                                .context("prettier & plugins install")
+                                .map_err(Arc::new)?;
+                            this.update(&mut cx, |this, _| {
+                                let default_prettier =
+                                    this.default_prettier
+                                        .get_or_insert_with(|| DefaultPrettier {
+                                            instance: None,
+                                            installation_process: Some(
+                                                Task::ready(Ok(())).shared(),
+                                            ),
+                                            installed_plugins: HashSet::default(),
+                                        });
+                                default_prettier.instance = None;
+                                default_prettier.installed_plugins.extend(installed_plugins);
+                            });
+                        }
                     }
                 }
+                Ok(())
+            })
+            .shared(),
+        );
+    }
+}
+
+fn start_default_prettier(
+    node: Arc<dyn NodeRuntime>,
+    worktree_id: Option<WorktreeId>,
+    cx: &mut ModelContext<'_, Project>,
+) -> Task<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>> {
+    cx.spawn(|project, mut cx| async move {
+        loop {
+            let default_prettier_installing = project.update(&mut cx, |project, _| {
+                project
+                    .default_prettier
+                    .as_ref()
+                    .and_then(|default_prettier| default_prettier.installation_process.clone())
+            });
+            match default_prettier_installing {
+                Some(installation_task) => {
+                    if installation_task.await.is_ok() {
+                        break;
+                    }
+                }
+                None => break,
+            }
+        }
+
+        project.update(&mut cx, |project, cx| {
+            match project
+                .default_prettier
+                .as_mut()
+                .and_then(|default_prettier| default_prettier.instance.as_mut())
+            {
+                Some(default_prettier) => default_prettier.clone(),
+                None => {
+                    let new_default_prettier =
+                        start_prettier(node, DEFAULT_PRETTIER_DIR.clone(), worktree_id, cx);
+                    project
+                        .default_prettier
+                        .get_or_insert_with(|| DefaultPrettier {
+                            instance: None,
+                            installation_process: None,
+                            #[cfg(not(any(test, feature = "test-support")))]
+                            installed_plugins: HashSet::default(),
+                        })
+                        .instance = Some(new_default_prettier.clone());
+                    new_default_prettier
+                }
             }
-            Ok(())
         })
-    }
+    })
 }
 
 fn start_prettier(

crates/project/src/project_tests.rs 🔗

@@ -13,7 +13,7 @@ use pretty_assertions::assert_eq;
 use serde_json::json;
 use std::{cell::RefCell, os::unix, rc::Rc, task::Poll};
 use unindent::Unindent as _;
-use util::{assert_set_eq, test::temp_tree, paths::PathMatcher};
+use util::{assert_set_eq, paths::PathMatcher, test::temp_tree};
 
 #[cfg(test)]
 #[ctor::ctor]