From bb4e2db47fa4c6f37c67f10a71ced01f16b65665 Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Thu, 5 Nov 2026 12:49:13 +0800 Subject: [PATCH] fix(python): name the project interpreter in the status bar wherever it lives (#1461) An interpreter that detection doesn't look for (a virtualenv in a subfolder such as backend/.venv, or one set by path) was missing from sdk_detect, so the status bar and interpreter menu showed its raw path. sdk_detect for Python now adds the project's interpreter when detection didn't find it, named by the same rules, and a project-local .venv in a subfolder is named after that folder. Fixes #1428 --- crates/ide-host/src/ops/sdk.rs | 22 ++++++++++++++++++++-- crates/ide-sdk/src/discover.rs | 11 +++++++++-- crates/ide-sdk/src/lib.rs | 2 +- crates/ide-sdk/src/tests.rs | 17 +++++++++++++++++ 4 files changed, 47 insertions(+), 5 deletions(-) diff --git a/crates/ide-host/src/ops/sdk.rs b/crates/ide-host/src/ops/sdk.rs index 535426d8..702c35c2 100644 --- a/crates/ide-host/src/ops/sdk.rs +++ b/crates/ide-host/src/ops/sdk.rs @@ -23,8 +23,26 @@ pub struct LocatedTools { /// Installed interpreters / toolchains of `kind`; project-local ones first. pub async fn sdk_detect(host: &Host, window: &str, kind: SdkKind) -> IdeResult> { host.require_trust(window)?; - let root = host.project(window).ok().map(|p| p.root.clone()); - tokio::task::spawn_blocking(move || ide_sdk::detect(kind, root.as_deref())).await.map_err(join_err) + let project = host.project(window).ok(); + let root = project.as_ref().map(|p| p.root.clone()); + // The interpreter the project uses is a choice even where detection doesn't look (a + // virtualenv in a subfolder, one picked by path), so it shows by name and version. + let current = match (&project, kind) { + (Some(p), SdkKind::Python) => ide_sdk::apply::python_interpreter(&p.root, &p.settings.effective()), + _ => None, + }; + tokio::task::spawn_blocking(move || { + let mut sdks = ide_sdk::detect(kind, root.as_deref()); + if let Some(path) = current + && !sdks.iter().any(|s| *s.path == *path) + && let Some(sdk) = ide_sdk::python_interpreter_sdk(&path, root.as_deref()) + { + sdks.push(sdk); + } + sdks + }) + .await + .map_err(join_err) } /// The interpreter the project uses: the configured one, else its virtualenv. diff --git a/crates/ide-sdk/src/discover.rs b/crates/ide-sdk/src/discover.rs index e18b2dae..d5141e27 100644 --- a/crates/ide-sdk/src/discover.rs +++ b/crates/ide-sdk/src/discover.rs @@ -184,6 +184,12 @@ fn python(root: Option<&Path>) -> Vec { par_map(&candidates, |(path, source)| python_sdk(path, source, root)) } +/// Interpreter `path` (one the project is set to, wherever it lives) as an SDK, named +/// like the detected ones. +pub fn python_interpreter_sdk(path: &Path, root: Option<&Path>) -> Option { + python_sdk(path, if venv_of(path).is_some() { "venv" } else { "system" }, root) +} + pub(crate) fn python_sdk(path: &Path, source: &str, root: Option<&Path>) -> Option { let version = query(path, &["--version"], VERSION_TIMEOUT) .and_then(|v| v.strip_prefix("Python ").map(str::to_owned)) @@ -193,10 +199,11 @@ pub(crate) fn python_sdk(path: &Path, source: &str, root: Option<&Path>) -> Opti let label = match (&venv, source) { (Some(home), _) => { let dir = home.file_name().map(|n| n.to_string_lossy().into_owned()).unwrap_or_default(); - // `.venv` inside a project reads better as the project name, like PyCharm. + // `.venv` inside a project reads better as the folder that holds it (the project, + // or a member such as `backend`), like PyCharm. let owner = home.parent().and_then(|p| p.file_name()).map(|n| n.to_string_lossy().into_owned()); match (dir.starts_with('.') || dir == "venv" || dir == "env", owner, root) { - (true, Some(owner), Some(root)) if home.parent() == Some(root) => format!("Python {short} ({owner})"), + (true, Some(owner), Some(root)) if home.starts_with(root) => format!("Python {short} ({owner})"), _ => format!("Python {short} ({dir})"), } } diff --git a/crates/ide-sdk/src/lib.rs b/crates/ide-sdk/src/lib.rs index 35d52417..90c5cae2 100644 --- a/crates/ide-sdk/src/lib.rs +++ b/crates/ide-sdk/src/lib.rs @@ -28,7 +28,7 @@ use serde::{Deserialize, Serialize}; use specta::Type; use std::path::{Path, PathBuf}; -pub use discover::{detect, jdk_sdk}; +pub use discover::{detect, jdk_sdk, python_interpreter_sdk}; /// What [`detect`] looks for. #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Type)] diff --git a/crates/ide-sdk/src/tests.rs b/crates/ide-sdk/src/tests.rs index 7a8bd103..84a45bb8 100644 --- a/crates/ide-sdk/src/tests.rs +++ b/crates/ide-sdk/src/tests.rs @@ -76,6 +76,23 @@ fn project_venv_is_the_default_interpreter() { assert_eq!(python_interpreter(tmp.path(), &s).unwrap(), Path::new("/opt/py/bin/python3")); } +#[test] +fn an_interpreter_outside_detection_is_named_like_a_detected_one() { + let tmp = tempfile::tempdir().unwrap(); + let root = tmp.path().join("shop"); + let member = fake_venv(&root.join("backend"), ".venv"); + let sdk = crate::python_interpreter_sdk(&member, Some(&root)).unwrap(); + assert_eq!( + (sdk.name.as_str(), sdk.version.as_deref(), sdk.source.as_str()), + ("Python 3.12 (backend)", Some("3.12.1"), "venv") + ); + let own = fake_venv(&root, ".venv"); + assert_eq!(crate::python_interpreter_sdk(&own, Some(&root)).unwrap().name, "Python 3.12 (shop)"); + // Outside the project the folder name says which one it is. + let elsewhere = fake_venv(&tmp.path().join("other"), ".venv"); + assert_eq!(crate::python_interpreter_sdk(&elsewhere, Some(&root)).unwrap().name, "Python 3.12 (.venv)"); +} + #[test] fn pyright_gets_python_path_and_venv_env() { let tmp = tempfile::tempdir().unwrap();