mirror of
https://github.com/zed-industries/zed.git
synced 2024-11-07 20:39:04 +03:00
Extend task templates with shell
and hide
fields to use custom shells and custom close behavior (#15031)
This commit is contained in:
parent
4a43084cb7
commit
b2b9d4ccb6
@ -17,6 +17,27 @@
|
||||
// What to do with the terminal pane and tab, after the command was started:
|
||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
"reveal": "always"
|
||||
"reveal": "always",
|
||||
// What to do with the terminal pane and tab, after the command had finished:
|
||||
// * `never` — Do nothing when the command finishes (default)
|
||||
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
||||
// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`
|
||||
"hide": "never",
|
||||
// Which shell to use when running a task inside the terminal.
|
||||
// May take 3 values:
|
||||
// 1. (default) Use the system's default terminal configuration in /etc/passwd
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system"
|
||||
}
|
||||
]
|
||||
|
@ -110,7 +110,7 @@ use std::{
|
||||
};
|
||||
use task::{
|
||||
static_source::{StaticSource, TrackedFile},
|
||||
RevealStrategy, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
||||
HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
||||
};
|
||||
use terminals::Terminals;
|
||||
use text::{Anchor, BufferId, LineEnding};
|
||||
@ -9587,9 +9587,25 @@ impl Project {
|
||||
use_new_terminal: template.use_new_terminal,
|
||||
allow_concurrent_runs: template.allow_concurrent_runs,
|
||||
reveal: match template.reveal {
|
||||
RevealStrategy::Always => proto::RevealStrategy::Always as i32,
|
||||
RevealStrategy::Never => proto::RevealStrategy::Never as i32,
|
||||
RevealStrategy::Always => proto::RevealStrategy::RevealAlways as i32,
|
||||
RevealStrategy::Never => proto::RevealStrategy::RevealNever as i32,
|
||||
},
|
||||
hide: match template.hide {
|
||||
HideStrategy::Always => proto::HideStrategy::HideAlways as i32,
|
||||
HideStrategy::Never => proto::HideStrategy::HideNever as i32,
|
||||
HideStrategy::OnSuccess => proto::HideStrategy::HideOnSuccess as i32,
|
||||
},
|
||||
shell: Some(proto::Shell {
|
||||
shell_type: Some(match template.shell {
|
||||
Shell::System => proto::shell::ShellType::System(proto::System {}),
|
||||
Shell::Program(program) => proto::shell::ShellType::Program(program),
|
||||
Shell::WithArguments { program, args } => {
|
||||
proto::shell::ShellType::WithArguments(
|
||||
proto::shell::WithArguments { program, args },
|
||||
)
|
||||
}
|
||||
}),
|
||||
}),
|
||||
tags: template.tags,
|
||||
});
|
||||
proto::TemplatePair { kind, template }
|
||||
@ -10628,10 +10644,31 @@ impl Project {
|
||||
|
||||
let proto_template = template_pair.template?;
|
||||
let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
|
||||
.unwrap_or(proto::RevealStrategy::Always)
|
||||
.unwrap_or(proto::RevealStrategy::RevealAlways)
|
||||
{
|
||||
proto::RevealStrategy::Always => RevealStrategy::Always,
|
||||
proto::RevealStrategy::Never => RevealStrategy::Never,
|
||||
proto::RevealStrategy::RevealAlways => RevealStrategy::Always,
|
||||
proto::RevealStrategy::RevealNever => RevealStrategy::Never,
|
||||
};
|
||||
let hide = match proto::HideStrategy::from_i32(proto_template.hide)
|
||||
.unwrap_or(proto::HideStrategy::HideNever)
|
||||
{
|
||||
proto::HideStrategy::HideAlways => HideStrategy::Always,
|
||||
proto::HideStrategy::HideNever => HideStrategy::Never,
|
||||
proto::HideStrategy::HideOnSuccess => HideStrategy::OnSuccess,
|
||||
};
|
||||
let shell = match proto_template
|
||||
.shell
|
||||
.and_then(|shell| shell.shell_type)
|
||||
.unwrap_or(proto::shell::ShellType::System(proto::System {}))
|
||||
{
|
||||
proto::shell::ShellType::System(_) => Shell::System,
|
||||
proto::shell::ShellType::Program(program) => Shell::Program(program),
|
||||
proto::shell::ShellType::WithArguments(with_arguments) => {
|
||||
Shell::WithArguments {
|
||||
program: with_arguments.program,
|
||||
args: with_arguments.args,
|
||||
}
|
||||
}
|
||||
};
|
||||
let task_template = TaskTemplate {
|
||||
label: proto_template.label,
|
||||
@ -10642,6 +10679,8 @@ impl Project {
|
||||
use_new_terminal: proto_template.use_new_terminal,
|
||||
allow_concurrent_runs: proto_template.allow_concurrent_runs,
|
||||
reveal,
|
||||
hide,
|
||||
shell,
|
||||
tags: proto_template.tags,
|
||||
};
|
||||
Some((task_source_kind, task_template))
|
||||
|
@ -13,9 +13,9 @@ use std::{
|
||||
io::Write,
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
use task::{SpawnInTerminal, TerminalWorkDir};
|
||||
use task::{Shell, SpawnInTerminal, TerminalWorkDir};
|
||||
use terminal::{
|
||||
terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
|
||||
terminal_settings::{self, TerminalSettings, VenvSettingsContent},
|
||||
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
||||
};
|
||||
use util::ResultExt;
|
||||
@ -131,6 +131,7 @@ impl Project {
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
completion_rx,
|
||||
}),
|
||||
@ -155,6 +156,7 @@ impl Project {
|
||||
full_label: spawn_task.full_label,
|
||||
label: spawn_task.label,
|
||||
command_label: spawn_task.command_label,
|
||||
hide: spawn_task.hide,
|
||||
status: TaskStatus::Running,
|
||||
completion_rx,
|
||||
}),
|
||||
|
@ -2284,12 +2284,35 @@ message TaskTemplate {
|
||||
bool use_new_terminal = 6;
|
||||
bool allow_concurrent_runs = 7;
|
||||
RevealStrategy reveal = 8;
|
||||
HideStrategy hide = 10;
|
||||
repeated string tags = 9;
|
||||
Shell shell = 11;
|
||||
}
|
||||
|
||||
message Shell {
|
||||
message WithArguments {
|
||||
string program = 1;
|
||||
repeated string args = 2;
|
||||
}
|
||||
|
||||
oneof shell_type {
|
||||
System system = 1;
|
||||
string program = 2;
|
||||
WithArguments with_arguments = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message System {}
|
||||
|
||||
enum RevealStrategy {
|
||||
Always = 0;
|
||||
Never = 1;
|
||||
RevealAlways = 0;
|
||||
RevealNever = 1;
|
||||
}
|
||||
|
||||
enum HideStrategy {
|
||||
HideAlways = 0;
|
||||
HideNever = 1;
|
||||
HideOnSuccess = 2;
|
||||
}
|
||||
|
||||
message TaskSourceKind {
|
||||
|
@ -20,6 +20,7 @@ use rpc::{
|
||||
proto::{CreateDevServerResponse, DevServerStatus},
|
||||
ErrorCode, ErrorExt,
|
||||
};
|
||||
use task::HideStrategy;
|
||||
use task::RevealStrategy;
|
||||
use task::SpawnInTerminal;
|
||||
use task::TerminalWorkDir;
|
||||
@ -1191,10 +1192,12 @@ pub async fn spawn_ssh_task(
|
||||
ssh_command: ssh_connection_string,
|
||||
path: None,
|
||||
}),
|
||||
env: Default::default(),
|
||||
use_new_terminal: true,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: RevealStrategy::Always,
|
||||
hide: HideStrategy::Never,
|
||||
env: Default::default(),
|
||||
shell: Default::default(),
|
||||
},
|
||||
cx,
|
||||
)
|
||||
|
@ -412,11 +412,11 @@ impl ProtoClient for SshSession {
|
||||
impl SshClientState {
|
||||
#[cfg(not(unix))]
|
||||
async fn new(
|
||||
user: String,
|
||||
host: String,
|
||||
port: u16,
|
||||
delegate: Arc<dyn SshClientDelegate>,
|
||||
cx: &mut AsyncAppContext,
|
||||
_user: String,
|
||||
_host: String,
|
||||
_port: u16,
|
||||
_delegate: Arc<dyn SshClientDelegate>,
|
||||
_cx: &mut AsyncAppContext,
|
||||
) -> Result<Self> {
|
||||
Err(anyhow!("ssh is not supported on this platform"))
|
||||
}
|
||||
|
@ -7,12 +7,13 @@ mod vscode_format;
|
||||
|
||||
use collections::{hash_map, HashMap, HashSet};
|
||||
use gpui::SharedString;
|
||||
use schemars::JsonSchema;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
use std::{borrow::Cow, path::Path};
|
||||
|
||||
pub use task_template::{RevealStrategy, TaskTemplate, TaskTemplates};
|
||||
pub use task_template::{HideStrategy, RevealStrategy, TaskTemplate, TaskTemplates};
|
||||
pub use vscode_format::VsCodeTaskFile;
|
||||
|
||||
/// Task identifier, unique within the application.
|
||||
@ -78,6 +79,10 @@ pub struct SpawnInTerminal {
|
||||
pub allow_concurrent_runs: bool,
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
pub reveal: RevealStrategy,
|
||||
/// What to do with the terminal pane and tab, after the command had finished.
|
||||
pub hide: HideStrategy,
|
||||
/// Which shell to use when spawning the task.
|
||||
pub shell: Shell,
|
||||
}
|
||||
|
||||
/// A final form of the [`TaskTemplate`], that got resolved with a particualar [`TaskContext`] and now is ready to spawn the actual task.
|
||||
@ -271,3 +276,21 @@ pub struct TaskContext {
|
||||
/// This is a new type representing a 'tag' on a 'runnable symbol', typically a test of main() function, found via treesitter.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RunnableTag(pub SharedString);
|
||||
|
||||
/// Shell configuration to open the terminal with.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[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>,
|
||||
},
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ use sha2::{Digest, Sha256};
|
||||
use util::{truncate_and_remove_front, ResultExt};
|
||||
|
||||
use crate::{
|
||||
ResolvedTask, SpawnInTerminal, TaskContext, TaskId, TerminalWorkDir, VariableName,
|
||||
ResolvedTask, Shell, SpawnInTerminal, TaskContext, TaskId, TerminalWorkDir, VariableName,
|
||||
ZED_VARIABLE_NAME_PREFIX,
|
||||
};
|
||||
|
||||
@ -45,10 +45,18 @@ pub struct TaskTemplate {
|
||||
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
#[serde(default)]
|
||||
pub reveal: RevealStrategy,
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command had finished:
|
||||
/// * `never` — do nothing when the command finishes (default)
|
||||
/// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
||||
/// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`.
|
||||
#[serde(default)]
|
||||
pub hide: HideStrategy,
|
||||
/// Represents the tags which this template attaches to. Adding this removes this task from other UI.
|
||||
#[serde(default)]
|
||||
pub tags: Vec<String>,
|
||||
/// Which shell to use when spawning the task.
|
||||
#[serde(default)]
|
||||
pub shell: Shell,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command was started.
|
||||
@ -62,6 +70,19 @@ pub enum RevealStrategy {
|
||||
Never,
|
||||
}
|
||||
|
||||
/// What to do with the terminal pane and tab, after the command has finished.
|
||||
#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum HideStrategy {
|
||||
/// Do nothing when the command finishes.
|
||||
#[default]
|
||||
Never,
|
||||
/// Always hide the terminal tab, hide the pane also if it was the last tab in it.
|
||||
Always,
|
||||
/// Hide the terminal tab on task success only, otherwise behaves similar to `Always`.
|
||||
OnSuccess,
|
||||
}
|
||||
|
||||
/// A group of Tasks defined in a JSON file.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||
pub struct TaskTemplates(pub Vec<TaskTemplate>);
|
||||
@ -194,6 +215,8 @@ impl TaskTemplate {
|
||||
use_new_terminal: self.use_new_terminal,
|
||||
allow_concurrent_runs: self.allow_concurrent_runs,
|
||||
reveal: self.reveal,
|
||||
hide: self.hide,
|
||||
shell: self.shell.clone(),
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
@ -39,8 +39,8 @@ use pty_info::PtyProcessInfo;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use smol::channel::{Receiver, Sender};
|
||||
use task::TaskId;
|
||||
use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
|
||||
use task::{HideStrategy, Shell, TaskId};
|
||||
use terminal_settings::{AlternateScroll, TerminalBlink, TerminalSettings};
|
||||
use theme::{ActiveTheme, Theme};
|
||||
use util::truncate_and_trailoff;
|
||||
|
||||
@ -612,6 +612,7 @@ pub struct TaskState {
|
||||
pub command_label: String,
|
||||
pub status: TaskStatus,
|
||||
pub completion_rx: Receiver<()>,
|
||||
pub hide: HideStrategy,
|
||||
}
|
||||
|
||||
/// A status of the current terminal tab's task.
|
||||
@ -1567,32 +1568,43 @@ impl Terminal {
|
||||
}
|
||||
};
|
||||
|
||||
let (task_line, command_line) = task_summary(task, error_code);
|
||||
let (finished_successfully, task_line, command_line) = task_summary(task, error_code);
|
||||
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
|
||||
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
||||
// when Zed task finishes and no more output is made.
|
||||
// After the task summary is output once, no more text is appended to the terminal.
|
||||
unsafe { append_text_to_term(&mut self.term.lock(), &[&task_line, &command_line]) };
|
||||
match task.hide {
|
||||
HideStrategy::Never => {}
|
||||
HideStrategy::Always => {
|
||||
cx.emit(Event::CloseTerminal);
|
||||
}
|
||||
HideStrategy::OnSuccess => {
|
||||
if finished_successfully {
|
||||
cx.emit(Event::CloseTerminal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TASK_DELIMITER: &str = "⏵ ";
|
||||
fn task_summary(task: &TaskState, error_code: Option<i32>) -> (String, String) {
|
||||
fn task_summary(task: &TaskState, error_code: Option<i32>) -> (bool, String, String) {
|
||||
let escaped_full_label = task.full_label.replace("\r\n", "\r").replace('\n', "\r");
|
||||
let task_line = match error_code {
|
||||
let (success, task_line) = match error_code {
|
||||
Some(0) => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully")
|
||||
(true, format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully"))
|
||||
}
|
||||
Some(error_code) => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished with non-zero error code: {error_code}")
|
||||
(false, format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished with non-zero error code: {error_code}"))
|
||||
}
|
||||
None => {
|
||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished")
|
||||
(false, format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished"))
|
||||
}
|
||||
};
|
||||
let escaped_command_label = task.command_label.replace("\r\n", "\r").replace('\n', "\r");
|
||||
let command_line = format!("{TASK_DELIMITER}Command: '{escaped_command_label}'");
|
||||
(task_line, command_line)
|
||||
(success, task_line, command_line)
|
||||
}
|
||||
|
||||
/// Appends a stringified task summary to the terminal, after its output.
|
||||
|
@ -9,6 +9,7 @@ use serde_derive::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use settings::{SettingsJsonSchemaParams, SettingsSources};
|
||||
use std::path::PathBuf;
|
||||
use task::Shell;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
@ -256,60 +257,6 @@ pub enum TerminalBlink {
|
||||
On,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum Shell {
|
||||
/// Use the system's default terminal configuration in /etc/passwd
|
||||
System,
|
||||
Program(String),
|
||||
WithArguments {
|
||||
program: String,
|
||||
args: Vec<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Shell {
|
||||
pub fn retrieve_system_shell() -> Option<String> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use util::ResultExt;
|
||||
|
||||
return std::env::var("SHELL")
|
||||
.context("Error finding SHELL in env.")
|
||||
.log_err();
|
||||
}
|
||||
// `alacritty_terminal` uses this as default on Windows. See:
|
||||
// https://github.com/alacritty/alacritty/blob/0d4ab7bca43213d96ddfe40048fc0f922543c6f8/alacritty_terminal/src/tty/windows/mod.rs#L130
|
||||
#[cfg(target_os = "windows")]
|
||||
return Some("powershell".to_owned());
|
||||
}
|
||||
|
||||
/// Convert unix-shell variable syntax to windows-shell syntax.
|
||||
/// `powershell` and `cmd` are considered valid here.
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn to_windows_shell_variable(shell_type: WindowsShellType, input: String) -> String {
|
||||
match shell_type {
|
||||
WindowsShellType::Powershell => to_powershell_variable(input),
|
||||
WindowsShellType::Cmd => to_cmd_variable(input),
|
||||
WindowsShellType::Other => input,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
pub fn to_windows_shell_type(shell: &str) -> WindowsShellType {
|
||||
if shell == "powershell" || shell.ends_with("powershell.exe") {
|
||||
WindowsShellType::Powershell
|
||||
} else if shell == "cmd" || shell.ends_with("cmd.exe") {
|
||||
WindowsShellType::Cmd
|
||||
} else {
|
||||
// Someother shell detected, the user might install and use a
|
||||
// unix-like shell.
|
||||
WindowsShellType::Other
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum AlternateScroll {
|
||||
@ -341,55 +288,3 @@ pub struct ToolbarContent {
|
||||
/// Default: true
|
||||
pub title: Option<bool>,
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub enum WindowsShellType {
|
||||
Powershell,
|
||||
Cmd,
|
||||
Other,
|
||||
}
|
||||
|
||||
/// Convert `${SOME_VAR}`, `$SOME_VAR` to `%SOME_VAR%`.
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_cmd_variable(input: String) -> String {
|
||||
if let Some(var_str) = input.strip_prefix("${") {
|
||||
if var_str.find(':').is_none() {
|
||||
// If the input starts with "${", remove the trailing "}"
|
||||
format!("%{}%", &var_str[..var_str.len() - 1])
|
||||
} else {
|
||||
// `${SOME_VAR:-SOME_DEFAULT}`, we currently do not handle this situation,
|
||||
// which will result in the task failing to run in such cases.
|
||||
input
|
||||
}
|
||||
} else if let Some(var_str) = input.strip_prefix('$') {
|
||||
// If the input starts with "$", directly append to "$env:"
|
||||
format!("%{}%", var_str)
|
||||
} else {
|
||||
// If no prefix is found, return the input as is
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `${SOME_VAR}`, `$SOME_VAR` to `$env:SOME_VAR`.
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_powershell_variable(input: String) -> String {
|
||||
if let Some(var_str) = input.strip_prefix("${") {
|
||||
if var_str.find(':').is_none() {
|
||||
// If the input starts with "${", remove the trailing "}"
|
||||
format!("$env:{}", &var_str[..var_str.len() - 1])
|
||||
} else {
|
||||
// `${SOME_VAR:-SOME_DEFAULT}`, we currently do not handle this situation,
|
||||
// which will result in the task failing to run in such cases.
|
||||
input
|
||||
}
|
||||
} else if let Some(var_str) = input.strip_prefix('$') {
|
||||
// If the input starts with "$", directly append to "$env:"
|
||||
format!("$env:{}", var_str)
|
||||
} else {
|
||||
// If no prefix is found, return the input as is
|
||||
input
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ use project::{Fs, ProjectEntryId};
|
||||
use search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use settings::Settings;
|
||||
use task::{RevealStrategy, SpawnInTerminal, TaskId, TerminalWorkDir};
|
||||
use task::{RevealStrategy, Shell, SpawnInTerminal, TaskId, TerminalWorkDir};
|
||||
use terminal::{
|
||||
terminal_settings::{Shell, TerminalDockPosition, TerminalSettings},
|
||||
terminal_settings::{TerminalDockPosition, TerminalSettings},
|
||||
Terminal,
|
||||
};
|
||||
use ui::{
|
||||
@ -363,15 +363,15 @@ impl TerminalPanel {
|
||||
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
|
||||
let mut spawn_task = spawn_in_terminal.clone();
|
||||
// Set up shell args unconditionally, as tasks are always spawned inside of a shell.
|
||||
let Some((shell, mut user_args)) = (match TerminalSettings::get_global(cx).shell.clone() {
|
||||
Shell::System => Shell::retrieve_system_shell().map(|shell| (shell, Vec::new())),
|
||||
let Some((shell, mut user_args)) = (match spawn_in_terminal.shell.clone() {
|
||||
Shell::System => retrieve_system_shell().map(|shell| (shell, Vec::new())),
|
||||
Shell::Program(shell) => Some((shell, Vec::new())),
|
||||
Shell::WithArguments { program, args } => Some((program, args)),
|
||||
}) else {
|
||||
return;
|
||||
};
|
||||
#[cfg(target_os = "windows")]
|
||||
let windows_shell_type = Shell::to_windows_shell_type(&shell);
|
||||
let windows_shell_type = to_windows_shell_type(&shell);
|
||||
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
@ -379,7 +379,7 @@ impl TerminalPanel {
|
||||
}
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use terminal::terminal_settings::WindowsShellType;
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
@ -404,7 +404,7 @@ impl TerminalPanel {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
command.push_str(&arg);
|
||||
#[cfg(target_os = "windows")]
|
||||
command.push_str(&Shell::to_windows_shell_variable(windows_shell_type, arg));
|
||||
command.push_str(&to_windows_shell_variable(windows_shell_type, arg));
|
||||
command
|
||||
});
|
||||
|
||||
@ -412,7 +412,7 @@ impl TerminalPanel {
|
||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use terminal::terminal_settings::WindowsShellType;
|
||||
use crate::terminal_panel::WindowsShellType;
|
||||
|
||||
match windows_shell_type {
|
||||
WindowsShellType::Powershell => {
|
||||
@ -845,3 +845,93 @@ struct SerializedTerminalPanel {
|
||||
width: Option<Pixels>,
|
||||
height: Option<Pixels>,
|
||||
}
|
||||
|
||||
fn retrieve_system_shell() -> Option<String> {
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
{
|
||||
use anyhow::Context;
|
||||
use util::ResultExt;
|
||||
|
||||
return std::env::var("SHELL")
|
||||
.context("Error finding SHELL in env.")
|
||||
.log_err();
|
||||
}
|
||||
// `alacritty_terminal` uses this as default on Windows. See:
|
||||
// https://github.com/alacritty/alacritty/blob/0d4ab7bca43213d96ddfe40048fc0f922543c6f8/alacritty_terminal/src/tty/windows/mod.rs#L130
|
||||
#[cfg(target_os = "windows")]
|
||||
return Some("powershell".to_owned());
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_windows_shell_variable(shell_type: WindowsShellType, input: String) -> String {
|
||||
match shell_type {
|
||||
WindowsShellType::Powershell => to_powershell_variable(input),
|
||||
WindowsShellType::Cmd => to_cmd_variable(input),
|
||||
WindowsShellType::Other => input,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_windows_shell_type(shell: &str) -> WindowsShellType {
|
||||
if shell == "powershell" || shell.ends_with("powershell.exe") {
|
||||
WindowsShellType::Powershell
|
||||
} else if shell == "cmd" || shell.ends_with("cmd.exe") {
|
||||
WindowsShellType::Cmd
|
||||
} else {
|
||||
// Someother shell detected, the user might install and use a
|
||||
// unix-like shell.
|
||||
WindowsShellType::Other
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `${SOME_VAR}`, `$SOME_VAR` to `%SOME_VAR%`.
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_cmd_variable(input: String) -> String {
|
||||
if let Some(var_str) = input.strip_prefix("${") {
|
||||
if var_str.find(':').is_none() {
|
||||
// If the input starts with "${", remove the trailing "}"
|
||||
format!("%{}%", &var_str[..var_str.len() - 1])
|
||||
} else {
|
||||
// `${SOME_VAR:-SOME_DEFAULT}`, we currently do not handle this situation,
|
||||
// which will result in the task failing to run in such cases.
|
||||
input
|
||||
}
|
||||
} else if let Some(var_str) = input.strip_prefix('$') {
|
||||
// If the input starts with "$", directly append to "$env:"
|
||||
format!("%{}%", var_str)
|
||||
} else {
|
||||
// If no prefix is found, return the input as is
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert `${SOME_VAR}`, `$SOME_VAR` to `$env:SOME_VAR`.
|
||||
#[inline]
|
||||
#[cfg(target_os = "windows")]
|
||||
fn to_powershell_variable(input: String) -> String {
|
||||
if let Some(var_str) = input.strip_prefix("${") {
|
||||
if var_str.find(':').is_none() {
|
||||
// If the input starts with "${", remove the trailing "}"
|
||||
format!("$env:{}", &var_str[..var_str.len() - 1])
|
||||
} else {
|
||||
// `${SOME_VAR:-SOME_DEFAULT}`, we currently do not handle this situation,
|
||||
// which will result in the task failing to run in such cases.
|
||||
input
|
||||
}
|
||||
} else if let Some(var_str) = input.strip_prefix('$') {
|
||||
// If the input starts with "$", directly append to "$env:"
|
||||
format!("$env:{}", var_str)
|
||||
} else {
|
||||
// If no prefix is found, return the input as is
|
||||
input
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(target_os = "windows")]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
enum WindowsShellType {
|
||||
Powershell,
|
||||
Cmd,
|
||||
Other,
|
||||
}
|
||||
|
@ -41,6 +41,6 @@ pub fn schedule_resolved_task(
|
||||
})
|
||||
});
|
||||
}
|
||||
cx.emit(crate::Event::SpawnTask(spawn_in_terminal));
|
||||
cx.emit(crate::Event::SpawnTask(Box::new(spawn_in_terminal)));
|
||||
}
|
||||
}
|
||||
|
@ -660,7 +660,7 @@ pub enum Event {
|
||||
ActiveItemChanged,
|
||||
ContactRequestedJoin(u64),
|
||||
WorkspaceCreated(WeakView<Workspace>),
|
||||
SpawnTask(SpawnInTerminal),
|
||||
SpawnTask(Box<SpawnInTerminal>),
|
||||
OpenBundledFile {
|
||||
text: Cow<'static, str>,
|
||||
title: &'static str,
|
||||
|
@ -1000,7 +1000,7 @@ mod tests {
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
use task::{RevealStrategy, SpawnInTerminal};
|
||||
use task::{HideStrategy, RevealStrategy, Shell, SpawnInTerminal};
|
||||
use theme::{ThemeRegistry, ThemeSettings};
|
||||
use workspace::{
|
||||
item::{Item, ItemHandle},
|
||||
@ -3349,6 +3349,8 @@ mod tests {
|
||||
use_new_terminal: false,
|
||||
allow_concurrent_runs: false,
|
||||
reveal: RevealStrategy::Always,
|
||||
hide: HideStrategy::Never,
|
||||
shell: Shell::System,
|
||||
};
|
||||
let project = Project::test(app_state.fs.clone(), [project_root.path()], cx).await;
|
||||
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||
@ -3356,7 +3358,7 @@ mod tests {
|
||||
cx.update(|cx| {
|
||||
window
|
||||
.update(cx, |_workspace, cx| {
|
||||
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
|
||||
cx.emit(workspace::Event::SpawnTask(Box::new(spawn_in_terminal)));
|
||||
})
|
||||
.unwrap();
|
||||
});
|
||||
|
@ -19,7 +19,28 @@ Zed supports ways to spawn (and rerun) commands using its integrated terminal to
|
||||
// What to do with the terminal pane and tab, after the command was started:
|
||||
// * `always` — always show the terminal pane, add and focus the corresponding task's tab in it (default)
|
||||
// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||
"reveal": "always"
|
||||
"reveal": "always",
|
||||
// What to do with the terminal pane and tab, after the command had finished:
|
||||
// * `never` — Do nothing when the command finishes (default)
|
||||
// * `always` — always hide the terminal tab, hide the pane also if it was the last tab in it
|
||||
// * `on_success` — hide the terminal tab on task success only, otherwise behaves similar to `always`
|
||||
"hide": "never",
|
||||
// Which shell to use when running a task inside the terminal.
|
||||
// May take 3 values:
|
||||
// 1. (default) Use the system's default terminal configuration in /etc/passwd
|
||||
// "shell": "system"
|
||||
// 2. A program:
|
||||
// "shell": {
|
||||
// "program": "sh"
|
||||
// }
|
||||
// 3. A program with arguments:
|
||||
// "shell": {
|
||||
// "with_arguments": {
|
||||
// "program": "/bin/bash",
|
||||
// "arguments": ["--login"]
|
||||
// }
|
||||
// }
|
||||
"shell": "system"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
Loading…
Reference in New Issue
Block a user