terminal: Fix detection of ignored python venv (#20227)

Askar created

Closes #19227

Since items listed in `.gitignore` file are not included in a worktree,
python virtual environment cannot be detected until venv directory is
unfolded from project panel and forcefully added into worktree. I didn't
come up with anything better than scanning fs directly. I'm not sure how
it will affect remote development. if at all.

Release Notes:

- Fixed detection of `detect_venv.directories` ignored by a worktree

Change summary

crates/project/src/terminals.rs | 40 ++++++++++++++++++++++++++++++++++
1 file changed, 39 insertions(+), 1 deletion(-)

Detailed changes

crates/project/src/terminals.rs 🔗

@@ -276,6 +276,18 @@ impl Project {
         cx: &AppContext,
     ) -> Option<PathBuf> {
         let venv_settings = settings.detect_venv.as_option()?;
+        if let Some(path) = self.find_venv_in_worktree(abs_path, &venv_settings, cx) {
+            return Some(path);
+        }
+        self.find_venv_on_filesystem(abs_path, &venv_settings, cx)
+    }
+
+    fn find_venv_in_worktree(
+        &self,
+        abs_path: &Path,
+        venv_settings: &terminal_settings::VenvSettingsContent,
+        cx: &AppContext,
+    ) -> Option<PathBuf> {
         let bin_dir_name = match std::env::consts::OS {
             "windows" => "Scripts",
             _ => "bin",
@@ -283,7 +295,7 @@ impl Project {
         venv_settings
             .directories
             .iter()
-            .map(|virtual_environment_name| abs_path.join(virtual_environment_name))
+            .map(|name| abs_path.join(name))
             .find(|venv_path| {
                 let bin_path = venv_path.join(bin_dir_name);
                 self.find_worktree(&bin_path, cx)
@@ -294,6 +306,32 @@ impl Project {
             })
     }
 
+    fn find_venv_on_filesystem(
+        &self,
+        abs_path: &Path,
+        venv_settings: &terminal_settings::VenvSettingsContent,
+        cx: &AppContext,
+    ) -> Option<PathBuf> {
+        let (worktree, _) = self.find_worktree(abs_path, cx)?;
+        let fs = worktree.read(cx).as_local()?.fs();
+        let bin_dir_name = match std::env::consts::OS {
+            "windows" => "Scripts",
+            _ => "bin",
+        };
+        venv_settings
+            .directories
+            .iter()
+            .map(|name| abs_path.join(name))
+            .find(|venv_path| {
+                let bin_path = venv_path.join(bin_dir_name);
+                // One-time synchronous check is acceptable for terminal/task initialization
+                smol::block_on(fs.metadata(&bin_path))
+                    .ok()
+                    .flatten()
+                    .map_or(false, |meta| meta.is_dir)
+            })
+    }
+
     fn python_activate_command(
         &self,
         venv_base_directory: &Path,