Do not enable venv in terminal for bash-like oneshot task invocations (#8444)
Release Notes: - Work around #8334 by only activating venv in the terminal not in tasks (see #8440 for a proper solution) - To use venv modify your tasks in the following way: ```json { "label": "Python main.py", "command": "sh", "args": ["-c", "source .venv/bin/activate && python3 main.py"] } ``` --------- Co-authored-by: Kirill Bulatov <kirill@zed.dev>
This commit is contained in:
parent
cd61297740
commit
85fdcef564
4 changed files with 109 additions and 49 deletions
13
Cargo.lock
generated
13
Cargo.lock
generated
|
@ -87,10 +87,11 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "alacritty_terminal"
|
name = "alacritty_terminal"
|
||||||
version = "0.22.1-dev"
|
version = "0.23.0-rc1"
|
||||||
source = "git+https://github.com/alacritty/alacritty?rev=992011a4cd9a35f197acc0a0bd430d89a0d01013#992011a4cd9a35f197acc0a0bd430d89a0d01013"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bc2c16faa5425a10be102dda76f73d76049b44746e18ddeefc44d78bbe76cbce"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64 0.21.4",
|
"base64 0.22.0",
|
||||||
"bitflags 2.4.2",
|
"bitflags 2.4.2",
|
||||||
"home",
|
"home",
|
||||||
"libc",
|
"libc",
|
||||||
|
@ -1292,6 +1293,12 @@ version = "0.21.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
checksum = "9ba43ea6f343b788c8764558649e08df62f86c6ef251fdaeb1ffd010a9ae50a2"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "base64"
|
||||||
|
version = "0.22.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "base64-simd"
|
name = "base64-simd"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
use crate::Project;
|
use crate::Project;
|
||||||
|
use collections::HashMap;
|
||||||
use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
|
use gpui::{AnyWindowHandle, Context, Entity, Model, ModelContext, WeakModel};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::channel::bounded;
|
use smol::channel::bounded;
|
||||||
|
@ -7,6 +8,7 @@ use terminal::{
|
||||||
terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
|
terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
|
||||||
SpawnTask, TaskState, Terminal, TerminalBuilder,
|
SpawnTask, TaskState, Terminal, TerminalBuilder,
|
||||||
};
|
};
|
||||||
|
use util::ResultExt;
|
||||||
|
|
||||||
// #[cfg(target_os = "macos")]
|
// #[cfg(target_os = "macos")]
|
||||||
// use std::os::unix::ffi::OsStrExt;
|
// use std::os::unix::ffi::OsStrExt;
|
||||||
|
@ -28,12 +30,27 @@ impl Project {
|
||||||
"creating terminals as a guest is not supported yet"
|
"creating terminals as a guest is not supported yet"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
let is_terminal = spawn_task.is_none();
|
||||||
let settings = TerminalSettings::get_global(cx);
|
let settings = TerminalSettings::get_global(cx);
|
||||||
let python_settings = settings.detect_venv.clone();
|
let python_settings = settings.detect_venv.clone();
|
||||||
let (completion_tx, completion_rx) = bounded(1);
|
let (completion_tx, completion_rx) = bounded(1);
|
||||||
|
|
||||||
let mut env = settings.env.clone();
|
let mut env = settings.env.clone();
|
||||||
|
// Alacritty uses parent project's working directory when no working directory is provided
|
||||||
|
// https://github.com/alacritty/alacritty/blob/fd1a3cc79192d1d03839f0fd8c72e1f8d0fce42e/extra/man/alacritty.5.scd?plain=1#L47-L52
|
||||||
|
|
||||||
|
let current_directory = std::env::current_dir().ok();
|
||||||
|
let venv_base_directory = working_directory
|
||||||
|
.as_deref()
|
||||||
|
.or(current_directory.as_deref())
|
||||||
|
.unwrap_or_else(|| Path::new("")); // if everything fails, use relative path
|
||||||
|
|
||||||
let (spawn_task, shell) = if let Some(spawn_task) = spawn_task {
|
let (spawn_task, shell) = if let Some(spawn_task) = spawn_task {
|
||||||
env.extend(spawn_task.env);
|
env.extend(spawn_task.env);
|
||||||
|
// Activate minimal Python virtual environment
|
||||||
|
if let Some(python_settings) = &python_settings.as_option() {
|
||||||
|
self.set_python_venv_path_for_tasks(python_settings, venv_base_directory, &mut env);
|
||||||
|
}
|
||||||
(
|
(
|
||||||
Some(TaskState {
|
Some(TaskState {
|
||||||
id: spawn_task.id,
|
id: spawn_task.id,
|
||||||
|
@ -82,16 +99,20 @@ impl Project {
|
||||||
})
|
})
|
||||||
.detach();
|
.detach();
|
||||||
|
|
||||||
if let Some(python_settings) = &python_settings.as_option() {
|
// if the terminal is not a task, activate full Python virtual environment
|
||||||
let activate_command = Project::get_activate_command(python_settings);
|
if is_terminal {
|
||||||
let activate_script_path =
|
if let Some(python_settings) = &python_settings.as_option() {
|
||||||
self.find_activate_script_path(python_settings, working_directory);
|
if let Some(activate_script_path) =
|
||||||
self.activate_python_virtual_environment(
|
self.find_activate_script_path(python_settings, venv_base_directory)
|
||||||
activate_command,
|
{
|
||||||
activate_script_path,
|
self.activate_python_virtual_environment(
|
||||||
&terminal_handle,
|
Project::get_activate_command(python_settings),
|
||||||
cx,
|
activate_script_path,
|
||||||
);
|
&terminal_handle,
|
||||||
|
cx,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
terminal_handle
|
terminal_handle
|
||||||
});
|
});
|
||||||
|
@ -102,12 +123,8 @@ impl Project {
|
||||||
pub fn find_activate_script_path(
|
pub fn find_activate_script_path(
|
||||||
&mut self,
|
&mut self,
|
||||||
settings: &VenvSettingsContent,
|
settings: &VenvSettingsContent,
|
||||||
working_directory: Option<PathBuf>,
|
venv_base_directory: &Path,
|
||||||
) -> Option<PathBuf> {
|
) -> Option<PathBuf> {
|
||||||
// When we are unable to resolve the working directory, the terminal builder
|
|
||||||
// defaults to '/'. We should probably encode this directly somewhere, but for
|
|
||||||
// now, let's just hard code it here.
|
|
||||||
let working_directory = working_directory.unwrap_or_else(|| Path::new("/").to_path_buf());
|
|
||||||
let activate_script_name = match settings.activate_script {
|
let activate_script_name = match settings.activate_script {
|
||||||
terminal_settings::ActivateScript::Default => "activate",
|
terminal_settings::ActivateScript::Default => "activate",
|
||||||
terminal_settings::ActivateScript::Csh => "activate.csh",
|
terminal_settings::ActivateScript::Csh => "activate.csh",
|
||||||
|
@ -115,17 +132,53 @@ impl Project {
|
||||||
terminal_settings::ActivateScript::Nushell => "activate.nu",
|
terminal_settings::ActivateScript::Nushell => "activate.nu",
|
||||||
};
|
};
|
||||||
|
|
||||||
for virtual_environment_name in settings.directories {
|
settings
|
||||||
let mut path = working_directory.join(virtual_environment_name);
|
.directories
|
||||||
path.push("bin/");
|
.into_iter()
|
||||||
path.push(activate_script_name);
|
.find_map(|virtual_environment_name| {
|
||||||
|
let path = venv_base_directory
|
||||||
|
.join(virtual_environment_name)
|
||||||
|
.join("bin")
|
||||||
|
.join(activate_script_name);
|
||||||
|
path.exists().then_some(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
if path.exists() {
|
pub fn set_python_venv_path_for_tasks(
|
||||||
return Some(path);
|
&mut self,
|
||||||
|
settings: &VenvSettingsContent,
|
||||||
|
venv_base_directory: &Path,
|
||||||
|
env: &mut HashMap<String, String>,
|
||||||
|
) {
|
||||||
|
let activate_path = settings
|
||||||
|
.directories
|
||||||
|
.into_iter()
|
||||||
|
.find_map(|virtual_environment_name| {
|
||||||
|
let path = venv_base_directory.join(virtual_environment_name);
|
||||||
|
path.exists().then_some(path)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(path) = activate_path {
|
||||||
|
// Some tools use VIRTUAL_ENV to detect the virtual environment
|
||||||
|
env.insert(
|
||||||
|
"VIRTUAL_ENV".to_string(),
|
||||||
|
path.to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let path_bin = path.join("bin");
|
||||||
|
// We need to set the PATH to include the virtual environment's bin directory
|
||||||
|
if let Some(paths) = std::env::var_os("PATH") {
|
||||||
|
let paths = std::iter::once(path_bin).chain(std::env::split_paths(&paths));
|
||||||
|
if let Some(new_path) = std::env::join_paths(paths).log_err() {
|
||||||
|
env.insert("PATH".to_string(), new_path.to_string_lossy().to_string());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
env.insert(
|
||||||
|
"PATH".to_string(),
|
||||||
|
path.join("bin").to_string_lossy().to_string(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_activate_command(settings: &VenvSettingsContent) -> &'static str {
|
fn get_activate_command(settings: &VenvSettingsContent) -> &'static str {
|
||||||
|
@ -138,22 +191,20 @@ impl Project {
|
||||||
fn activate_python_virtual_environment(
|
fn activate_python_virtual_environment(
|
||||||
&mut self,
|
&mut self,
|
||||||
activate_command: &'static str,
|
activate_command: &'static str,
|
||||||
activate_script: Option<PathBuf>,
|
activate_script: PathBuf,
|
||||||
terminal_handle: &Model<Terminal>,
|
terminal_handle: &Model<Terminal>,
|
||||||
cx: &mut ModelContext<Project>,
|
cx: &mut ModelContext<Project>,
|
||||||
) {
|
) {
|
||||||
if let Some(activate_script) = activate_script {
|
// Paths are not strings so we need to jump through some hoops to format the command without `format!`
|
||||||
// Paths are not strings so we need to jump through some hoops to format the command without `format!`
|
let mut command = Vec::from(activate_command.as_bytes());
|
||||||
let mut command = Vec::from(activate_command.as_bytes());
|
command.push(b' ');
|
||||||
command.push(b' ');
|
// Wrapping path in double quotes to catch spaces in folder name
|
||||||
// Wrapping path in double quotes to catch spaces in folder name
|
command.extend_from_slice(b"\"");
|
||||||
command.extend_from_slice(b"\"");
|
command.extend_from_slice(activate_script.as_os_str().as_encoded_bytes());
|
||||||
command.extend_from_slice(activate_script.as_os_str().as_encoded_bytes());
|
command.extend_from_slice(b"\"");
|
||||||
command.extend_from_slice(b"\"");
|
command.push(b'\n');
|
||||||
command.push(b'\n');
|
|
||||||
|
|
||||||
terminal_handle.update(cx, |this, _| this.input_bytes(command));
|
terminal_handle.update(cx, |this, _| this.input_bytes(command));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
|
pub fn local_terminal_handles(&self) -> &Vec<WeakModel<terminal::Terminal>> {
|
||||||
|
|
|
@ -15,7 +15,7 @@ doctest = false
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
# TODO: when new version of this crate is released, change it
|
# TODO: when new version of this crate is released, change it
|
||||||
alacritty_terminal = { git = "https://github.com/alacritty/alacritty", rev = "992011a4cd9a35f197acc0a0bd430d89a0d01013" }
|
alacritty_terminal = "0.23.0-rc1"
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
collections.workspace = true
|
collections.workspace = true
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
|
|
|
@ -310,13 +310,19 @@ impl TerminalBuilder {
|
||||||
working_directory: Option<PathBuf>,
|
working_directory: Option<PathBuf>,
|
||||||
task: Option<TaskState>,
|
task: Option<TaskState>,
|
||||||
shell: Shell,
|
shell: Shell,
|
||||||
env: HashMap<String, String>,
|
mut env: HashMap<String, String>,
|
||||||
blink_settings: Option<TerminalBlink>,
|
blink_settings: Option<TerminalBlink>,
|
||||||
alternate_scroll: AlternateScroll,
|
alternate_scroll: AlternateScroll,
|
||||||
max_scroll_history_lines: Option<usize>,
|
max_scroll_history_lines: Option<usize>,
|
||||||
window: AnyWindowHandle,
|
window: AnyWindowHandle,
|
||||||
completion_tx: Sender<()>,
|
completion_tx: Sender<()>,
|
||||||
) -> Result<TerminalBuilder> {
|
) -> Result<TerminalBuilder> {
|
||||||
|
// TODO: Properly set the current locale,
|
||||||
|
env.entry("LC_ALL".to_string())
|
||||||
|
.or_insert_with(|| "en_US.UTF-8".to_string());
|
||||||
|
|
||||||
|
env.insert("ZED_TERM".to_string(), "true".to_string());
|
||||||
|
|
||||||
let pty_options = {
|
let pty_options = {
|
||||||
let alac_shell = match shell.clone() {
|
let alac_shell = match shell.clone() {
|
||||||
Shell::System => None,
|
Shell::System => None,
|
||||||
|
@ -332,20 +338,13 @@ impl TerminalBuilder {
|
||||||
shell: alac_shell,
|
shell: alac_shell,
|
||||||
working_directory: working_directory.clone(),
|
working_directory: working_directory.clone(),
|
||||||
hold: false,
|
hold: false,
|
||||||
|
env: env.into_iter().collect(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// First, setup Alacritty's env
|
// Setup Alacritty's env
|
||||||
setup_env();
|
setup_env();
|
||||||
|
|
||||||
// Then setup configured environment variables
|
|
||||||
for (key, value) in env {
|
|
||||||
std::env::set_var(key, value);
|
|
||||||
}
|
|
||||||
//TODO: Properly set the current locale,
|
|
||||||
std::env::set_var("LC_ALL", "en_US.UTF-8");
|
|
||||||
std::env::set_var("ZED_TERM", "true");
|
|
||||||
|
|
||||||
let scrolling_history = if task.is_some() {
|
let scrolling_history = if task.is_some() {
|
||||||
// Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling.
|
// Tasks like `cargo build --all` may produce a lot of output, ergo allow maximum scrolling.
|
||||||
// After the task finishes, we do not allow appending to that terminal, so small tasks output should not
|
// After the task finishes, we do not allow appending to that terminal, so small tasks output should not
|
||||||
|
@ -650,6 +649,9 @@ impl Terminal {
|
||||||
self.events
|
self.events
|
||||||
.push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
|
.push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
|
||||||
}
|
}
|
||||||
|
AlacTermEvent::ChildExit(_) => {
|
||||||
|
// TODO: Handle child exit
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue