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
Цей коміт міститься в:
l0ng-ai authored and GitHub committed 2026-11-05 12:49:13 +08:00
1 джерело fec391b9c1
коміт bb4e2db47f
4 files changed
+47 -5

No files matched your search

+9 -2
Переглянути файл
@@ -184,6 +184,12 @@ fn python(root: Option<&Path>) -> Vec<Sdk> {
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<Sdk> {
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<Sdk> {
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})"),
}
}
+1 -1
Переглянути файл
@@ -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)]
+17
Переглянути файл
@@ -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();