Setup activation scripts in python toolchain
This commit is contained in:
parent
c9eb9a9e41
commit
d2f2142127
8 changed files with 109 additions and 249 deletions
|
@ -2,6 +2,7 @@ use anyhow::{Context as _, ensure};
|
|||
use anyhow::{Result, anyhow};
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use futures::AsyncBufReadExt;
|
||||
use gpui::{App, Task};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use language::Toolchain;
|
||||
|
@ -30,8 +31,6 @@ use std::{
|
|||
borrow::Cow,
|
||||
ffi::OsString,
|
||||
fmt::Write,
|
||||
fs,
|
||||
io::{self, BufRead},
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
@ -741,14 +740,16 @@ fn env_priority(kind: Option<PythonEnvironmentKind>) -> usize {
|
|||
/// Return the name of environment declared in <worktree-root/.venv.
|
||||
///
|
||||
/// https://virtualfish.readthedocs.io/en/latest/plugins.html#auto-activation-auto-activation
|
||||
fn get_worktree_venv_declaration(worktree_root: &Path) -> Option<String> {
|
||||
fs::File::open(worktree_root.join(".venv"))
|
||||
.and_then(|file| {
|
||||
let mut venv_name = String::new();
|
||||
io::BufReader::new(file).read_line(&mut venv_name)?;
|
||||
Ok(venv_name.trim().to_string())
|
||||
})
|
||||
.ok()
|
||||
async fn get_worktree_venv_declaration(worktree_root: &Path) -> Option<String> {
|
||||
let file = async_fs::File::open(worktree_root.join(".venv"))
|
||||
.await
|
||||
.ok()?;
|
||||
let mut venv_name = String::new();
|
||||
smol::io::BufReader::new(file)
|
||||
.read_line(&mut venv_name)
|
||||
.await
|
||||
.ok()?;
|
||||
Some(venv_name.trim().to_string())
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
|
@ -791,7 +792,7 @@ impl ToolchainLister for PythonToolchainProvider {
|
|||
.map_or(Vec::new(), |mut guard| std::mem::take(&mut guard));
|
||||
|
||||
let wr = worktree_root;
|
||||
let wr_venv = get_worktree_venv_declaration(&wr);
|
||||
let wr_venv = get_worktree_venv_declaration(&wr).await;
|
||||
// Sort detected environments by:
|
||||
// environment name matching activation file (<workdir>/.venv)
|
||||
// environment project dir matching worktree_root
|
||||
|
@ -856,7 +857,7 @@ impl ToolchainLister for PythonToolchainProvider {
|
|||
.into_iter()
|
||||
.filter_map(|toolchain| {
|
||||
let mut name = String::from("Python");
|
||||
if let Some(ref version) = toolchain.version {
|
||||
if let Some(version) = &toolchain.version {
|
||||
_ = write!(name, " {version}");
|
||||
}
|
||||
|
||||
|
@ -876,13 +877,46 @@ impl ToolchainLister for PythonToolchainProvider {
|
|||
name: name.into(),
|
||||
path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
|
||||
language_name: LanguageName::new("Python"),
|
||||
as_json: serde_json::to_value(toolchain).ok()?,
|
||||
activation_script: std::iter::once((
|
||||
ShellKind::Fish,
|
||||
"echo python recognized a venv and injected a fish startup command"
|
||||
.to_owned(),
|
||||
))
|
||||
.collect(),
|
||||
as_json: serde_json::to_value(toolchain.clone()).ok()?,
|
||||
activation_script: match (toolchain.kind, toolchain.prefix) {
|
||||
(Some(PythonEnvironmentKind::Venv), Some(prefix)) => [
|
||||
(
|
||||
ShellKind::Fish,
|
||||
"source",
|
||||
prefix.join(BINARY_DIR).join("activate.fish"),
|
||||
),
|
||||
(
|
||||
ShellKind::Powershell,
|
||||
".",
|
||||
prefix.join(BINARY_DIR).join("activate.ps1"),
|
||||
),
|
||||
(
|
||||
ShellKind::Nushell,
|
||||
"overlay use",
|
||||
prefix.join(BINARY_DIR).join("activate.nu"),
|
||||
),
|
||||
(
|
||||
ShellKind::Posix,
|
||||
".",
|
||||
prefix.join(BINARY_DIR).join("activate"),
|
||||
),
|
||||
(
|
||||
ShellKind::Cmd,
|
||||
".",
|
||||
prefix.join(BINARY_DIR).join("activate.bat"),
|
||||
),
|
||||
(
|
||||
ShellKind::Csh,
|
||||
".",
|
||||
prefix.join(BINARY_DIR).join("activate.csh"),
|
||||
),
|
||||
]
|
||||
.into_iter()
|
||||
.filter(|(_, _, path)| path.exists() && path.is_file())
|
||||
.map(|(kind, cmd, path)| (kind, format!("{cmd} {}", path.display())))
|
||||
.collect(),
|
||||
_ => Default::default(),
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -1694,180 +1728,3 @@ mod tests {
|
|||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
fn python_venv_directory(
|
||||
abs_path: Arc<Path>,
|
||||
venv_settings: VenvSettings,
|
||||
cx: &Context<Project>,
|
||||
) -> Task<Option<PathBuf>> {
|
||||
cx.spawn(async move |this, cx| {
|
||||
if let Some((worktree, relative_path)) = this
|
||||
.update(cx, |this, cx| this.find_worktree(&abs_path, cx))
|
||||
.ok()?
|
||||
{
|
||||
let toolchain = this
|
||||
.update(cx, |this, cx| {
|
||||
this.active_toolchain(
|
||||
ProjectPath {
|
||||
worktree_id: worktree.read(cx).id(),
|
||||
path: relative_path.into(),
|
||||
},
|
||||
LanguageName::new("Python"),
|
||||
cx,
|
||||
)
|
||||
})
|
||||
.ok()?
|
||||
.await;
|
||||
|
||||
if let Some(toolchain) = toolchain {
|
||||
let toolchain_path = Path::new(toolchain.path.as_ref());
|
||||
return Some(toolchain_path.parent()?.parent()?.to_path_buf());
|
||||
}
|
||||
}
|
||||
let venv_settings = venv_settings.as_option()?;
|
||||
this.update(cx, move |this, cx| {
|
||||
if let Some(path) = this.find_venv_in_worktree(&abs_path, &venv_settings, cx) {
|
||||
return Some(path);
|
||||
}
|
||||
this.find_venv_on_filesystem(&abs_path, &venv_settings, cx)
|
||||
})
|
||||
.ok()
|
||||
.flatten()
|
||||
})
|
||||
}
|
||||
|
||||
fn find_venv_in_worktree(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
venv_settings: &terminal_settings::VenvSettingsContent,
|
||||
cx: &App,
|
||||
) -> Option<PathBuf> {
|
||||
venv_settings
|
||||
.directories
|
||||
.iter()
|
||||
.map(|name| abs_path.join(name))
|
||||
.find(|venv_path| {
|
||||
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
|
||||
self.find_worktree(&bin_path, cx)
|
||||
.and_then(|(worktree, relative_path)| {
|
||||
worktree.read(cx).entry_for_path(&relative_path)
|
||||
})
|
||||
.is_some_and(|entry| entry.is_dir())
|
||||
})
|
||||
}
|
||||
|
||||
fn find_venv_on_filesystem(
|
||||
&self,
|
||||
abs_path: &Path,
|
||||
venv_settings: &terminal_settings::VenvSettingsContent,
|
||||
cx: &App,
|
||||
) -> Option<PathBuf> {
|
||||
let (worktree, _) = self.find_worktree(abs_path, cx)?;
|
||||
let fs = worktree.read(cx).as_local()?.fs();
|
||||
venv_settings
|
||||
.directories
|
||||
.iter()
|
||||
.map(|name| abs_path.join(name))
|
||||
.find(|venv_path| {
|
||||
let bin_path = venv_path.join(PYTHON_VENV_BIN_DIR);
|
||||
// One-time synchronous check is acceptable for terminal/task initialization
|
||||
smol::block_on(fs.metadata(&bin_path))
|
||||
.ok()
|
||||
.flatten()
|
||||
.map_or(false, |meta| meta.is_dir)
|
||||
})
|
||||
}
|
||||
|
||||
fn activate_script_kind(shell: Option<&str>) -> ActivateScript {
|
||||
let shell_env = std::env::var("SHELL").ok();
|
||||
let shell_path = shell.or_else(|| shell_env.as_deref());
|
||||
let shell = std::path::Path::new(shell_path.unwrap_or(""))
|
||||
.file_name()
|
||||
.and_then(|name| name.to_str())
|
||||
.unwrap_or("");
|
||||
match shell {
|
||||
"fish" => ActivateScript::Fish,
|
||||
"tcsh" => ActivateScript::Csh,
|
||||
"nu" => ActivateScript::Nushell,
|
||||
"powershell" | "pwsh" => ActivateScript::PowerShell,
|
||||
_ => ActivateScript::Default,
|
||||
}
|
||||
}
|
||||
|
||||
fn python_activate_command(
|
||||
&self,
|
||||
venv_base_directory: &Path,
|
||||
venv_settings: &VenvSettings,
|
||||
shell: &Shell,
|
||||
cx: &mut App,
|
||||
) -> Task<Option<String>> {
|
||||
let Some(venv_settings) = venv_settings.as_option() else {
|
||||
return Task::ready(None);
|
||||
};
|
||||
let activate_keyword = match venv_settings.activate_script {
|
||||
terminal_settings::ActivateScript::Default => match std::env::consts::OS {
|
||||
"windows" => ".",
|
||||
_ => ".",
|
||||
},
|
||||
terminal_settings::ActivateScript::Nushell => "overlay use",
|
||||
terminal_settings::ActivateScript::PowerShell => ".",
|
||||
terminal_settings::ActivateScript::Pyenv => "pyenv",
|
||||
_ => "source",
|
||||
};
|
||||
let script_kind = if venv_settings.activate_script == terminal_settings::ActivateScript::Default
|
||||
{
|
||||
match shell {
|
||||
Shell::Program(program) => Self::activate_script_kind(Some(program)),
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: _,
|
||||
title_override: _,
|
||||
} => Self::activate_script_kind(Some(program)),
|
||||
Shell::System => Self::activate_script_kind(None),
|
||||
}
|
||||
} else {
|
||||
venv_settings.activate_script
|
||||
};
|
||||
|
||||
let activate_script_name = match script_kind {
|
||||
terminal_settings::ActivateScript::Default | terminal_settings::ActivateScript::Pyenv => {
|
||||
"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 line_ending = match std::env::consts::OS {
|
||||
"windows" => "\r",
|
||||
_ => "\n",
|
||||
};
|
||||
|
||||
if venv_settings.venv_name.is_empty() {
|
||||
let path = venv_base_directory
|
||||
.join(PYTHON_VENV_BIN_DIR)
|
||||
.join(activate_script_name)
|
||||
.to_string_lossy()
|
||||
.to_string();
|
||||
|
||||
let is_valid_path = self.resolve_abs_path(path.as_ref(), cx);
|
||||
cx.background_spawn(async move {
|
||||
let quoted = shlex::try_quote(&path).ok()?;
|
||||
if is_valid_path.await.is_some_and(|meta| meta.is_file()) {
|
||||
Some(format!(
|
||||
"{} {} ; clear{}",
|
||||
activate_keyword, quoted, line_ending
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Task::ready(Some(format!(
|
||||
"{activate_keyword} {activate_script_name} {name}; clear{line_ending}",
|
||||
name = venv_settings.venv_name
|
||||
)))
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue