diff --git a/crates/project/src/terminals.rs b/crates/project/src/terminals.rs index 17564b17dd4d6623d7ca72fadbd0aa8defd1f9cc..eaa897b53c09d427d53a0df6b01eb079ee147cd0 100644 --- a/crates/project/src/terminals.rs +++ b/crates/project/src/terminals.rs @@ -2,6 +2,7 @@ use anyhow::Result; use collections::HashMap; use gpui::{App, AppContext as _, Context, Entity, Task, WeakEntity}; +use futures::{FutureExt, future::Shared}; use itertools::Itertools as _; use language::LanguageName; use remote::RemoteClient; @@ -75,16 +76,6 @@ impl Project { let (completion_tx, completion_rx) = bounded(1); - // Start with the environment that we might have inherited from the Zed CLI. - let mut env = self - .environment - .read(cx) - .get_cli_environment() - .unwrap_or_default(); - // Then extend it with the explicit env variables from the settings, so they take - // precedence. - env.extend(settings.env); - let local_path = if is_via_remote { None } else { path.clone() }; let task_state = Some(TaskState { spawned_task: spawn_task.clone(), @@ -99,8 +90,12 @@ impl Project { .unwrap_or_else(get_default_system_shell), None => settings.shell.program(), }; - let is_windows = self.path_style(cx).is_windows(); + let shell_kind = ShellKind::new(&shell, is_windows); + + // Prepare a task for resolving the environment + let env_task = + self.resolve_directory_environment(&shell, path.clone(), remote_client.clone(), cx); let project_path_contexts = self .active_entry() @@ -120,7 +115,8 @@ impl Project { .collect::>(); let lang_registry = self.languages.clone(); cx.spawn(async move |project, cx| { - let shell_kind = ShellKind::new(&shell, is_windows); + let mut env = env_task.await.unwrap_or_default(); + env.extend(settings.env); let activation_script = maybe!(async { for toolchain in toolchains { @@ -295,17 +291,6 @@ impl Project { } let settings = TerminalSettings::get(settings_location, cx).clone(); let detect_venv = settings.detect_venv.as_option().is_some(); - - // Start with the environment that we might have inherited from the Zed CLI. - let mut env = self - .environment - .read(cx) - .get_cli_environment() - .unwrap_or_default(); - // Then extend it with the explicit env variables from the settings, so they take - // precedence. - env.extend(settings.env); - let local_path = if is_via_remote { None } else { path.clone() }; let project_path_contexts = self @@ -332,11 +317,17 @@ impl Project { .unwrap_or_else(get_default_system_shell), None => settings.shell.program(), }; - let shell_kind = ShellKind::new(&shell, self.path_style(cx).is_windows()); + // Prepare a task for resolving the environment + let env_task = + self.resolve_directory_environment(&shell, path.clone(), remote_client.clone(), cx); + let lang_registry = self.languages.clone(); cx.spawn(async move |project, cx| { + let mut env = env_task.await.unwrap_or_default(); + env.extend(settings.env); + let activation_script = maybe!(async { for toolchain in toolchains { let Some(toolchain) = toolchain.await else { @@ -469,9 +460,13 @@ impl Project { TerminalSettings::get(settings_location, cx) } - pub fn exec_in_shell(&self, command: String, cx: &App) -> Result { + pub fn exec_in_shell( + &self, + command: String, + cx: &mut Context, + ) -> Task> { let path = self.first_project_directory(cx); - let remote_client = self.remote_client.as_ref(); + let remote_client = self.remote_client.clone(); let settings = self.terminal_settings(&path, cx).clone(); let shell = remote_client .as_ref() @@ -482,43 +477,77 @@ impl Project { let builder = ShellBuilder::new(&shell, is_windows).non_interactive(); let (command, args) = builder.build(Some(command), &Vec::new()); - let mut env = self - .environment - .read(cx) - .get_cli_environment() - .unwrap_or_default(); - env.extend(settings.env); - - match remote_client { - Some(remote_client) => { - let command_template = - remote_client - .read(cx) - .build_command(Some(command), &args, &env, None, None)?; - let mut command = std::process::Command::new(command_template.program); - command.args(command_template.args); - command.envs(command_template.env); - Ok(command) - } - None => { - let mut command = std::process::Command::new(command); - command.args(args); - command.envs(env); - if let Some(path) = path { - command.current_dir(path); + let env_task = self.resolve_directory_environment( + &shell.program(), + path.as_ref().map(|p| Arc::from(&**p)), + remote_client.clone(), + cx, + ); + + cx.spawn(async move |project, cx| { + let mut env = env_task.await.unwrap_or_default(); + env.extend(settings.env); + + project.update(cx, move |_, cx| { + match remote_client { + Some(remote_client) => { + let command_template = remote_client.read(cx).build_command( + Some(command), + &args, + &env, + None, + None, + )?; + let mut command = std::process::Command::new(command_template.program); + command.args(command_template.args); + command.envs(command_template.env); + Ok(command) + } + None => { + let mut command = std::process::Command::new(command); + command.args(args); + command.envs(env); + if let Some(path) = path { + command.current_dir(path); + } + Ok(command) + } } - Ok(command) - } - } - .map(|mut process| { - util::set_pre_exec_to_start_new_session(&mut process); - smol::process::Command::from(process) + .map(|mut process| { + util::set_pre_exec_to_start_new_session(&mut process); + smol::process::Command::from(process) + }) + })? }) } pub fn local_terminal_handles(&self) -> &Vec> { &self.terminals.local_handles } + + fn resolve_directory_environment( + &self, + shell: &str, + path: Option>, + remote_client: Option>, + cx: &mut App, + ) -> Shared>>> { + if let Some(path) = &path { + let shell = Shell::Program(shell.to_string()); + self.environment + .update(cx, |project_env, cx| match &remote_client { + Some(remote_client) => project_env.remote_directory_environment( + &shell, + path.clone(), + remote_client.clone(), + cx, + ), + None => project_env.local_directory_environment(&shell, path.clone(), cx), + }) + } else { + Task::ready(None).shared() + } + } } fn create_remote_shell( @@ -528,12 +557,8 @@ fn create_remote_shell( remote_client: Entity, cx: &mut App, ) -> Result<(Shell, HashMap)> { - // Alacritty sets its terminfo to `alacritty`, this requiring hosts to have it installed - // to properly display colors. - // We do not have the luxury of assuming the host has it installed, - // so we set it to a default that does not break the highlighting via ssh. - env.entry("TERM".to_string()) - .or_insert_with(|| "xterm-256color".to_string()); + // Set default terminfo that does not break the highlighting via ssh. + env.insert("TERM".to_string(), "xterm-256color".to_string()); let (program, args) = match spawn_command { Some((program, args)) => (Some(program.clone()), args), diff --git a/crates/vim/src/command.rs b/crates/vim/src/command.rs index 9dc4ec999a47e6a0e8ab802761cab474ef81499b..4b66cb1aaa7e423d202dfe03c5f5ad1d315199c0 100644 --- a/crates/vim/src/command.rs +++ b/crates/vim/src/command.rs @@ -2194,21 +2194,23 @@ impl ShellExec { let Some(range) = input_range else { return }; - let Some(mut process) = project.read(cx).exec_in_shell(command, cx).log_err() else { - return; - }; - process.stdout(Stdio::piped()); - process.stderr(Stdio::piped()); - - if input_snapshot.is_some() { - process.stdin(Stdio::piped()); - } else { - process.stdin(Stdio::null()); - }; + let process_task = project.update(cx, |project, cx| project.exec_in_shell(command, cx)); let is_read = self.is_read; let task = cx.spawn_in(window, async move |vim, cx| { + let Some(mut process) = process_task.await.log_err() else { + return; + }; + process.stdout(Stdio::piped()); + process.stderr(Stdio::piped()); + + if input_snapshot.is_some() { + process.stdin(Stdio::piped()); + } else { + process.stdin(Stdio::null()); + }; + let Some(mut running) = process.spawn().log_err() else { vim.update_in(cx, |vim, window, cx| { vim.cancel_running_command(window, cx);