python: Look for local venvs in all directories between root of the worktree and current pyproject.toml (#37037)

Piotr Osiewicz created

cc @michael-ud - if you can build Zed, I'd appreciate it if you could
give this a go with your project. Otherwise I can provide a link to
download of current nightly via an e-mail for you to try out (if you
want).
This change will land in Preview (if merged) on next Wednesday and then
it'll be in Stable a week after that.
Related to: #20402
Release Notes:

- python: Zed now searches for virtual environments in intermediate
directories between a root of the worktree and the location of
pyproject.toml applicable to the currently focused file.

Change summary

crates/language/src/toolchain.rs      |  2 +-
crates/languages/src/python.rs        | 18 ++++++++++--------
crates/project/src/project_tests.rs   |  7 ++++---
crates/project/src/toolchain_store.rs |  7 +------
4 files changed, 16 insertions(+), 18 deletions(-)

Detailed changes

crates/language/src/toolchain.rs 🔗

@@ -52,7 +52,7 @@ pub trait ToolchainLister: Send + Sync {
     async fn list(
         &self,
         worktree_root: PathBuf,
-        subroot_relative_path: Option<Arc<Path>>,
+        subroot_relative_path: Arc<Path>,
         project_env: Option<HashMap<String, String>>,
     ) -> ToolchainList;
     // Returns a term which we should use in UI to refer to a toolchain.

crates/languages/src/python.rs 🔗

@@ -759,7 +759,7 @@ impl ToolchainLister for PythonToolchainProvider {
     async fn list(
         &self,
         worktree_root: PathBuf,
-        subroot_relative_path: Option<Arc<Path>>,
+        subroot_relative_path: Arc<Path>,
         project_env: Option<HashMap<String, String>>,
     ) -> ToolchainList {
         let env = project_env.unwrap_or_default();
@@ -771,13 +771,15 @@ impl ToolchainLister for PythonToolchainProvider {
         );
         let mut config = Configuration::default();
 
-        let mut directories = vec![worktree_root.clone()];
-        if let Some(subroot_relative_path) = subroot_relative_path {
-            debug_assert!(subroot_relative_path.is_relative());
-            directories.push(worktree_root.join(subroot_relative_path));
-        }
-
-        config.workspace_directories = Some(directories);
+        debug_assert!(subroot_relative_path.is_relative());
+        // `.ancestors()` will yield at least one path, so in case of empty `subroot_relative_path`, we'll just use
+        // worktree root as the workspace directory.
+        config.workspace_directories = Some(
+            subroot_relative_path
+                .ancestors()
+                .map(|ancestor| worktree_root.join(ancestor))
+                .collect(),
+        );
         for locator in locators.iter() {
             locator.configure(&config);
         }

crates/project/src/project_tests.rs 🔗

@@ -9189,13 +9189,14 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
         async fn list(
             &self,
             worktree_root: PathBuf,
-            subroot_relative_path: Option<Arc<Path>>,
+            subroot_relative_path: Arc<Path>,
             _: Option<HashMap<String, String>>,
         ) -> ToolchainList {
             // This lister will always return a path .venv directories within ancestors
             let ancestors = subroot_relative_path
-                .into_iter()
-                .flat_map(|path| path.ancestors().map(ToOwned::to_owned).collect::<Vec<_>>());
+                .ancestors()
+                .map(ToOwned::to_owned)
+                .collect::<Vec<_>>();
             let mut toolchains = vec![];
             for ancestor in ancestors {
                 let venv_path = worktree_root.join(ancestor).join(".venv");

crates/project/src/toolchain_store.rs 🔗

@@ -389,12 +389,7 @@ impl LocalToolchainStore {
             cx.background_spawn(async move {
                 Some((
                     toolchains
-                        .list(
-                            worktree_root,
-                            Some(relative_path.path.clone())
-                                .filter(|_| *relative_path.path != *Path::new("")),
-                            project_env,
-                        )
+                        .list(worktree_root, relative_path.path.clone(), project_env)
                         .await,
                     relative_path.path,
                 ))