Add Python venv activation support for Windows and PowerShell (#17839)

ClanEver created

Release Notes:

- Add Python venv activation support for Windows and PowerShell

Additional:

I discovered a related bug on my Windows system. When first opening the
project, it fails to detect the virtual environment folder `.venv`.
After expanding the .venv folder in the Project Panel, it then becomes
able to detect the virtual environment folder. However, I don't know how
to fix it.

Change summary

assets/settings/default.json             |  2 
crates/project/src/terminals.rs          | 31 ++++++++++++++++++++-----
crates/terminal/src/terminal_settings.rs |  1 
3 files changed, 26 insertions(+), 8 deletions(-)

Detailed changes

assets/settings/default.json 🔗

@@ -710,7 +710,7 @@
         // to the current working directory. We recommend overriding this
         // in your project's settings, rather than globally.
         "directories": [".env", "env", ".venv", "venv"],
-        // Can also be `csh`, `fish`, and `nushell`
+        // Can also be `csh`, `fish`, `nushell` and `power_shell`
         "activate_script": "default"
       }
     },

crates/project/src/terminals.rs 🔗

@@ -259,12 +259,16 @@ impl Project {
         cx: &AppContext,
     ) -> Option<PathBuf> {
         let venv_settings = settings.detect_venv.as_option()?;
+        let bin_dir_name = match std::env::consts::OS {
+            "windows" => "Scripts",
+            _ => "bin",
+        };
         venv_settings
             .directories
             .iter()
             .map(|virtual_environment_name| abs_path.join(virtual_environment_name))
             .find(|venv_path| {
-                let bin_path = venv_path.join("bin");
+                let bin_path = venv_path.join(bin_dir_name);
                 self.find_worktree(&bin_path, cx)
                     .and_then(|(worktree, relative_path)| {
                         worktree.read(cx).entry_for_path(&relative_path)
@@ -279,23 +283,36 @@ impl Project {
         settings: &TerminalSettings,
     ) -> Option<String> {
         let venv_settings = settings.detect_venv.as_option()?;
+        let activate_keyword = match venv_settings.activate_script {
+            terminal_settings::ActivateScript::Default => match std::env::consts::OS {
+                "windows" => ".",
+                _ => "source",
+            },
+            terminal_settings::ActivateScript::Nushell => "overlay use",
+            terminal_settings::ActivateScript::PowerShell => ".",
+            _ => "source",
+        };
         let activate_script_name = match venv_settings.activate_script {
             terminal_settings::ActivateScript::Default => "activate",
             terminal_settings::ActivateScript::Csh => "activate.csh",
             terminal_settings::ActivateScript::Fish => "activate.fish",
             terminal_settings::ActivateScript::Nushell => "activate.nu",
+            terminal_settings::ActivateScript::PowerShell => "activate.ps1",
         };
         let path = venv_base_directory
-            .join("bin")
+            .join(match std::env::consts::OS {
+                "windows" => "Scripts",
+                _ => "bin",
+            })
             .join(activate_script_name)
             .to_string_lossy()
             .to_string();
         let quoted = shlex::try_quote(&path).ok()?;
-
-        Some(match venv_settings.activate_script {
-            terminal_settings::ActivateScript::Nushell => format!("overlay use {}\n", quoted),
-            _ => format!("source {}\n", quoted),
-        })
+        let line_ending = match std::env::consts::OS {
+            "windows" => "\r",
+            _ => "\n",
+        };
+        Some(format!("{} {}{}", activate_keyword, quoted, line_ending))
     }
 
     fn activate_python_virtual_environment(