Use ShellKind::try_quote whenever we need to quote shell args (#40912)

Using `shlex` unconditionally is dangerous as it assumes the underlying
shell is POSIX which is not the case for PowerShell, CMD, or Nushell.
Therefore, whenever we want to quote the args we should utilise our
helper `util::shell::ShellKind::try_quote` which takes into account
which shell is being used to actually exec/spawn the invocation.

Release Notes:

- N/A

---------

Co-authored-by: Lukas Wirth <me@lukaswirth.dev>
This commit is contained in:
Jakub Konka
2025-10-23 06:44:42 +02:00
committed by GitHub
co-authored by Lukas Wirth
parent 18daa9a839
commit 8f4646d6c3
22 changed files with 317 additions and 232 deletions
+26 -77
View File
@@ -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),
}
}