diff --git a/crates/repl/src/components/kernel_options.rs b/crates/repl/src/components/kernel_options.rs index 3b9535767b64dd3e674020035778dffad1601fc6..45e55f0d5f8a17d66a76d206216c07ba7cc36e8a 100644 --- a/crates/repl/src/components/kernel_options.rs +++ b/crates/repl/src/components/kernel_options.rs @@ -27,6 +27,7 @@ fn build_grouped_entries(store: &ReplStore, worktree_id: WorktreeId) -> Vec Vec { + KernelSpecification::JupyterServer(_) | KernelSpecification::SshRemote(_) => { remote_kernels.push(KernelPickerEntry::Kernel { spec: spec.clone(), is_recommended, }); } + KernelSpecification::WslRemote(_) => { + wsl_kernels.push(KernelPickerEntry::Kernel { + spec: spec.clone(), + is_recommended, + }); + } } } @@ -105,6 +110,12 @@ fn build_grouped_entries(store: &ReplStore, worktree_id: WorktreeId) -> Vec None, + KernelSpecification::WslRemote(_) => Some(spec.path().to_string()), KernelSpecification::PythonEnv(_) | KernelSpecification::JupyterServer(_) - | KernelSpecification::SshRemote(_) - | KernelSpecification::WslRemote(_) => { + | KernelSpecification::SshRemote(_) => { let env_kind = spec.environment_kind_label(); let path = spec.path(); match env_kind { diff --git a/crates/repl/src/kernels/mod.rs b/crates/repl/src/kernels/mod.rs index 9ec2ddb497f8c265b51dcfce58d0946d331d87d2..0f1ee9dabebe03b3735bfb95ab0e620a914de1e0 100644 --- a/crates/repl/src/kernels/mod.rs +++ b/crates/repl/src/kernels/mod.rs @@ -9,6 +9,7 @@ pub use native_kernel::*; mod remote_kernels; use project::{Project, ProjectPath, Toolchains, WorktreeId}; +use remote::RemoteConnectionOptions; pub use remote_kernels::*; mod ssh_kernel; @@ -238,7 +239,7 @@ impl KernelSpecification { Self::PythonEnv(spec) => spec.name.clone().into(), Self::JupyterServer(spec) => spec.name.clone().into(), Self::SshRemote(spec) => spec.name.clone().into(), - Self::WslRemote(spec) => spec.name.clone().into(), + Self::WslRemote(spec) => spec.kernelspec.display_name.clone().into(), } } @@ -262,7 +263,7 @@ impl KernelSpecification { Self::PythonEnv(spec) => spec.path.to_string_lossy().into_owned(), Self::JupyterServer(spec) => spec.url.to_string(), Self::SshRemote(spec) => spec.path.to_string(), - Self::WslRemote(_) => "WSL".to_string(), + Self::WslRemote(spec) => spec.distro.clone(), }) } @@ -348,7 +349,16 @@ pub fn python_env_kernel_specifications( ) -> impl Future>> + use<> { let python_language = LanguageName::new_static("Python"); let is_remote = project.read(cx).is_remote(); - log::info!("python_env_kernel_specifications: is_remote: {}", is_remote); + let wsl_distro = project + .read(cx) + .remote_connection_options(cx) + .and_then(|opts| { + if let RemoteConnectionOptions::Wsl(wsl) = opts { + Some(wsl.distro_name) + } else { + None + } + }); let toolchains = project.read(cx).available_toolchains( ProjectPath { @@ -383,6 +393,7 @@ pub fn python_env_kernel_specifications( .flatten() .chain(toolchains.toolchains) .map(|toolchain| { + let wsl_distro = wsl_distro.clone(); background_executor.spawn(async move { // For remote projects, we assume python is available assuming toolchain is reported. // We can skip the `ipykernel` check or run it remotely. @@ -390,10 +401,6 @@ pub fn python_env_kernel_specifications( // `new_smol_command` runs locally. We need to run remotely if `is_remote`. if is_remote { - log::info!( - "python_env_kernel_specifications: returning SshRemote for toolchain {}", - toolchain.name - ); let default_kernelspec = JupyterKernelspec { argv: vec![ toolchain.path.to_string(), @@ -409,6 +416,22 @@ pub fn python_env_kernel_specifications( env: None, }; + if let Some(distro) = wsl_distro { + log::debug!( + "python_env_kernel_specifications: returning WslRemote for toolchain {}", + toolchain.name + ); + return Some(KernelSpecification::WslRemote(WslKernelSpecification { + name: toolchain.name.to_string(), + kernelspec: default_kernelspec, + distro, + })); + } + + log::debug!( + "python_env_kernel_specifications: returning SshRemote for toolchain {}", + toolchain.name + ); return Some(KernelSpecification::SshRemote( SshRemoteKernelSpecification { name: format!("Remote {}", toolchain.name), diff --git a/crates/repl/src/kernels/wsl_kernel.rs b/crates/repl/src/kernels/wsl_kernel.rs index 34340c74feeb76cc4822a6ca5d669693cc448334..d9ac05c5fc8c2cb756898ff449d6714b78cb7997 100644 --- a/crates/repl/src/kernels/wsl_kernel.rs +++ b/crates/repl/src/kernels/wsl_kernel.rs @@ -274,7 +274,23 @@ impl WslRunningKernel { cd_command, set_env_command, arg_string, arg_string, arg_string, arg_string ) } else { - quote_posix_shell_arguments(&kernel_args)? + let args_string = quote_posix_shell_arguments(&resolved_argv)?; + + let cd_command = if let Some(wd) = wsl_working_directory.as_ref() { + let quoted_wd = shlex::try_quote(wd) + .map(|quoted| quoted.into_owned())?; + format!("cd {quoted_wd} && ") + } else { + String::new() + }; + + let env_prefix_inline = if !env_assignments.is_empty() { + format!("env {} ", env_assignments.join(" ")) + } else { + String::new() + }; + + format!("{cd_command}exec {env_prefix_inline}{args_string}") }; cmd.arg("bash") @@ -578,8 +594,20 @@ pub async fn wsl_kernel_specifications( }) }) .collect::>(); + } else if let Err(e) = + serde_json::from_str::(&json_str) + { + log::error!( + "wsl_kernel_specifications parse error: {} \nJSON: {}", + e, + json_str + ); } + } else { + log::error!("wsl_kernel_specifications command failed"); } + } else if let Err(e) = output { + log::error!("wsl_kernel_specifications command execution failed: {}", e); } Vec::new() diff --git a/crates/repl/src/repl_store.rs b/crates/repl/src/repl_store.rs index 8da94eaa7fe40e28a1d6336a648d7eae5c6767ae..ff0a2793617982e75d6c81d6c3a180d2f9b3c8ee 100644 --- a/crates/repl/src/repl_store.rs +++ b/crates/repl/src/repl_store.rs @@ -8,6 +8,7 @@ use gpui::{App, Context, Entity, EntityId, Global, SharedString, Subscription, T use jupyter_websocket_client::RemoteServer; use language::{Language, LanguageName}; use project::{Fs, Project, ProjectPath, WorktreeId}; +use remote::RemoteConnectionOptions; use settings::{Settings, SettingsStore}; use util::rel_path::RelPath; @@ -144,6 +145,14 @@ impl ReplStore { cx: &mut Context, ) -> Task> { let is_remote = project.read(cx).is_remote(); + // WSL does require access to global kernel specs, so we only exclude remote worktrees that aren't WSL. + // TODO: a better way to handle WSL vs SSH/remote projects, + let is_wsl_remote = project + .read(cx) + .remote_connection_options(cx) + .map_or(false, |opts| { + matches!(opts, RemoteConnectionOptions::Wsl(_)) + }); let kernel_specifications = python_env_kernel_specifications(project, worktree_id, cx); let active_toolchain = project.read(cx).active_toolchain( ProjectPath { @@ -168,7 +177,7 @@ impl ReplStore { this.active_python_toolchain_for_worktree .insert(worktree_id, path); } - if is_remote { + if is_remote && !is_wsl_remote { this.remote_worktrees.insert(worktree_id); } else { this.remote_worktrees.remove(&worktree_id);