Add support for Nushell in shell builder (#33806)
We also swap out env variables before sending them to shells now in the task system. This fixed issues Fish and Nushell had where an empty argument could be sent into a command when no argument should be sent. This only happened from task's generated by Zed. Closes #31297 #31240 Release Notes: - Fix bug where spawning a Zed generated task or debug session with Fish or Nushell failed
This commit is contained in:
parent
0ca0914cca
commit
bcac748c2b
2 changed files with 123 additions and 8 deletions
|
@ -5,6 +5,7 @@ enum ShellKind {
|
||||||
#[default]
|
#[default]
|
||||||
Posix,
|
Posix,
|
||||||
Powershell,
|
Powershell,
|
||||||
|
Nushell,
|
||||||
Cmd,
|
Cmd,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +19,8 @@ impl ShellKind {
|
||||||
ShellKind::Powershell
|
ShellKind::Powershell
|
||||||
} else if program == "cmd" || program.ends_with("cmd.exe") {
|
} else if program == "cmd" || program.ends_with("cmd.exe") {
|
||||||
ShellKind::Cmd
|
ShellKind::Cmd
|
||||||
|
} else if program == "nu" {
|
||||||
|
ShellKind::Nushell
|
||||||
} else {
|
} else {
|
||||||
// Someother shell detected, the user might install and use a
|
// Someother shell detected, the user might install and use a
|
||||||
// unix-like shell.
|
// unix-like shell.
|
||||||
|
@ -30,6 +33,7 @@ impl ShellKind {
|
||||||
Self::Powershell => Self::to_powershell_variable(input),
|
Self::Powershell => Self::to_powershell_variable(input),
|
||||||
Self::Cmd => Self::to_cmd_variable(input),
|
Self::Cmd => Self::to_cmd_variable(input),
|
||||||
Self::Posix => input.to_owned(),
|
Self::Posix => input.to_owned(),
|
||||||
|
Self::Nushell => Self::to_nushell_variable(input),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,11 +74,86 @@ impl ShellKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn to_nushell_variable(input: &str) -> String {
|
||||||
|
let mut result = String::new();
|
||||||
|
let mut source = input;
|
||||||
|
let mut is_start = true;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match source.chars().next() {
|
||||||
|
None => return result,
|
||||||
|
Some('$') => {
|
||||||
|
source = Self::parse_nushell_var(&source[1..], &mut result, is_start);
|
||||||
|
is_start = false;
|
||||||
|
}
|
||||||
|
Some(_) => {
|
||||||
|
is_start = false;
|
||||||
|
let chunk_end = source.find('$').unwrap_or(source.len());
|
||||||
|
let (chunk, rest) = source.split_at(chunk_end);
|
||||||
|
result.push_str(chunk);
|
||||||
|
source = rest;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_nushell_var<'a>(source: &'a str, text: &mut String, is_start: bool) -> &'a str {
|
||||||
|
if source.starts_with("env.") {
|
||||||
|
text.push('$');
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
match source.chars().next() {
|
||||||
|
Some('{') => {
|
||||||
|
let source = &source[1..];
|
||||||
|
if let Some(end) = source.find('}') {
|
||||||
|
let var_name = &source[..end];
|
||||||
|
if !var_name.is_empty() {
|
||||||
|
if !is_start {
|
||||||
|
text.push_str("(");
|
||||||
|
}
|
||||||
|
text.push_str("$env.");
|
||||||
|
text.push_str(var_name);
|
||||||
|
if !is_start {
|
||||||
|
text.push_str(")");
|
||||||
|
}
|
||||||
|
&source[end + 1..]
|
||||||
|
} else {
|
||||||
|
text.push_str("${}");
|
||||||
|
&source[end + 1..]
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text.push_str("${");
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(c) if c.is_alphabetic() || c == '_' => {
|
||||||
|
let end = source
|
||||||
|
.find(|c: char| !c.is_alphanumeric() && c != '_')
|
||||||
|
.unwrap_or(source.len());
|
||||||
|
let var_name = &source[..end];
|
||||||
|
if !is_start {
|
||||||
|
text.push_str("(");
|
||||||
|
}
|
||||||
|
text.push_str("$env.");
|
||||||
|
text.push_str(var_name);
|
||||||
|
if !is_start {
|
||||||
|
text.push_str(")");
|
||||||
|
}
|
||||||
|
&source[end..]
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
text.push('$');
|
||||||
|
source
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn args_for_shell(&self, interactive: bool, combined_command: String) -> Vec<String> {
|
fn args_for_shell(&self, interactive: bool, combined_command: String) -> Vec<String> {
|
||||||
match self {
|
match self {
|
||||||
ShellKind::Powershell => vec!["-C".to_owned(), combined_command],
|
ShellKind::Powershell => vec!["-C".to_owned(), combined_command],
|
||||||
ShellKind::Cmd => vec!["/C".to_owned(), combined_command],
|
ShellKind::Cmd => vec!["/C".to_owned(), combined_command],
|
||||||
ShellKind::Posix => interactive
|
ShellKind::Posix | ShellKind::Nushell => interactive
|
||||||
.then(|| "-i".to_owned())
|
.then(|| "-i".to_owned())
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(["-c".to_owned(), combined_command])
|
.chain(["-c".to_owned(), combined_command])
|
||||||
|
@ -142,9 +221,12 @@ impl ShellBuilder {
|
||||||
ShellKind::Cmd => {
|
ShellKind::Cmd => {
|
||||||
format!("{} /C '{}'", self.program, command_label)
|
format!("{} /C '{}'", self.program, command_label)
|
||||||
}
|
}
|
||||||
ShellKind::Posix => {
|
ShellKind::Posix | ShellKind::Nushell => {
|
||||||
let interactivity = self.interactive.then_some("-i ").unwrap_or_default();
|
let interactivity = self.interactive.then_some("-i ").unwrap_or_default();
|
||||||
format!("{} {interactivity}-c '{}'", self.program, command_label)
|
format!(
|
||||||
|
"{} {interactivity}-c '$\"{}\"'",
|
||||||
|
self.program, command_label
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,3 +252,36 @@ impl ShellBuilder {
|
||||||
(self.program, self.args)
|
(self.program, self.args)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_nu_shell_variable_substitution() {
|
||||||
|
let shell = Shell::Program("nu".to_owned());
|
||||||
|
let shell_builder = ShellBuilder::new(true, &shell);
|
||||||
|
|
||||||
|
let (program, args) = shell_builder.build(
|
||||||
|
Some("echo".into()),
|
||||||
|
&vec![
|
||||||
|
"${hello}".to_string(),
|
||||||
|
"$world".to_string(),
|
||||||
|
"nothing".to_string(),
|
||||||
|
"--$something".to_string(),
|
||||||
|
"$".to_string(),
|
||||||
|
"${test".to_string(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(program, "nu");
|
||||||
|
assert_eq!(
|
||||||
|
args,
|
||||||
|
vec![
|
||||||
|
"-i",
|
||||||
|
"-c",
|
||||||
|
"echo $env.hello $env.world nothing --($env.something) $ ${test"
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -256,7 +256,7 @@ impl TaskTemplate {
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
command: Some(command),
|
command: Some(command),
|
||||||
args: self.args.clone(),
|
args: args_with_substitutions,
|
||||||
env,
|
env,
|
||||||
use_new_terminal: self.use_new_terminal,
|
use_new_terminal: self.use_new_terminal,
|
||||||
allow_concurrent_runs: self.allow_concurrent_runs,
|
allow_concurrent_runs: self.allow_concurrent_runs,
|
||||||
|
@ -642,11 +642,11 @@ mod tests {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spawn_in_terminal.args,
|
spawn_in_terminal.args,
|
||||||
&[
|
&[
|
||||||
"arg1 $ZED_SELECTED_TEXT",
|
"arg1 test_selected_text",
|
||||||
"arg2 $ZED_COLUMN",
|
"arg2 5678",
|
||||||
"arg3 $ZED_SYMBOL",
|
"arg3 010101010101010101010101010101010101010101010101010101010101",
|
||||||
],
|
],
|
||||||
"Args should not be substituted with variables"
|
"Args should be substituted with variables"
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
spawn_in_terminal.command_label,
|
spawn_in_terminal.command_label,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue