Detailed changes
@@ -3,7 +3,7 @@ use language::Buffer;
use remote::RemoteClient;
use rpc::proto::{self, REMOTE_SERVER_PROJECT_ID};
use std::{collections::VecDeque, path::Path, sync::Arc};
-use task::Shell;
+use task::{Shell, shell_to_proto};
use util::ResultExt;
use worktree::Worktree;
@@ -153,7 +153,7 @@ impl ProjectEnvironment {
.proto_client()
.request(proto::GetDirectoryEnvironment {
project_id: REMOTE_SERVER_PROJECT_ID,
- shell: Some(shell.clone().to_proto()),
+ shell: Some(shell_to_proto(shell.clone())),
directory: abs_path.to_string_lossy().to_string(),
});
cx.spawn(async move |_, _| {
@@ -774,7 +774,7 @@ impl HeadlessProject {
envelope: TypedEnvelope<proto::GetDirectoryEnvironment>,
mut cx: AsyncApp,
) -> Result<proto::DirectoryEnvironment> {
- let shell = task::Shell::from_proto(envelope.payload.shell.context("missing shell")?)?;
+ let shell = task::shell_from_proto(envelope.payload.shell.context("missing shell")?)?;
let directory = PathBuf::from(envelope.payload.directory);
let environment = this
.update(&mut cx, |this, cx| {
@@ -3,7 +3,6 @@
mod adapter_schema;
mod debug_format;
mod serde_helpers;
-mod shell_builder;
pub mod static_source;
mod task_template;
mod vscode_debug_format;
@@ -12,23 +11,22 @@ mod vscode_format;
use anyhow::Context as _;
use collections::{HashMap, HashSet, hash_map};
use gpui::SharedString;
-use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::borrow::Cow;
use std::path::PathBuf;
use std::str::FromStr;
-use util::get_system_shell;
pub use adapter_schema::{AdapterSchema, AdapterSchemas};
pub use debug_format::{
AttachRequest, BuildTaskDefinition, DebugRequest, DebugScenario, DebugTaskFile, LaunchRequest,
Request, TcpArgumentsTemplate, ZedDebugConfig,
};
-pub use shell_builder::{ShellBuilder, ShellKind};
pub use task_template::{
DebugArgsRequest, HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates,
substitute_variables_in_map, substitute_variables_in_str,
};
+pub use util::shell::{Shell, ShellKind};
+pub use util::shell_builder::ShellBuilder;
pub use vscode_debug_format::VsCodeDebugTaskFile;
pub use vscode_format::VsCodeTaskFile;
pub use zed_actions::RevealTarget;
@@ -318,81 +316,32 @@ pub struct TaskContext {
#[derive(Clone, Debug)]
pub struct RunnableTag(pub SharedString);
-/// Shell configuration to open the terminal with.
-#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Hash)]
-#[serde(rename_all = "snake_case")]
-pub enum Shell {
- /// Use the system's default terminal configuration in /etc/passwd
- #[default]
- System,
- /// Use a specific program with no arguments.
- Program(String),
- /// Use a specific program with arguments.
- WithArguments {
- /// The program to run.
- program: String,
- /// The arguments to pass to the program.
- args: Vec<String>,
- /// An optional string to override the title of the terminal tab
- title_override: Option<SharedString>,
- },
+pub fn shell_from_proto(proto: proto::Shell) -> anyhow::Result<Shell> {
+ let shell_type = proto.shell_type.context("invalid shell type")?;
+ let shell = match shell_type {
+ proto::shell::ShellType::System(_) => Shell::System,
+ proto::shell::ShellType::Program(program) => Shell::Program(program),
+ proto::shell::ShellType::WithArguments(program) => Shell::WithArguments {
+ program: program.program,
+ args: program.args,
+ title_override: None,
+ },
+ };
+ Ok(shell)
}
-impl Shell {
- pub fn program(&self) -> String {
- match self {
- Shell::Program(program) => program.clone(),
- Shell::WithArguments { program, .. } => program.clone(),
- Shell::System => get_system_shell(),
- }
- }
-
- pub fn program_and_args(&self) -> (String, &[String]) {
- match self {
- Shell::Program(program) => (program.clone(), &[]),
- Shell::WithArguments { program, args, .. } => (program.clone(), args),
- Shell::System => (get_system_shell(), &[]),
- }
- }
-
- pub fn shell_kind(&self, is_windows: bool) -> ShellKind {
- match self {
- Shell::Program(program) => ShellKind::new(program, is_windows),
- Shell::WithArguments { program, .. } => ShellKind::new(program, is_windows),
- Shell::System => ShellKind::system(),
- }
- }
-
- pub fn from_proto(proto: proto::Shell) -> anyhow::Result<Self> {
- let shell_type = proto.shell_type.context("invalid shell type")?;
- let shell = match shell_type {
- proto::shell::ShellType::System(_) => Self::System,
- proto::shell::ShellType::Program(program) => Self::Program(program),
- proto::shell::ShellType::WithArguments(program) => Self::WithArguments {
- program: program.program,
- args: program.args,
- title_override: None,
- },
- };
- Ok(shell)
- }
-
- pub fn to_proto(self) -> proto::Shell {
- let shell_type = match self {
- Shell::System => proto::shell::ShellType::System(proto::System {}),
- Shell::Program(program) => proto::shell::ShellType::Program(program),
- Shell::WithArguments {
- program,
- args,
- title_override: _,
- } => proto::shell::ShellType::WithArguments(proto::shell::WithArguments {
- program,
- args,
- }),
- };
- proto::Shell {
- shell_type: Some(shell_type),
- }
+pub fn shell_to_proto(shell: Shell) -> proto::Shell {
+ let shell_type = match shell {
+ Shell::System => proto::shell::ShellType::System(proto::System {}),
+ Shell::Program(program) => proto::shell::ShellType::Program(program),
+ Shell::WithArguments {
+ program,
+ args,
+ title_override: _,
+ } => proto::shell::ShellType::WithArguments(proto::shell::WithArguments { program, args }),
+ };
+ proto::Shell {
+ shell_type: Some(shell_type),
}
}
@@ -67,7 +67,7 @@ use thiserror::Error;
use gpui::{
App, AppContext as _, Bounds, ClipboardItem, Context, EventEmitter, Hsla, Keystroke, Modifiers,
MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent, Pixels, Point, Rgba,
- ScrollWheelEvent, SharedString, Size, Task, TouchPhase, Window, actions, black, px,
+ ScrollWheelEvent, Size, Task, TouchPhase, Window, actions, black, px,
};
use crate::mappings::{colors::to_alac_rgb, keys::to_esc_str};
@@ -277,7 +277,7 @@ pub struct TerminalError {
pub directory: Option<PathBuf>,
pub program: Option<String>,
pub args: Option<Vec<String>>,
- pub title_override: Option<SharedString>,
+ pub title_override: Option<String>,
pub source: std::io::Error,
}
@@ -452,7 +452,7 @@ impl TerminalBuilder {
fn new(
program: String,
args: Option<Vec<String>>,
- title_override: Option<SharedString>,
+ title_override: Option<String>,
) -> Self {
log::debug!("Using {program} as shell");
Self {
@@ -493,6 +493,7 @@ impl TerminalBuilder {
.log_err()
.unwrap_or(params.program.clone())
});
+ dbg!(&alac_shell);
// Note: when remoting, this shell_kind will scrutinize `ssh` or
// `wsl.exe` as a shell and fall back to posix or powershell based on
@@ -824,7 +825,7 @@ pub struct Terminal {
pub last_content: TerminalContent,
pub selection_head: Option<AlacPoint>,
pub breadcrumb_text: String,
- title_override: Option<SharedString>,
+ title_override: Option<String>,
scroll_px: Pixels,
next_link_id: usize,
selection_phase: SelectionPhase,
@@ -66,7 +66,7 @@ fn settings_shell_to_task_shell(shell: settings::Shell) -> Shell {
} => Shell::WithArguments {
program,
args,
- title_override,
+ title_override: title_override.map(Into::into),
},
}
}
@@ -1,7 +1,53 @@
+use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use std::{borrow::Cow, fmt, path::Path, sync::LazyLock};
-#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
+/// Shell configuration to open the terminal with.
+#[derive(Clone, Debug, Default, PartialEq, Eq, Hash, Serialize, Deserialize, JsonSchema)]
+pub enum Shell {
+ /// Use the system's default terminal configuration in /etc/passwd
+ #[default]
+ System,
+ /// Use a specific program with no arguments.
+ Program(String),
+ /// Use a specific program with arguments.
+ WithArguments {
+ /// The program to run.
+ program: String,
+ /// The arguments to pass to the program.
+ args: Vec<String>,
+ /// An optional string to override the title of the terminal tab
+ title_override: Option<String>,
+ },
+}
+
+impl Shell {
+ pub fn program(&self) -> String {
+ match self {
+ Shell::Program(program) => program.clone(),
+ Shell::WithArguments { program, .. } => program.clone(),
+ Shell::System => get_system_shell(),
+ }
+ }
+
+ pub fn program_and_args(&self) -> (String, &[String]) {
+ match self {
+ Shell::Program(program) => (program.clone(), &[]),
+ Shell::WithArguments { program, args, .. } => (program.clone(), args),
+ Shell::System => (get_system_shell(), &[]),
+ }
+ }
+
+ pub fn shell_kind(&self, is_windows: bool) -> ShellKind {
+ match self {
+ Shell::Program(program) => ShellKind::new(program, is_windows),
+ Shell::WithArguments { program, .. } => ShellKind::new(program, is_windows),
+ Shell::System => ShellKind::system(),
+ }
+ }
+}
+
+#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
pub enum ShellKind {
#[default]
Posix,
@@ -185,32 +231,20 @@ impl ShellKind {
.unwrap_or_else(|| program.as_os_str())
.to_string_lossy();
- if program == "powershell" || program == "pwsh" {
- ShellKind::PowerShell
- } else if program == "cmd" {
- ShellKind::Cmd
- } else if program == "nu" {
- ShellKind::Nushell
- } else if program == "fish" {
- ShellKind::Fish
- } else if program == "csh" {
- ShellKind::Csh
- } else if program == "tcsh" {
- ShellKind::Tcsh
- } else if program == "rc" {
- ShellKind::Rc
- } else if program == "xonsh" {
- ShellKind::Xonsh
- } else if program == "sh" || program == "bash" {
- ShellKind::Posix
- } else {
- if is_windows {
- ShellKind::PowerShell
- } else {
- // Some other shell detected, the user might install and use a
- // unix-like shell.
- ShellKind::Posix
- }
+ match &*program {
+ "powershell" | "pwsh" => ShellKind::PowerShell,
+ "cmd" => ShellKind::Cmd,
+ "nu" => ShellKind::Nushell,
+ "fish" => ShellKind::Fish,
+ "csh" => ShellKind::Csh,
+ "tcsh" => ShellKind::Tcsh,
+ "rc" => ShellKind::Rc,
+ "xonsh" => ShellKind::Xonsh,
+ "sh" | "bash" => ShellKind::Posix,
+ _ if is_windows => ShellKind::PowerShell,
+ // Some other shell detected, the user might install and use a
+ // unix-like shell.
+ _ => ShellKind::Posix,
}
}
@@ -363,24 +397,41 @@ impl ShellKind {
match self {
ShellKind::PowerShell => Some('&'),
ShellKind::Nushell => Some('^'),
- _ => None,
+ ShellKind::Posix
+ | ShellKind::Csh
+ | ShellKind::Tcsh
+ | ShellKind::Rc
+ | ShellKind::Fish
+ | ShellKind::Cmd
+ | ShellKind::Xonsh => None,
}
}
pub const fn sequential_commands_separator(&self) -> char {
match self {
ShellKind::Cmd => '&',
- _ => ';',
+ ShellKind::Posix
+ | ShellKind::Csh
+ | ShellKind::Tcsh
+ | ShellKind::Rc
+ | ShellKind::Fish
+ | ShellKind::PowerShell
+ | ShellKind::Nushell
+ | ShellKind::Xonsh => ';',
}
}
pub fn try_quote<'a>(&self, arg: &'a str) -> Option<Cow<'a, str>> {
shlex::try_quote(arg).ok().map(|arg| match self {
- // If we are running in PowerShell, we want to take extra care when escaping strings.
- // In particular, we want to escape strings with a backtick (`) rather than a backslash (\).
- // TODO double escaping backslashes is not necessary in PowerShell and probably CMD
ShellKind::PowerShell => Cow::Owned(arg.replace("\\\"", "`\"")),
- _ => arg,
+ ShellKind::Posix
+ | ShellKind::Csh
+ | ShellKind::Tcsh
+ | ShellKind::Rc
+ | ShellKind::Fish
+ | ShellKind::Nushell
+ | ShellKind::Cmd
+ | ShellKind::Xonsh => arg,
})
}
@@ -389,18 +440,53 @@ impl ShellKind {
ShellKind::Cmd => "",
ShellKind::Nushell => "overlay use",
ShellKind::PowerShell => ".",
- ShellKind::Fish => "source",
- ShellKind::Csh => "source",
- ShellKind::Tcsh => "source",
- ShellKind::Posix | ShellKind::Rc => "source",
- ShellKind::Xonsh => "source",
+ ShellKind::Fish
+ | ShellKind::Csh
+ | ShellKind::Tcsh
+ | ShellKind::Posix
+ | ShellKind::Rc
+ | ShellKind::Xonsh => "source",
}
}
pub const fn clear_screen_command(&self) -> &'static str {
match self {
ShellKind::Cmd => "cls",
- _ => "clear",
+ ShellKind::Posix
+ | ShellKind::Csh
+ | ShellKind::Tcsh
+ | ShellKind::Rc
+ | ShellKind::Fish
+ | ShellKind::PowerShell
+ | ShellKind::Nushell
+ | ShellKind::Xonsh => "clear",
}
}
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ // Examples
+ // WSL
+ // wsl.exe --distribution NixOS --cd /home/user -- /usr/bin/zsh -c "echo hello"
+ // wsl.exe --distribution NixOS --cd /home/user -- /usr/bin/zsh -c "\"echo hello\"" | grep hello"
+ // wsl.exe --distribution NixOS --cd ~ env RUST_LOG=info,remote=debug .zed_wsl_server/zed-remote-server-dev-build proxy --identifier dev-workspace-53
+ // PowerShell from Nushell
+ // nu -c overlay use "C:\Users\kubko\dev\python\39007\tests\.venv\Scripts\activate.nu"; ^"C:\Program Files\PowerShell\7\pwsh.exe" -C "C:\Users\kubko\dev\python\39007\tests\.venv\Scripts\python.exe -m pytest \"test_foo.py::test_foo\""
+ // PowerShell from CMD
+ // cmd /C \" \"C:\\\\Users\\\\kubko\\\\dev\\\\python\\\\39007\\\\tests\\\\.venv\\\\Scripts\\\\activate.bat\"& \"C:\\\\Program Files\\\\PowerShell\\\\7\\\\pwsh.exe\" -C \"C:\\\\Users\\\\kubko\\\\dev\\\\python\\\\39007\\\\tests\\\\.venv\\\\Scripts\\\\python.exe -m pytest \\\"test_foo.py::test_foo\\\"\"\"
+
+ #[test]
+ fn test_try_quote_powershell() {
+ let shell_kind = ShellKind::PowerShell;
+ assert_eq!(
+ shell_kind
+ .try_quote("pwsh.exe -c \"echo \"hello there\"\"")
+ .unwrap()
+ .into_owned(),
+ "\"echo `\"hello there`\"\"".to_string()
+ );
+ }
+}
@@ -1,8 +1,5 @@
-use util::shell::get_system_shell;
-
-use crate::Shell;
-
-pub use util::shell::ShellKind;
+use crate::shell::get_system_shell;
+use crate::shell::{Shell, ShellKind};
/// ShellBuilder is used to turn a user-requested task into a
/// program that can be executed by the shell.
@@ -9,6 +9,7 @@ pub mod rel_path;
pub mod schemars;
pub mod serde;
pub mod shell;
+pub mod shell_builder;
pub mod shell_env;
pub mod size;
#[cfg(any(test, feature = "test-support"))]