Add activation script support for terminals/toolchains
This commit is contained in:
parent
7b3d73d6fd
commit
8e11e6a03e
11 changed files with 98 additions and 32 deletions
|
@ -10,14 +10,14 @@ use std::{
|
|||
};
|
||||
|
||||
use async_trait::async_trait;
|
||||
use collections::HashMap;
|
||||
use collections::{FxHashMap, HashMap};
|
||||
use gpui::{AsyncApp, SharedString};
|
||||
use settings::WorktreeId;
|
||||
|
||||
use crate::{LanguageName, ManifestName};
|
||||
|
||||
/// Represents a single toolchain.
|
||||
#[derive(Clone, Debug, Eq)]
|
||||
#[derive(Clone, Eq, Debug)]
|
||||
pub struct Toolchain {
|
||||
/// User-facing label
|
||||
pub name: SharedString,
|
||||
|
@ -25,24 +25,41 @@ pub struct Toolchain {
|
|||
pub language_name: LanguageName,
|
||||
/// Full toolchain data (including language-specific details)
|
||||
pub as_json: serde_json::Value,
|
||||
/// shell -> script
|
||||
pub startup_script: FxHashMap<String, String>,
|
||||
}
|
||||
|
||||
impl std::hash::Hash for Toolchain {
|
||||
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||
self.name.hash(state);
|
||||
self.path.hash(state);
|
||||
self.language_name.hash(state);
|
||||
let Self {
|
||||
name,
|
||||
path,
|
||||
language_name,
|
||||
as_json: _,
|
||||
startup_script: _,
|
||||
} = self;
|
||||
name.hash(state);
|
||||
path.hash(state);
|
||||
language_name.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Toolchain {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
let Self {
|
||||
name,
|
||||
path,
|
||||
language_name,
|
||||
as_json: _,
|
||||
startup_script,
|
||||
} = self;
|
||||
// Do not use as_json for comparisons; it shouldn't impact equality, as it's not user-surfaced.
|
||||
// Thus, there could be multiple entries that look the same in the UI.
|
||||
(&self.name, &self.path, &self.language_name).eq(&(
|
||||
(name, path, language_name, startup_script).eq(&(
|
||||
&other.name,
|
||||
&other.path,
|
||||
&other.language_name,
|
||||
&other.startup_script,
|
||||
))
|
||||
}
|
||||
}
|
||||
|
@ -82,7 +99,7 @@ pub trait LocalLanguageToolchainStore: Send + Sync + 'static {
|
|||
) -> Option<Toolchain>;
|
||||
}
|
||||
|
||||
#[async_trait(?Send )]
|
||||
#[async_trait(?Send)]
|
||||
impl<T: LocalLanguageToolchainStore> LanguageToolchainStore for T {
|
||||
async fn active_toolchain(
|
||||
self: Arc<Self>,
|
||||
|
|
|
@ -878,6 +878,8 @@ impl ToolchainLister for PythonToolchainProvider {
|
|||
path: toolchain.executable.as_ref()?.to_str()?.to_owned().into(),
|
||||
language_name: LanguageName::new("Python"),
|
||||
as_json: serde_json::to_value(toolchain).ok()?,
|
||||
startup_script: std::iter::once(("fish".to_owned(), "test".to_owned()))
|
||||
.collect(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
|
@ -293,6 +293,7 @@ impl DapStore {
|
|||
binary.cwd.as_deref(),
|
||||
binary.envs,
|
||||
path_style,
|
||||
None,
|
||||
);
|
||||
|
||||
Ok(DebugAdapterBinary {
|
||||
|
|
|
@ -9204,6 +9204,7 @@ fn python_lang(fs: Arc<FakeFs>) -> Arc<Language> {
|
|||
path: venv_path.to_string_lossy().into_owned().into(),
|
||||
language_name: LanguageName(SharedString::new_static("Python")),
|
||||
as_json: serde_json::Value::Null,
|
||||
startup_script: Default::default(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ use std::{
|
|||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
use task::{Shell, ShellBuilder, SpawnInTerminal};
|
||||
use task::{Shell, ShellBuilder, SpawnInTerminal, system_shell};
|
||||
use terminal::{
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder, terminal_settings::TerminalSettings,
|
||||
};
|
||||
|
@ -173,6 +173,7 @@ impl Project {
|
|||
path.as_deref(),
|
||||
env,
|
||||
path_style,
|
||||
None,
|
||||
);
|
||||
env = HashMap::default();
|
||||
if let Some(envs) = envs {
|
||||
|
@ -213,6 +214,7 @@ impl Project {
|
|||
cx.entity_id().as_u64(),
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
None,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
@ -276,21 +278,33 @@ impl Project {
|
|||
let toolchain =
|
||||
project_path_context.map(|p| self.active_toolchain(p, LanguageName::new("Python"), cx));
|
||||
cx.spawn(async move |project, cx| {
|
||||
let toolchain = maybe!(async {
|
||||
let toolchain = toolchain?.await?;
|
||||
let shell = match &ssh_details {
|
||||
Some(ssh) => ssh.shell.clone(),
|
||||
None => match &settings.shell {
|
||||
Shell::Program(program) => program.clone(),
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args: _,
|
||||
title_override: _,
|
||||
} => program.clone(),
|
||||
Shell::System => system_shell(),
|
||||
},
|
||||
};
|
||||
|
||||
Some(())
|
||||
let scripts = maybe!(async {
|
||||
let toolchain = toolchain?.await?;
|
||||
Some(toolchain.startup_script)
|
||||
})
|
||||
.await;
|
||||
|
||||
let (spawn_task, shell) = {
|
||||
let activation_script = scripts.as_ref().and_then(|it| it.get(&shell));
|
||||
let shell = {
|
||||
match ssh_details {
|
||||
Some(SshDetails {
|
||||
host,
|
||||
ssh_command,
|
||||
envs,
|
||||
path_style,
|
||||
shell,
|
||||
shell: _,
|
||||
}) => {
|
||||
log::debug!("Connecting to a remote server: {ssh_command:?}");
|
||||
|
||||
|
@ -308,28 +322,37 @@ impl Project {
|
|||
path.as_deref(),
|
||||
env,
|
||||
path_style,
|
||||
activation_script.map(String::as_str),
|
||||
);
|
||||
env = HashMap::default();
|
||||
if let Some(envs) = envs {
|
||||
env.extend(envs);
|
||||
}
|
||||
(
|
||||
Option::<TaskState>::None,
|
||||
Shell::WithArguments {
|
||||
program,
|
||||
args,
|
||||
title_override: Some(format!("{} — Terminal", host).into()),
|
||||
},
|
||||
)
|
||||
}
|
||||
None => (None, settings.shell),
|
||||
}
|
||||
None if activation_script.is_some() => Shell::WithArguments {
|
||||
program: shell.clone(),
|
||||
args: vec![
|
||||
"-c".to_owned(),
|
||||
format!(
|
||||
"{}; exec {} -l",
|
||||
activation_script.unwrap().to_string(),
|
||||
shell
|
||||
),
|
||||
],
|
||||
title_override: None,
|
||||
},
|
||||
None => settings.shell,
|
||||
}
|
||||
};
|
||||
|
||||
project.update(cx, move |this, cx| {
|
||||
TerminalBuilder::new(
|
||||
local_path.map(|path| path.to_path_buf()),
|
||||
spawn_task,
|
||||
None,
|
||||
shell,
|
||||
env,
|
||||
settings.cursor_shape.unwrap_or_default(),
|
||||
|
@ -339,6 +362,7 @@ impl Project {
|
|||
cx.entity_id().as_u64(),
|
||||
None,
|
||||
cx,
|
||||
None,
|
||||
)
|
||||
.map(|builder| {
|
||||
let terminal_handle = cx.new(|cx| builder.subscribe(cx));
|
||||
|
@ -447,6 +471,7 @@ impl Project {
|
|||
path.as_deref(),
|
||||
env,
|
||||
path_style,
|
||||
None,
|
||||
);
|
||||
let mut command = std::process::Command::new(command);
|
||||
command.args(args);
|
||||
|
@ -479,6 +504,7 @@ pub fn wrap_for_ssh(
|
|||
path: Option<&Path>,
|
||||
env: HashMap<String, String>,
|
||||
path_style: PathStyle,
|
||||
activation_script: Option<&str>,
|
||||
) -> (String, Vec<String>) {
|
||||
let to_run = if let Some((command, args)) = command {
|
||||
let command: Option<Cow<str>> = shlex::try_quote(command).ok();
|
||||
|
@ -495,6 +521,9 @@ pub fn wrap_for_ssh(
|
|||
}
|
||||
}
|
||||
|
||||
let activation_script = activation_script
|
||||
.map(|s| format!(" {s};"))
|
||||
.unwrap_or_default();
|
||||
let commands = if let Some(path) = path {
|
||||
let path = RemotePathBuf::new(path.to_path_buf(), path_style).to_string();
|
||||
// shlex will wrap the command in single quotes (''), disabling ~ expansion,
|
||||
|
@ -506,12 +535,12 @@ pub fn wrap_for_ssh(
|
|||
.trim_start_matches("~")
|
||||
.trim_start_matches("/");
|
||||
|
||||
format!("cd \"$HOME/{trimmed_path}\"; {env_changes} {to_run}")
|
||||
format!("cd \"$HOME/{trimmed_path}\";{activation_script} {env_changes} {to_run}")
|
||||
} else {
|
||||
format!("cd \"{path}\"; {env_changes} {to_run}")
|
||||
format!("cd \"{path}\";{activation_script} {env_changes} {to_run}")
|
||||
}
|
||||
} else {
|
||||
format!("cd; {env_changes} {to_run}")
|
||||
format!("cd;{activation_script} {env_changes} {to_run}")
|
||||
};
|
||||
let shell_invocation = format!("{shell} -c {}", shlex::try_quote(&commands).unwrap());
|
||||
|
||||
|
|
|
@ -138,6 +138,7 @@ impl ToolchainStore {
|
|||
// Do we need to convert path to native string?
|
||||
path: PathBuf::from(toolchain.path).to_proto().into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json)?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
language_name,
|
||||
};
|
||||
let worktree_id = WorktreeId::from_proto(envelope.payload.worktree_id);
|
||||
|
@ -178,6 +179,7 @@ impl ToolchainStore {
|
|||
name: toolchain.name.into(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
}
|
||||
}),
|
||||
})
|
||||
|
@ -221,6 +223,7 @@ impl ToolchainStore {
|
|||
name: toolchain.name.to_string(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
@ -449,6 +452,7 @@ impl RemoteToolchainStore {
|
|||
name: toolchain.name.into(),
|
||||
path: path.to_proto(),
|
||||
raw_json: toolchain.as_json.to_string(),
|
||||
activation_script: toolchain.startup_script.into_iter().collect(),
|
||||
}),
|
||||
path: Some(project_path.path.to_string_lossy().into_owned()),
|
||||
})
|
||||
|
@ -501,6 +505,7 @@ impl RemoteToolchainStore {
|
|||
.to_string()
|
||||
.into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
@ -557,6 +562,7 @@ impl RemoteToolchainStore {
|
|||
.to_string()
|
||||
.into(),
|
||||
as_json: serde_json::Value::from_str(&toolchain.raw_json).ok()?,
|
||||
startup_script: toolchain.activation_script.into_iter().collect(),
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -12,6 +12,7 @@ message Toolchain {
|
|||
string name = 1;
|
||||
string path = 2;
|
||||
string raw_json = 3;
|
||||
map<string, string> activation_script = 4;
|
||||
}
|
||||
|
||||
message ToolchainGroup {
|
||||
|
|
|
@ -178,7 +178,7 @@ impl ShellKind {
|
|||
}
|
||||
}
|
||||
|
||||
fn system_shell() -> String {
|
||||
pub fn system_shell() -> String {
|
||||
if cfg!(target_os = "windows") {
|
||||
// `alacritty_terminal` uses this as default on Windows. See:
|
||||
// https://github.com/alacritty/alacritty/blob/0d4ab7bca43213d96ddfe40048fc0f922543c6f8/alacritty_terminal/src/tty/windows/mod.rs#L130
|
||||
|
|
|
@ -22,7 +22,7 @@ pub use debug_format::{
|
|||
AttachRequest, BuildTaskDefinition, DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest,
|
||||
Request, TcpArgumentsTemplate, ZedDebugConfig,
|
||||
};
|
||||
pub use shell_builder::{ShellBuilder, ShellKind};
|
||||
pub use shell_builder::{ShellBuilder, ShellKind, system_shell};
|
||||
pub use task_template::{
|
||||
DebugArgsRequest, HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates,
|
||||
substitute_variables_in_map, substitute_variables_in_str,
|
||||
|
|
|
@ -354,6 +354,7 @@ impl TerminalBuilder {
|
|||
window_id: u64,
|
||||
completion_tx: Option<Sender<Option<ExitStatus>>>,
|
||||
cx: &App,
|
||||
startup_script: Option<String>,
|
||||
) -> Result<TerminalBuilder> {
|
||||
// If the parent environment doesn't have a locale set
|
||||
// (As is the case when launched from a .app on MacOS),
|
||||
|
@ -517,6 +518,7 @@ impl TerminalBuilder {
|
|||
last_hyperlink_search_position: None,
|
||||
#[cfg(windows)]
|
||||
shell_program,
|
||||
startup_script,
|
||||
template: CopyTemplate {
|
||||
shell,
|
||||
env,
|
||||
|
@ -710,6 +712,7 @@ pub struct Terminal {
|
|||
#[cfg(windows)]
|
||||
shell_program: Option<String>,
|
||||
template: CopyTemplate,
|
||||
startup_script: Option<String>,
|
||||
}
|
||||
|
||||
struct CopyTemplate {
|
||||
|
@ -1983,6 +1986,7 @@ impl Terminal {
|
|||
self.template.window_id,
|
||||
None,
|
||||
cx,
|
||||
self.startup_script.clone(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2214,6 +2218,7 @@ mod tests {
|
|||
0,
|
||||
Some(completion_tx),
|
||||
cx,
|
||||
None,
|
||||
)
|
||||
.unwrap()
|
||||
.subscribe(cx)
|
||||
|
|
|
@ -1403,7 +1403,9 @@ impl WorkspaceDb {
|
|||
name: name.into(),
|
||||
path: path.into(),
|
||||
language_name,
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?,
|
||||
// todo refresh?
|
||||
startup_script: Default::default(),
|
||||
})))
|
||||
})
|
||||
.await
|
||||
|
@ -1427,7 +1429,9 @@ impl WorkspaceDb {
|
|||
name: name.into(),
|
||||
path: path.into(),
|
||||
language_name: LanguageName::new(&language_name),
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?
|
||||
as_json: serde_json::Value::from_str(&raw_json).ok()?,
|
||||
// todo refresh?
|
||||
startup_script: Default::default(),
|
||||
}, WorktreeId::from_proto(worktree_id), Arc::from(relative_worktree_path.as_ref())))).collect())
|
||||
})
|
||||
.await
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue