Rework prettier installation and start

Kirill Bulatov created

Change summary

crates/prettier/src/prettier.rs | 134 -------
crates/project/src/project.rs   | 616 ++++++++++++++++++++--------------
crates/project/src/search.rs    |   3 
3 files changed, 363 insertions(+), 390 deletions(-)

Detailed changes

crates/prettier/src/prettier.rs 🔗

@@ -1,4 +1,3 @@
-use std::collections::VecDeque;
 use std::path::{Path, PathBuf};
 use std::sync::Arc;
 
@@ -20,7 +19,6 @@ pub enum Prettier {
 }
 
 pub struct RealPrettier {
-    worktree_id: Option<usize>,
     default: bool,
     prettier_dir: PathBuf,
     server: Arc<LanguageServer>,
@@ -28,17 +26,10 @@ pub struct RealPrettier {
 
 #[cfg(any(test, feature = "test-support"))]
 pub struct TestPrettier {
-    worktree_id: Option<usize>,
     prettier_dir: PathBuf,
     default: bool,
 }
 
-#[derive(Debug)]
-pub struct LocateStart {
-    pub worktree_root_path: Arc<Path>,
-    pub starting_path: Arc<Path>,
-}
-
 pub const PRETTIER_SERVER_FILE: &str = "prettier_server.js";
 pub const PRETTIER_SERVER_JS: &str = include_str!("./prettier_server.js");
 const PRETTIER_PACKAGE_NAME: &str = "prettier";
@@ -130,75 +121,21 @@ impl Prettier {
         }
     }
 
-    pub async fn locate(
-        starting_path: Option<LocateStart>,
-        fs: Arc<dyn Fs>,
-    ) -> anyhow::Result<PathBuf> {
-        let paths_to_check = match starting_path.as_ref() {
-            Some(starting_path) => {
-                let worktree_root = starting_path
-                    .worktree_root_path
-                    .components()
-                    .into_iter()
-                    .take_while(|path_component| !is_node_modules(path_component))
-                    .collect::<PathBuf>();
-                if worktree_root != starting_path.worktree_root_path.as_ref() {
-                    vec![worktree_root]
-                } else {
-                    if starting_path.starting_path.as_ref() == Path::new("") {
-                        worktree_root
-                            .parent()
-                            .map(|path| vec![path.to_path_buf()])
-                            .unwrap_or_default()
-                    } else {
-                        let file_to_format = starting_path.starting_path.as_ref();
-                        let mut paths_to_check = VecDeque::new();
-                        let mut current_path = worktree_root;
-                        for path_component in file_to_format.components().into_iter() {
-                            let new_path = current_path.join(path_component);
-                            let old_path = std::mem::replace(&mut current_path, new_path);
-                            paths_to_check.push_front(old_path);
-                            if is_node_modules(&path_component) {
-                                break;
-                            }
-                        }
-                        Vec::from(paths_to_check)
-                    }
-                }
-            }
-            None => Vec::new(),
-        };
-
-        match find_closest_prettier_dir(fs.as_ref(), paths_to_check)
-            .await
-            .with_context(|| format!("finding prettier starting with {starting_path:?}"))?
-        {
-            Some(prettier_dir) => Ok(prettier_dir),
-            None => Ok(DEFAULT_PRETTIER_DIR.to_path_buf()),
-        }
-    }
-
     #[cfg(any(test, feature = "test-support"))]
     pub async fn start(
-        worktree_id: Option<usize>,
         _: LanguageServerId,
         prettier_dir: PathBuf,
         _: Arc<dyn NodeRuntime>,
         _: AsyncAppContext,
     ) -> anyhow::Result<Self> {
-        Ok(
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(TestPrettier {
-                worktree_id,
-                default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
-                prettier_dir,
-            }),
-        )
+        Ok(Self::Test(TestPrettier {
+            default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
+            prettier_dir,
+        }))
     }
 
     #[cfg(not(any(test, feature = "test-support")))]
     pub async fn start(
-        worktree_id: Option<usize>,
         server_id: LanguageServerId,
         prettier_dir: PathBuf,
         node: Arc<dyn NodeRuntime>,
@@ -206,7 +143,7 @@ impl Prettier {
     ) -> anyhow::Result<Self> {
         use lsp::LanguageServerBinary;
 
-        let backgroud = cx.background();
+        let background = cx.background();
         anyhow::ensure!(
             prettier_dir.is_dir(),
             "Prettier dir {prettier_dir:?} is not a directory"
@@ -217,7 +154,7 @@ impl Prettier {
             "no prettier server package found at {prettier_server:?}"
         );
 
-        let node_path = backgroud
+        let node_path = background
             .spawn(async move { node.binary_path().await })
             .await?;
         let server = LanguageServer::new(
@@ -232,12 +169,11 @@ impl Prettier {
             cx,
         )
         .context("prettier server creation")?;
-        let server = backgroud
+        let server = background
             .spawn(server.initialize(None))
             .await
             .context("prettier server initialization")?;
         Ok(Self::Real(RealPrettier {
-            worktree_id,
             server,
             default: prettier_dir == DEFAULT_PRETTIER_DIR.as_path(),
             prettier_dir,
@@ -403,14 +339,6 @@ impl Prettier {
             Self::Test(test_prettier) => &test_prettier.prettier_dir,
         }
     }
-
-    pub fn worktree_id(&self) -> Option<usize> {
-        match self {
-            Self::Real(local) => local.worktree_id,
-            #[cfg(any(test, feature = "test-support"))]
-            Self::Test(test_prettier) => test_prettier.worktree_id,
-        }
-    }
 }
 
 async fn has_prettier_in_node_modules(fs: &dyn Fs, path: &Path) -> anyhow::Result<bool> {
@@ -466,54 +394,6 @@ fn has_prettier_in_package_json(
     false
 }
 
-async fn find_closest_prettier_dir(
-    fs: &dyn Fs,
-    paths_to_check: Vec<PathBuf>,
-) -> anyhow::Result<Option<PathBuf>> {
-    for path in paths_to_check {
-        let possible_package_json = path.join("package.json");
-        if let Some(package_json_metadata) = fs
-            .metadata(&possible_package_json)
-            .await
-            .with_context(|| format!("Fetching metadata for {possible_package_json:?}"))?
-        {
-            if !package_json_metadata.is_dir && !package_json_metadata.is_symlink {
-                let package_json_contents = fs
-                    .load(&possible_package_json)
-                    .await
-                    .with_context(|| format!("reading {possible_package_json:?} file contents"))?;
-                if let Ok(json_contents) = serde_json::from_str::<HashMap<String, serde_json::Value>>(
-                    &package_json_contents,
-                ) {
-                    if let Some(serde_json::Value::Object(o)) = json_contents.get("dependencies") {
-                        if o.contains_key(PRETTIER_PACKAGE_NAME) {
-                            return Ok(Some(path));
-                        }
-                    }
-                    if let Some(serde_json::Value::Object(o)) = json_contents.get("devDependencies")
-                    {
-                        if o.contains_key(PRETTIER_PACKAGE_NAME) {
-                            return Ok(Some(path));
-                        }
-                    }
-                }
-            }
-        }
-
-        let possible_node_modules_location = path.join("node_modules").join(PRETTIER_PACKAGE_NAME);
-        if let Some(node_modules_location_metadata) = fs
-            .metadata(&possible_node_modules_location)
-            .await
-            .with_context(|| format!("fetching metadata for {possible_node_modules_location:?}"))?
-        {
-            if node_modules_location_metadata.is_dir {
-                return Ok(Some(path));
-            }
-        }
-    }
-    Ok(None)
-}
-
 enum Format {}
 
 #[derive(Clone, Debug, Eq, PartialEq, Serialize, Deserialize)]

crates/project/src/project.rs 🔗

@@ -54,7 +54,7 @@ use lsp_command::*;
 use node_runtime::NodeRuntime;
 use parking_lot::Mutex;
 use postage::watch;
-use prettier::{LocateStart, Prettier};
+use prettier::Prettier;
 use project_settings::{LspSettings, ProjectSettings};
 use rand::prelude::*;
 use search::SearchQuery;
@@ -82,8 +82,11 @@ use std::{
 use terminals::Terminals;
 use text::Anchor;
 use util::{
-    debug_panic, defer, http::HttpClient, merge_json_value_into,
-    paths::LOCAL_SETTINGS_RELATIVE_PATH, post_inc, ResultExt, TryFutureExt as _,
+    debug_panic, defer,
+    http::HttpClient,
+    merge_json_value_into,
+    paths::{DEFAULT_PRETTIER_DIR, LOCAL_SETTINGS_RELATIVE_PATH},
+    post_inc, ResultExt, TryFutureExt as _,
 };
 
 pub use fs::*;
@@ -162,17 +165,14 @@ pub struct Project {
     copilot_log_subscription: Option<lsp::Subscription>,
     current_lsp_settings: HashMap<Arc<str>, LspSettings>,
     node: Option<Arc<dyn NodeRuntime>>,
-    #[cfg(not(any(test, feature = "test-support")))]
     default_prettier: Option<DefaultPrettier>,
-    prettier_instances: HashMap<
-        (Option<WorktreeId>, PathBuf),
-        Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>,
-    >,
+    prettiers_per_worktree: HashMap<WorktreeId, HashSet<Option<PathBuf>>>,
+    prettier_instances: HashMap<PathBuf, Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>,
 }
 
-#[cfg(not(any(test, feature = "test-support")))]
 struct DefaultPrettier {
-    installation_process: Option<Shared<Task<()>>>,
+    instance: Option<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>,
+    #[cfg(not(any(test, feature = "test-support")))]
     installed_plugins: HashSet<&'static str>,
 }
 
@@ -685,8 +685,8 @@ impl Project {
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
                 node: Some(node),
-                #[cfg(not(any(test, feature = "test-support")))]
                 default_prettier: None,
+                prettiers_per_worktree: HashMap::default(),
                 prettier_instances: HashMap::default(),
             }
         })
@@ -786,8 +786,8 @@ impl Project {
                 copilot_log_subscription: None,
                 current_lsp_settings: settings::get::<ProjectSettings>(cx).lsp.clone(),
                 node: None,
-                #[cfg(not(any(test, feature = "test-support")))]
                 default_prettier: None,
+                prettiers_per_worktree: HashMap::default(),
                 prettier_instances: HashMap::default(),
             };
             for worktree in worktrees {
@@ -2681,20 +2681,8 @@ impl Project {
         let buffer_file = File::from_dyn(buffer_file.as_ref());
         let worktree = buffer_file.as_ref().map(|f| f.worktree_id(cx));
 
-        let task_buffer = buffer.clone();
-        let prettier_installation_task =
-            self.install_default_formatters(worktree, &new_language, &settings, cx);
-        cx.spawn(|project, mut cx| async move {
-            prettier_installation_task.await?;
-            let _ = project
-                .update(&mut cx, |project, cx| {
-                    project.prettier_instance_for_buffer(&task_buffer, cx)
-                })
-                .await;
-            anyhow::Ok(())
-        })
-        .detach_and_log_err(cx);
-
+        self.install_default_formatters(worktree, &new_language, &settings, cx)
+            .detach_and_log_err(cx);
         if let Some(file) = buffer_file {
             let worktree = file.worktree.clone();
             if let Some(tree) = worktree.read(cx).as_local() {
@@ -4029,7 +4017,7 @@ impl Project {
     }
 
     pub fn format(
-        &self,
+        &mut self,
         buffers: HashSet<ModelHandle<Buffer>>,
         push_to_history: bool,
         trigger: FormatTrigger,
@@ -4049,10 +4037,10 @@ impl Project {
                 })
                 .collect::<Vec<_>>();
 
-            cx.spawn(|this, mut cx| async move {
+            cx.spawn(|project, mut cx| async move {
                 // Do not allow multiple concurrent formatting requests for the
                 // same buffer.
-                this.update(&mut cx, |this, cx| {
+                project.update(&mut cx, |this, cx| {
                     buffers_with_paths_and_servers.retain(|(buffer, _, _)| {
                         this.buffers_being_formatted
                             .insert(buffer.read(cx).remote_id())
@@ -4060,7 +4048,7 @@ impl Project {
                 });
 
                 let _cleanup = defer({
-                    let this = this.clone();
+                    let this = project.clone();
                     let mut cx = cx.clone();
                     let buffers = &buffers_with_paths_and_servers;
                     move || {
@@ -4128,7 +4116,7 @@ impl Project {
                             {
                                 format_operation = Some(FormatOperation::Lsp(
                                     Self::format_via_lsp(
-                                        &this,
+                                        &project,
                                         &buffer,
                                         buffer_abs_path,
                                         &language_server,
@@ -4163,16 +4151,24 @@ impl Project {
                             }
                         }
                         (Formatter::Auto, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(prettier_task) = this
+                            if let Some((prettier_path, prettier_task)) = project
                                 .update(&mut cx, |project, cx| {
                                     project.prettier_instance_for_buffer(buffer, cx)
                                 }).await {
                                     match prettier_task.await
                                     {
                                         Ok(prettier) => {
-                                            let buffer_path = buffer.read_with(&cx, |buffer, cx| {
-                                                File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                                            });
+                                            let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned());
+                                            let buffer_path= {
+                                                File::from_dyn(buffer_file.as_ref()).map(|file| {
+                                                    cx.update(|cx| {
+                                                       let worktree_id = file.worktree_id(cx);
+                                                       let file_abs_path = file.abs_path(cx);
+                                                       project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path));
+                                                       file_abs_path
+                                                    })
+                                                })
+                                            };
                                             format_operation = Some(FormatOperation::Prettier(
                                                 prettier
                                                     .format(buffer, buffer_path, &cx)
@@ -4180,16 +4176,29 @@ impl Project {
                                                     .context("formatting via prettier")?,
                                             ));
                                         }
-                                        Err(e) => anyhow::bail!(
-                                            "Failed to create prettier instance for buffer during autoformatting: {e:#}"
-                                        ),
+                                        Err(e) => {
+                                            project.update(&mut cx, |project, _| {
+                                                match &prettier_path {
+                                                    Some(prettier_path) => {
+                                                        project.prettier_instances.remove(prettier_path);
+                                                    },
+                                                    None => {
+                                                        if let Some(default_prettier) = project.default_prettier.as_mut() {
+                                                            default_prettier.instance = None;
+                                                        }
+                                                    },
+                                                }
+                                            });
+                                            anyhow::bail!(
+                                                "Failed to create prettier instance from {prettier_path:?} for buffer during autoformatting: {e:#}"
+                                            )},
                                     }
                             } else if let Some((language_server, buffer_abs_path)) =
                                 language_server.as_ref().zip(buffer_abs_path.as_ref())
                             {
                                 format_operation = Some(FormatOperation::Lsp(
                                     Self::format_via_lsp(
-                                        &this,
+                                        &project,
                                         &buffer,
                                         buffer_abs_path,
                                         &language_server,
@@ -4202,16 +4211,24 @@ impl Project {
                             }
                         }
                         (Formatter::Prettier { .. }, FormatOnSave::On | FormatOnSave::Off) => {
-                            if let Some(prettier_task) = this
+                            if let Some((prettier_path, prettier_task)) = project
                                 .update(&mut cx, |project, cx| {
                                     project.prettier_instance_for_buffer(buffer, cx)
                                 }).await {
                                     match prettier_task.await
                                     {
                                         Ok(prettier) => {
-                                            let buffer_path = buffer.read_with(&cx, |buffer, cx| {
-                                                File::from_dyn(buffer.file()).map(|file| file.abs_path(cx))
-                                            });
+                                            let buffer_file = buffer.update(&mut cx, |buffer, _| buffer.file().cloned());
+                                            let buffer_path= {
+                                                File::from_dyn(buffer_file.as_ref()).map(|file| {
+                                                    cx.update(|cx| {
+                                                       let worktree_id = file.worktree_id(cx);
+                                                       let file_abs_path = file.abs_path(cx);
+                                                       project.update(cx, |project, _| project.prettiers_per_worktree.entry(worktree_id).or_default().insert(prettier_path));
+                                                       file_abs_path
+                                                    })
+                                                })
+                                            };
                                             format_operation = Some(FormatOperation::Prettier(
                                                 prettier
                                                     .format(buffer, buffer_path, &cx)
@@ -4219,9 +4236,22 @@ impl Project {
                                                     .context("formatting via prettier")?,
                                             ));
                                         }
-                                        Err(e) => anyhow::bail!(
-                                            "Failed to create prettier instance for buffer during formatting: {e:#}"
-                                        ),
+                                        Err(e) => {
+                                            project.update(&mut cx, |project, _| {
+                                                match &prettier_path {
+                                                    Some(prettier_path) => {
+                                                        project.prettier_instances.remove(prettier_path);
+                                                    },
+                                                    None => {
+                                                        if let Some(default_prettier) = project.default_prettier.as_mut() {
+                                                            default_prettier.instance = None;
+                                                        }
+                                                    },
+                                                }
+                                            });
+                                            anyhow::bail!(
+                                                "Failed to create prettier instance from {prettier_path:?} for buffer during formatting: {e:#}"
+                                            )},
                                     }
                                 }
                         }
@@ -6431,15 +6461,25 @@ impl Project {
                 "Prettier config file {config_path:?} changed, reloading prettier instances for worktree {current_worktree_id}"
             );
             let prettiers_to_reload = self
-                .prettier_instances
+                .prettiers_per_worktree
+                .get(&current_worktree_id)
                 .iter()
-                .filter_map(|((worktree_id, prettier_path), prettier_task)| {
-                    if worktree_id.is_none() || worktree_id == &Some(current_worktree_id) {
-                        Some((*worktree_id, prettier_path.clone(), prettier_task.clone()))
-                    } else {
-                        None
-                    }
+                .flat_map(|prettier_paths| prettier_paths.iter())
+                .flatten()
+                .filter_map(|prettier_path| {
+                    Some((
+                        current_worktree_id,
+                        Some(prettier_path.clone()),
+                        self.prettier_instances.get(prettier_path)?.clone(),
+                    ))
                 })
+                .chain(self.default_prettier.iter().filter_map(|default_prettier| {
+                    Some((
+                        current_worktree_id,
+                        None,
+                        default_prettier.instance.clone()?,
+                    ))
+                }))
                 .collect::<Vec<_>>();
 
             cx.background()
@@ -6450,9 +6490,15 @@ impl Project {
                                 .clear_cache()
                                 .await
                                 .with_context(|| {
-                                    format!(
-                                        "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update"
-                                    )
+                                    match prettier_path {
+                                        Some(prettier_path) => format!(
+                                            "clearing prettier {prettier_path:?} cache for worktree {worktree_id:?} on prettier settings update"
+                                        ),
+                                        None => format!(
+                                            "clearing default prettier cache for worktree {worktree_id:?} on prettier settings update"
+                                        ),
+                                    }
+
                                 })
                                 .map_err(Arc::new)
                         }
@@ -8364,7 +8410,12 @@ impl Project {
         &mut self,
         buffer: &ModelHandle<Buffer>,
         cx: &mut ModelContext<Self>,
-    ) -> Task<Option<Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>>> {
+    ) -> Task<
+        Option<(
+            Option<PathBuf>,
+            Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>>,
+        )>,
+    > {
         let buffer = buffer.read(cx);
         let buffer_file = buffer.file();
         let Some(buffer_language) = buffer.language() else {
@@ -8374,136 +8425,136 @@ impl Project {
             return Task::ready(None);
         }
 
-        let buffer_file = File::from_dyn(buffer_file);
-        let buffer_path = buffer_file.map(|file| Arc::clone(file.path()));
-        let worktree_path = buffer_file
-            .as_ref()
-            .and_then(|file| Some(file.worktree.read(cx).abs_path()));
-        let worktree_id = buffer_file.map(|file| file.worktree_id(cx));
-        if self.is_local() || worktree_id.is_none() || worktree_path.is_none() {
+        if self.is_local() {
             let Some(node) = self.node.as_ref().map(Arc::clone) else {
                 return Task::ready(None);
             };
-            cx.spawn(|this, mut cx| async move {
-                let fs = this.update(&mut cx, |project, _| Arc::clone(&project.fs));
-                let prettier_dir = match cx
-                    .background()
-                    .spawn(Prettier::locate(
-                        worktree_path.zip(buffer_path).map(
-                            |(worktree_root_path, starting_path)| LocateStart {
-                                worktree_root_path,
-                                starting_path,
-                            },
-                        ),
-                        fs,
-                    ))
-                    .await
-                {
-                    Ok(path) => path,
-                    Err(e) => {
-                        return Some(
-                            Task::ready(Err(Arc::new(e.context(
-                                "determining prettier path for worktree {worktree_path:?}",
-                            ))))
-                            .shared(),
-                        );
-                    }
-                };
-
-                if let Some(existing_prettier) = this.update(&mut cx, |project, _| {
-                    project
-                        .prettier_instances
-                        .get(&(worktree_id, prettier_dir.clone()))
-                        .cloned()
-                }) {
-                    return Some(existing_prettier);
-                }
+            match File::from_dyn(buffer_file).map(|file| file.abs_path(cx)) {
+                Some(buffer_path) => {
+                    let fs = Arc::clone(&self.fs);
+                    let installed_prettiers = self.prettier_instances.keys().cloned().collect();
+                    return cx.spawn(|project, mut cx| async move {
+                        match cx
+                            .background()
+                            .spawn(async move {
+                                Prettier::locate_prettier_installation(
+                                    fs.as_ref(),
+                                    &installed_prettiers,
+                                    &buffer_path,
+                                )
+                                .await
+                            })
+                            .await
+                        {
+                            Ok(None) => {
+                                let new_task = project.update(&mut cx, |project, cx| {
+                                    let new_task = spawn_default_prettier(node, 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));
+                            }
+                            Err(e) => {
+                                return Some((
+                                    None,
+                                    Task::ready(Err(Arc::new(e.context(
+                                        "determining prettier path for worktree {worktree_path:?}",
+                                    ))))
+                                    .shared(),
+                                ));
+                            }
+                            Ok(Some(prettier_dir)) => {
+                                if let Some(existing_prettier) =
+                                    project.update(&mut cx, |project, _| {
+                                        project.prettier_instances.get(&prettier_dir).cloned()
+                                    })
+                                {
+                                    return Some((Some(prettier_dir), existing_prettier));
+                                }
 
-                log::info!("Found prettier in {prettier_dir:?}, starting.");
-                let task_prettier_dir = prettier_dir.clone();
-                let weak_project = this.downgrade();
-                let new_server_id =
-                    this.update(&mut cx, |this, _| this.languages.next_language_server_id());
-                let new_prettier_task = cx
-                    .spawn(|mut cx| async move {
-                        let prettier = Prettier::start(
-                            worktree_id.map(|id| id.to_usize()),
-                            new_server_id,
-                            task_prettier_dir,
-                            node,
-                            cx.clone(),
-                        )
-                        .await
-                        .context("prettier start")
-                        .map_err(Arc::new)?;
-                        log::info!("Started prettier in {:?}", prettier.prettier_dir());
+                                log::info!("Found prettier in {prettier_dir:?}, starting.");
+                                let task_prettier_dir = prettier_dir.clone();
+                                let weak_project = project.downgrade();
+                                let new_server_id = project.update(&mut cx, |this, _| {
+                                    this.languages.next_language_server_id()
+                                });
+                                let new_prettier_task = cx
+                                    .spawn(|mut cx| async move {
+                                        let prettier = Prettier::start(
+                                            new_server_id,
+                                            task_prettier_dir,
+                                            node,
+                                            cx.clone(),
+                                        )
+                                        .await
+                                        .context("prettier start")
+                                        .map_err(Arc::new)?;
+                                        log::info!(
+                                            "Started prettier in {:?}",
+                                            prettier.prettier_dir()
+                                        );
 
-                        if let Some((project, prettier_server)) =
-                            weak_project.upgrade(&mut cx).zip(prettier.server())
-                        {
-                            project.update(&mut cx, |project, cx| {
-                                let name = if prettier.is_default() {
-                                    LanguageServerName(Arc::from("prettier (default)"))
-                                } else {
-                                    let prettier_dir = prettier.prettier_dir();
-                                    let worktree_path = prettier
-                                        .worktree_id()
-                                        .map(WorktreeId::from_usize)
-                                        .and_then(|id| project.worktree_for_id(id, cx))
-                                        .map(|worktree| worktree.read(cx).abs_path());
-                                    match worktree_path {
-                                        Some(worktree_path) => {
-                                            if worktree_path.as_ref() == prettier_dir {
-                                                LanguageServerName(Arc::from(format!(
-                                                    "prettier ({})",
-                                                    prettier_dir
-                                                        .file_name()
-                                                        .and_then(|name| name.to_str())
-                                                        .unwrap_or_default()
-                                                )))
-                                            } else {
-                                                let dir_to_display = match prettier_dir
-                                                    .strip_prefix(&worktree_path)
-                                                    .ok()
-                                                {
-                                                    Some(relative_path) => relative_path,
-                                                    None => prettier_dir,
+                                        if let Some((project, prettier_server)) =
+                                            weak_project.upgrade(&mut cx).zip(prettier.server())
+                                        {
+                                            project.update(&mut cx, |project, cx| {
+                                                let name = if prettier.is_default() {
+                                                    LanguageServerName(Arc::from(
+                                                        "prettier (default)",
+                                                    ))
+                                                } else {
+                                                    LanguageServerName(Arc::from(format!(
+                                                        "prettier ({})",
+                                                        prettier.prettier_dir().display(),
+                                                    )))
                                                 };
-                                                LanguageServerName(Arc::from(format!(
-                                                    "prettier ({})",
-                                                    dir_to_display.display(),
-                                                )))
-                                            }
-                                        }
-                                        None => LanguageServerName(Arc::from(format!(
-                                            "prettier ({})",
-                                            prettier_dir.display(),
-                                        ))),
-                                    }
-                                };
 
-                                project
-                                    .supplementary_language_servers
-                                    .insert(new_server_id, (name, Arc::clone(prettier_server)));
-                                cx.emit(Event::LanguageServerAdded(new_server_id));
-                            });
+                                                project.supplementary_language_servers.insert(
+                                                    new_server_id,
+                                                    (name, Arc::clone(prettier_server)),
+                                                );
+                                                cx.emit(Event::LanguageServerAdded(new_server_id));
+                                            });
+                                        }
+                                        Ok(Arc::new(prettier)).map_err(Arc::new)
+                                    })
+                                    .shared();
+                                project.update(&mut cx, |project, _| {
+                                    project
+                                        .prettier_instances
+                                        .insert(prettier_dir.clone(), new_prettier_task.clone());
+                                });
+                                Some((Some(prettier_dir), new_prettier_task))
+                            }
                         }
-                        Ok(Arc::new(prettier)).map_err(Arc::new)
-                    })
-                    .shared();
-                this.update(&mut cx, |project, _| {
-                    project
-                        .prettier_instances
-                        .insert((worktree_id, prettier_dir), new_prettier_task.clone());
-                });
-                Some(new_prettier_task)
-            })
+                    });
+                }
+                None => {
+                    let new_task = spawn_default_prettier(node, 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)));
+                }
+            }
         } else if self.remote_id().is_some() {
             return Task::ready(None);
         } else {
-            Task::ready(Some(
+            Task::ready(Some((
+                None,
                 Task::ready(Err(Arc::new(anyhow!("project does not have a remote id")))).shared(),
-            ))
+            )))
         }
     }
 
@@ -8537,7 +8588,7 @@ impl Project {
         let mut prettier_plugins = None;
         if new_language.prettier_parser_name().is_some() {
             prettier_plugins
-                .get_or_insert_with(|| HashSet::default())
+                .get_or_insert_with(|| HashSet::<&'static str>::default())
                 .extend(
                     new_language
                         .lsp_adapters()
@@ -8549,27 +8600,25 @@ impl Project {
             return Task::ready(Ok(()));
         };
 
+        let fs = Arc::clone(&self.fs);
+        let locate_prettier_installation = match worktree.and_then(|worktree_id| {
+            self.worktree_for_id(worktree_id, cx)
+                .map(|worktree| worktree.read(cx).abs_path())
+        }) {
+            Some(locate_from) => {
+                let installed_prettiers = self.prettier_instances.keys().cloned().collect();
+                cx.background().spawn(async move {
+                    Prettier::locate_prettier_installation(
+                        fs.as_ref(),
+                        &installed_prettiers,
+                        locate_from.as_ref(),
+                    )
+                    .await
+                })
+            }
+            None => Task::ready(Ok(None)),
+        };
         let mut plugins_to_install = prettier_plugins;
-        let (mut install_success_tx, mut install_success_rx) =
-            futures::channel::mpsc::channel::<HashSet<&'static str>>(1);
-        let new_installation_process = cx
-            .spawn(|this, mut cx| async move {
-                if let Some(installed_plugins) = install_success_rx.next().await {
-                    this.update(&mut cx, |this, _| {
-                        let default_prettier =
-                            this.default_prettier
-                                .get_or_insert_with(|| DefaultPrettier {
-                                    installation_process: None,
-                                    installed_plugins: HashSet::default(),
-                                });
-                        if !installed_plugins.is_empty() {
-                            log::info!("Installed new prettier plugins: {installed_plugins:?}");
-                            default_prettier.installed_plugins.extend(installed_plugins);
-                        }
-                    })
-                }
-            })
-            .shared();
         let previous_installation_process =
             if let Some(default_prettier) = &mut self.default_prettier {
                 plugins_to_install
@@ -8577,81 +8626,126 @@ impl Project {
                 if plugins_to_install.is_empty() {
                     return Task::ready(Ok(()));
                 }
-                std::mem::replace(
-                    &mut default_prettier.installation_process,
-                    Some(new_installation_process.clone()),
-                )
+                default_prettier.instance.clone()
             } else {
                 None
             };
-
-        let default_prettier_dir = util::paths::DEFAULT_PRETTIER_DIR.as_path();
-        let already_running_prettier = self
-            .prettier_instances
-            .get(&(worktree, default_prettier_dir.to_path_buf()))
-            .cloned();
         let fs = Arc::clone(&self.fs);
         cx.spawn(|this, mut cx| async move {
-            if let Some(previous_installation_process) = previous_installation_process {
-                previous_installation_process.await;
-            }
-            let mut everything_was_installed = false;
-            this.update(&mut cx, |this, _| {
-                match &mut this.default_prettier {
-                    Some(default_prettier) => {
-                        plugins_to_install
-                            .retain(|plugin| !default_prettier.installed_plugins.contains(plugin));
-                        everything_was_installed = plugins_to_install.is_empty();
-                    },
-                    None => this.default_prettier = Some(DefaultPrettier { installation_process: Some(new_installation_process), installed_plugins: HashSet::default() }),
+            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")?;
+                        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 everything_was_installed {
-                return Ok(());
             }
+            Ok(())
+        })
+    }
+}
 
-            cx.background()
-                .spawn(async move {
-                    let prettier_wrapper_path = default_prettier_dir.join(prettier::PRETTIER_SERVER_FILE);
-                    // method creates parent directory if it doesn't exist
-                    fs.save(&prettier_wrapper_path, &text::Rope::from(prettier::PRETTIER_SERVER_JS), text::LineEnding::Unix).await
-                    .with_context(|| format!("writing {} file at {prettier_wrapper_path:?}", prettier::PRETTIER_SERVER_FILE))?;
+fn spawn_default_prettier(
+    node: Arc<dyn NodeRuntime>,
+    cx: &mut ModelContext<'_, Project>,
+) -> Shared<Task<Result<Arc<Prettier>, Arc<anyhow::Error>>>> {
+    cx.spawn(|project, mut cx| async move {
+        let new_server_id = project.update(&mut cx, |project, _| {
+            project.languages.next_language_server_id()
+        });
+        Prettier::start(
+            new_server_id,
+            DEFAULT_PRETTIER_DIR.clone(),
+            node,
+            cx.clone(),
+        )
+        .await
+        .context("default prettier spawn")
+        .map(Arc::new)
+        .map_err(Arc::new)
+    })
+    .shared()
+}
 
-                    let packages_to_versions = future::try_join_all(
-                        plugins_to_install
-                            .iter()
-                            .chain(Some(&"prettier"))
-                            .map(|package_name| async {
-                                let returned_package_name = package_name.to_string();
-                                let latest_version = node.npm_package_latest_version(package_name)
-                                    .await
-                                    .with_context(|| {
-                                        format!("fetching latest npm version for package {returned_package_name}")
-                                    })?;
-                                anyhow::Ok((returned_package_name, latest_version))
-                            }),
-                    )
+#[cfg(not(any(test, feature = "test-support")))]
+async fn install_default_prettier(
+    plugins_to_install: HashSet<&'static str>,
+    node: Arc<dyn NodeRuntime>,
+    fs: Arc<dyn Fs>,
+) -> anyhow::Result<()> {
+    let prettier_wrapper_path = DEFAULT_PRETTIER_DIR.join(prettier::PRETTIER_SERVER_FILE);
+    // method creates parent directory if it doesn't exist
+    fs.save(
+        &prettier_wrapper_path,
+        &text::Rope::from(prettier::PRETTIER_SERVER_JS),
+        text::LineEnding::Unix,
+    )
+    .await
+    .with_context(|| {
+        format!(
+            "writing {} file at {prettier_wrapper_path:?}",
+            prettier::PRETTIER_SERVER_FILE
+        )
+    })?;
+
+    let packages_to_versions =
+        future::try_join_all(plugins_to_install.iter().chain(Some(&"prettier")).map(
+            |package_name| async {
+                let returned_package_name = package_name.to_string();
+                let latest_version = node
+                    .npm_package_latest_version(package_name)
                     .await
-                    .context("fetching latest npm versions")?;
-
-                    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
-                    let borrowed_packages = packages_to_versions.iter().map(|(package, version)| {
-                        (package.as_str(), version.as_str())
-                    }).collect::<Vec<_>>();
-                    node.npm_install_packages(default_prettier_dir, &borrowed_packages).await.context("fetching formatter packages")?;
-                    let installed_packages = !plugins_to_install.is_empty();
-                    install_success_tx.try_send(plugins_to_install).ok();
-
-                    if !installed_packages {
-                        if let Some(prettier) = already_running_prettier {
-                            prettier.await.map_err(|e| anyhow::anyhow!("Default prettier startup await failure: {e:#}"))?.clear_cache().await.context("clearing default prettier cache after plugins install")?;
-                        }
-                    }
-
-                    anyhow::Ok(())
-                }).await
-        })
-    }
+                    .with_context(|| {
+                        format!("fetching latest npm version for package {returned_package_name}")
+                    })?;
+                anyhow::Ok((returned_package_name, latest_version))
+            },
+        ))
+        .await
+        .context("fetching latest npm versions")?;
+
+    log::info!("Fetching default prettier and plugins: {packages_to_versions:?}");
+    let borrowed_packages = packages_to_versions
+        .iter()
+        .map(|(package, version)| (package.as_str(), version.as_str()))
+        .collect::<Vec<_>>();
+    node.npm_install_packages(DEFAULT_PRETTIER_DIR.as_path(), &borrowed_packages)
+        .await
+        .context("fetching formatter packages")?;
+    anyhow::Ok(())
 }
 
 fn subscribe_for_copilot_events(

crates/project/src/search.rs 🔗

@@ -1,7 +1,6 @@
 use aho_corasick::{AhoCorasick, AhoCorasickBuilder};
 use anyhow::{Context, Result};
 use client::proto;
-use globset::{Glob, GlobMatcher};
 use itertools::Itertools;
 use language::{char_kind, BufferSnapshot};
 use regex::{Regex, RegexBuilder};
@@ -10,7 +9,7 @@ use std::{
     borrow::Cow,
     io::{BufRead, BufReader, Read},
     ops::Range,
-    path::{Path, PathBuf},
+    path::Path,
     sync::Arc,
 };
 use util::paths::PathMatcher;