languages: Fix python run module task failing on windows (#44064)

Lukas Wirth created

Fixes #40155

Release Notes:

- Fixed python's run module task not working on windows platforms

Co-authored by: Smit Barmase <smit@zed.dev>

Change summary

crates/languages/src/python.rs | 58 +++++++++++++++++++++++++++++------
1 file changed, 48 insertions(+), 10 deletions(-)

Detailed changes

crates/languages/src/python.rs 🔗

@@ -28,6 +28,7 @@ use std::env::consts;
 use terminal::terminal_settings::TerminalSettings;
 use util::command::new_smol_command;
 use util::fs::{make_file_executable, remove_matching};
+use util::paths::PathStyle;
 use util::rel_path::RelPath;
 
 use http_client::github_download::{GithubBinaryMetadata, download_server_binary};
@@ -884,7 +885,7 @@ impl PythonContextProvider {
         variables: &task::TaskVariables,
     ) -> Option<(VariableName, String)> {
         let python_module_name =
-            python_module_name_from_relative_path(variables.get(&VariableName::RelativeFile)?);
+            python_module_name_from_relative_path(variables.get(&VariableName::RelativeFile)?)?;
 
         let unittest_class_name =
             variables.get(&VariableName::Custom(Cow::Borrowed("_unittest_class_name")));
@@ -941,9 +942,10 @@ impl PythonContextProvider {
         &self,
         variables: &task::TaskVariables,
     ) -> Result<(VariableName, String)> {
-        let python_module_name = python_module_name_from_relative_path(
-            variables.get(&VariableName::RelativeFile).unwrap_or(""),
-        );
+        let python_module_name = variables
+            .get(&VariableName::RelativeFile)
+            .and_then(|module| python_module_name_from_relative_path(module))
+            .unwrap_or_default();
 
         let module_target = (PYTHON_MODULE_NAME_TASK_VARIABLE.clone(), python_module_name);
 
@@ -951,12 +953,15 @@ impl PythonContextProvider {
     }
 }
 
-fn python_module_name_from_relative_path(relative_path: &str) -> String {
-    let path_with_dots = relative_path.replace('/', ".");
-    path_with_dots
-        .strip_suffix(".py")
-        .unwrap_or(&path_with_dots)
-        .to_string()
+fn python_module_name_from_relative_path(relative_path: &str) -> Option<String> {
+    let rel_path = RelPath::new(relative_path.as_ref(), PathStyle::local()).ok()?;
+    let path_with_dots = rel_path.display(PathStyle::Posix).replace('/', ".");
+    Some(
+        path_with_dots
+            .strip_suffix(".py")
+            .map(ToOwned::to_owned)
+            .unwrap_or(path_with_dots),
+    )
 }
 
 fn is_python_env_global(k: &PythonEnvironmentKind) -> bool {
@@ -2311,6 +2316,8 @@ mod tests {
     use settings::SettingsStore;
     use std::num::NonZeroU32;
 
+    use crate::python::python_module_name_from_relative_path;
+
     #[gpui::test]
     async fn test_python_autoindent(cx: &mut TestAppContext) {
         cx.executor().set_block_on_ticks(usize::MAX..=usize::MAX);
@@ -2439,4 +2446,35 @@ mod tests {
             buffer
         });
     }
+
+    #[test]
+    fn test_python_module_name_from_relative_path() {
+        assert_eq!(
+            python_module_name_from_relative_path("foo/bar.py"),
+            Some("foo.bar".to_string())
+        );
+        assert_eq!(
+            python_module_name_from_relative_path("foo/bar"),
+            Some("foo.bar".to_string())
+        );
+        if cfg!(windows) {
+            assert_eq!(
+                python_module_name_from_relative_path("foo\\bar.py"),
+                Some("foo.bar".to_string())
+            );
+            assert_eq!(
+                python_module_name_from_relative_path("foo\\bar"),
+                Some("foo.bar".to_string())
+            );
+        } else {
+            assert_eq!(
+                python_module_name_from_relative_path("foo\\bar.py"),
+                Some("foo\\bar".to_string())
+            );
+            assert_eq!(
+                python_module_name_from_relative_path("foo\\bar"),
+                Some("foo\\bar".to_string())
+            );
+        }
+    }
 }