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
15 changed files with 302 additions and 148 deletions
|
@ -17,6 +17,27 @@
|
||||||
// What to do with the terminal pane and tab, after the command was started:
|
// 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)
|
// * `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
|
// * `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::{
|
use task::{
|
||||||
static_source::{StaticSource, TrackedFile},
|
static_source::{StaticSource, TrackedFile},
|
||||||
RevealStrategy, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
HideStrategy, RevealStrategy, Shell, TaskContext, TaskTemplate, TaskVariables, VariableName,
|
||||||
};
|
};
|
||||||
use terminals::Terminals;
|
use terminals::Terminals;
|
||||||
use text::{Anchor, BufferId, LineEnding};
|
use text::{Anchor, BufferId, LineEnding};
|
||||||
|
@ -9587,9 +9587,25 @@ impl Project {
|
||||||
use_new_terminal: template.use_new_terminal,
|
use_new_terminal: template.use_new_terminal,
|
||||||
allow_concurrent_runs: template.allow_concurrent_runs,
|
allow_concurrent_runs: template.allow_concurrent_runs,
|
||||||
reveal: match template.reveal {
|
reveal: match template.reveal {
|
||||||
RevealStrategy::Always => proto::RevealStrategy::Always as i32,
|
RevealStrategy::Always => proto::RevealStrategy::RevealAlways as i32,
|
||||||
RevealStrategy::Never => proto::RevealStrategy::Never 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,
|
tags: template.tags,
|
||||||
});
|
});
|
||||||
proto::TemplatePair { kind, template }
|
proto::TemplatePair { kind, template }
|
||||||
|
@ -10628,10 +10644,31 @@ impl Project {
|
||||||
|
|
||||||
let proto_template = template_pair.template?;
|
let proto_template = template_pair.template?;
|
||||||
let reveal = match proto::RevealStrategy::from_i32(proto_template.reveal)
|
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::RevealAlways => RevealStrategy::Always,
|
||||||
proto::RevealStrategy::Never => RevealStrategy::Never,
|
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 {
|
let task_template = TaskTemplate {
|
||||||
label: proto_template.label,
|
label: proto_template.label,
|
||||||
|
@ -10642,6 +10679,8 @@ impl Project {
|
||||||
use_new_terminal: proto_template.use_new_terminal,
|
use_new_terminal: proto_template.use_new_terminal,
|
||||||
allow_concurrent_runs: proto_template.allow_concurrent_runs,
|
allow_concurrent_runs: proto_template.allow_concurrent_runs,
|
||||||
reveal,
|
reveal,
|
||||||
|
hide,
|
||||||
|
shell,
|
||||||
tags: proto_template.tags,
|
tags: proto_template.tags,
|
||||||
};
|
};
|
||||||
Some((task_source_kind, task_template))
|
Some((task_source_kind, task_template))
|
||||||
|
|
|
@ -13,9 +13,9 @@ use std::{
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use task::{SpawnInTerminal, TerminalWorkDir};
|
use task::{Shell, SpawnInTerminal, TerminalWorkDir};
|
||||||
use terminal::{
|
use terminal::{
|
||||||
terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
|
terminal_settings::{self, TerminalSettings, VenvSettingsContent},
|
||||||
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
TaskState, TaskStatus, Terminal, TerminalBuilder,
|
||||||
};
|
};
|
||||||
use util::ResultExt;
|
use util::ResultExt;
|
||||||
|
@ -131,6 +131,7 @@ impl Project {
|
||||||
full_label: spawn_task.full_label,
|
full_label: spawn_task.full_label,
|
||||||
label: spawn_task.label,
|
label: spawn_task.label,
|
||||||
command_label: spawn_task.command_label,
|
command_label: spawn_task.command_label,
|
||||||
|
hide: spawn_task.hide,
|
||||||
status: TaskStatus::Running,
|
status: TaskStatus::Running,
|
||||||
completion_rx,
|
completion_rx,
|
||||||
}),
|
}),
|
||||||
|
@ -155,6 +156,7 @@ impl Project {
|
||||||
full_label: spawn_task.full_label,
|
full_label: spawn_task.full_label,
|
||||||
label: spawn_task.label,
|
label: spawn_task.label,
|
||||||
command_label: spawn_task.command_label,
|
command_label: spawn_task.command_label,
|
||||||
|
hide: spawn_task.hide,
|
||||||
status: TaskStatus::Running,
|
status: TaskStatus::Running,
|
||||||
completion_rx,
|
completion_rx,
|
||||||
}),
|
}),
|
||||||
|
|
|
@ -2284,12 +2284,35 @@ message TaskTemplate {
|
||||||
bool use_new_terminal = 6;
|
bool use_new_terminal = 6;
|
||||||
bool allow_concurrent_runs = 7;
|
bool allow_concurrent_runs = 7;
|
||||||
RevealStrategy reveal = 8;
|
RevealStrategy reveal = 8;
|
||||||
|
HideStrategy hide = 10;
|
||||||
repeated string tags = 9;
|
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 {
|
enum RevealStrategy {
|
||||||
Always = 0;
|
RevealAlways = 0;
|
||||||
Never = 1;
|
RevealNever = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum HideStrategy {
|
||||||
|
HideAlways = 0;
|
||||||
|
HideNever = 1;
|
||||||
|
HideOnSuccess = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
message TaskSourceKind {
|
message TaskSourceKind {
|
||||||
|
|
|
@ -20,6 +20,7 @@ use rpc::{
|
||||||
proto::{CreateDevServerResponse, DevServerStatus},
|
proto::{CreateDevServerResponse, DevServerStatus},
|
||||||
ErrorCode, ErrorExt,
|
ErrorCode, ErrorExt,
|
||||||
};
|
};
|
||||||
|
use task::HideStrategy;
|
||||||
use task::RevealStrategy;
|
use task::RevealStrategy;
|
||||||
use task::SpawnInTerminal;
|
use task::SpawnInTerminal;
|
||||||
use task::TerminalWorkDir;
|
use task::TerminalWorkDir;
|
||||||
|
@ -1191,10 +1192,12 @@ pub async fn spawn_ssh_task(
|
||||||
ssh_command: ssh_connection_string,
|
ssh_command: ssh_connection_string,
|
||||||
path: None,
|
path: None,
|
||||||
}),
|
}),
|
||||||
env: Default::default(),
|
|
||||||
use_new_terminal: true,
|
use_new_terminal: true,
|
||||||
allow_concurrent_runs: false,
|
allow_concurrent_runs: false,
|
||||||
reveal: RevealStrategy::Always,
|
reveal: RevealStrategy::Always,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
env: Default::default(),
|
||||||
|
shell: Default::default(),
|
||||||
},
|
},
|
||||||
cx,
|
cx,
|
||||||
)
|
)
|
||||||
|
|
|
@ -412,11 +412,11 @@ impl ProtoClient for SshSession {
|
||||||
impl SshClientState {
|
impl SshClientState {
|
||||||
#[cfg(not(unix))]
|
#[cfg(not(unix))]
|
||||||
async fn new(
|
async fn new(
|
||||||
user: String,
|
_user: String,
|
||||||
host: String,
|
_host: String,
|
||||||
port: u16,
|
_port: u16,
|
||||||
delegate: Arc<dyn SshClientDelegate>,
|
_delegate: Arc<dyn SshClientDelegate>,
|
||||||
cx: &mut AsyncAppContext,
|
_cx: &mut AsyncAppContext,
|
||||||
) -> Result<Self> {
|
) -> Result<Self> {
|
||||||
Err(anyhow!("ssh is not supported on this platform"))
|
Err(anyhow!("ssh is not supported on this platform"))
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,12 +7,13 @@ mod vscode_format;
|
||||||
|
|
||||||
use collections::{hash_map, HashMap, HashSet};
|
use collections::{hash_map, HashMap, HashSet};
|
||||||
use gpui::SharedString;
|
use gpui::SharedString;
|
||||||
|
use schemars::JsonSchema;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::{borrow::Cow, path::Path};
|
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;
|
pub use vscode_format::VsCodeTaskFile;
|
||||||
|
|
||||||
/// Task identifier, unique within the application.
|
/// Task identifier, unique within the application.
|
||||||
|
@ -78,6 +79,10 @@ pub struct SpawnInTerminal {
|
||||||
pub allow_concurrent_runs: bool,
|
pub allow_concurrent_runs: bool,
|
||||||
/// What to do with the terminal pane and tab, after the command was started.
|
/// What to do with the terminal pane and tab, after the command was started.
|
||||||
pub reveal: RevealStrategy,
|
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.
|
/// 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.
|
/// This is a new type representing a 'tag' on a 'runnable symbol', typically a test of main() function, found via treesitter.
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct RunnableTag(pub SharedString);
|
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 util::{truncate_and_remove_front, ResultExt};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
ResolvedTask, SpawnInTerminal, TaskContext, TaskId, TerminalWorkDir, VariableName,
|
ResolvedTask, Shell, SpawnInTerminal, TaskContext, TaskId, TerminalWorkDir, VariableName,
|
||||||
ZED_VARIABLE_NAME_PREFIX,
|
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
|
/// * `never` — avoid changing current terminal pane focus, but still add/reuse the task's tab there
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub reveal: RevealStrategy,
|
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.
|
/// Represents the tags which this template attaches to. Adding this removes this task from other UI.
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub tags: Vec<String>,
|
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.
|
/// What to do with the terminal pane and tab, after the command was started.
|
||||||
|
@ -62,6 +70,19 @@ pub enum RevealStrategy {
|
||||||
Never,
|
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.
|
/// A group of Tasks defined in a JSON file.
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
|
||||||
pub struct TaskTemplates(pub Vec<TaskTemplate>);
|
pub struct TaskTemplates(pub Vec<TaskTemplate>);
|
||||||
|
@ -194,6 +215,8 @@ impl TaskTemplate {
|
||||||
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,
|
||||||
reveal: self.reveal,
|
reveal: self.reveal,
|
||||||
|
hide: self.hide,
|
||||||
|
shell: self.shell.clone(),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,8 +39,8 @@ use pty_info::PtyProcessInfo;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use smol::channel::{Receiver, Sender};
|
use smol::channel::{Receiver, Sender};
|
||||||
use task::TaskId;
|
use task::{HideStrategy, Shell, TaskId};
|
||||||
use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings};
|
use terminal_settings::{AlternateScroll, TerminalBlink, TerminalSettings};
|
||||||
use theme::{ActiveTheme, Theme};
|
use theme::{ActiveTheme, Theme};
|
||||||
use util::truncate_and_trailoff;
|
use util::truncate_and_trailoff;
|
||||||
|
|
||||||
|
@ -612,6 +612,7 @@ pub struct TaskState {
|
||||||
pub command_label: String,
|
pub command_label: String,
|
||||||
pub status: TaskStatus,
|
pub status: TaskStatus,
|
||||||
pub completion_rx: Receiver<()>,
|
pub completion_rx: Receiver<()>,
|
||||||
|
pub hide: HideStrategy,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A status of the current terminal tab's task.
|
/// 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,
|
// SAFETY: the invocation happens on non `TaskStatus::Running` tasks, once,
|
||||||
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
// after either `AlacTermEvent::Exit` or `AlacTermEvent::ChildExit` events that are spawned
|
||||||
// when Zed task finishes and no more output is made.
|
// 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.
|
// 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]) };
|
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 = "⏵ ";
|
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 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) => {
|
Some(0) => {
|
||||||
format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully")
|
(true, format!("{TASK_DELIMITER}Task `{escaped_full_label}` finished successfully"))
|
||||||
}
|
}
|
||||||
Some(error_code) => {
|
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 => {
|
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 escaped_command_label = task.command_label.replace("\r\n", "\r").replace('\n', "\r");
|
||||||
let command_line = format!("{TASK_DELIMITER}Command: '{escaped_command_label}'");
|
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.
|
/// 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 serde_json::Value;
|
||||||
use settings::{SettingsJsonSchemaParams, SettingsSources};
|
use settings::{SettingsJsonSchemaParams, SettingsSources};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use task::Shell;
|
||||||
|
|
||||||
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
#[derive(Copy, Clone, Debug, Serialize, Deserialize, JsonSchema, PartialEq, Eq)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
|
@ -256,60 +257,6 @@ pub enum TerminalBlink {
|
||||||
On,
|
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)]
|
#[derive(Clone, Copy, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
|
||||||
#[serde(rename_all = "snake_case")]
|
#[serde(rename_all = "snake_case")]
|
||||||
pub enum AlternateScroll {
|
pub enum AlternateScroll {
|
||||||
|
@ -341,55 +288,3 @@ pub struct ToolbarContent {
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub title: Option<bool>,
|
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 search::{buffer_search::DivRegistrar, BufferSearchBar};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use task::{RevealStrategy, SpawnInTerminal, TaskId, TerminalWorkDir};
|
use task::{RevealStrategy, Shell, SpawnInTerminal, TaskId, TerminalWorkDir};
|
||||||
use terminal::{
|
use terminal::{
|
||||||
terminal_settings::{Shell, TerminalDockPosition, TerminalSettings},
|
terminal_settings::{TerminalDockPosition, TerminalSettings},
|
||||||
Terminal,
|
Terminal,
|
||||||
};
|
};
|
||||||
use ui::{
|
use ui::{
|
||||||
|
@ -363,15 +363,15 @@ impl TerminalPanel {
|
||||||
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
|
fn spawn_task(&mut self, spawn_in_terminal: &SpawnInTerminal, cx: &mut ViewContext<Self>) {
|
||||||
let mut spawn_task = spawn_in_terminal.clone();
|
let mut spawn_task = spawn_in_terminal.clone();
|
||||||
// Set up shell args unconditionally, as tasks are always spawned inside of a shell.
|
// 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() {
|
let Some((shell, mut user_args)) = (match spawn_in_terminal.shell.clone() {
|
||||||
Shell::System => Shell::retrieve_system_shell().map(|shell| (shell, Vec::new())),
|
Shell::System => retrieve_system_shell().map(|shell| (shell, Vec::new())),
|
||||||
Shell::Program(shell) => Some((shell, Vec::new())),
|
Shell::Program(shell) => Some((shell, Vec::new())),
|
||||||
Shell::WithArguments { program, args } => Some((program, args)),
|
Shell::WithArguments { program, args } => Some((program, args)),
|
||||||
}) else {
|
}) else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
#[cfg(target_os = "windows")]
|
#[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"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
{
|
{
|
||||||
|
@ -379,7 +379,7 @@ impl TerminalPanel {
|
||||||
}
|
}
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use terminal::terminal_settings::WindowsShellType;
|
use crate::terminal_panel::WindowsShellType;
|
||||||
|
|
||||||
match windows_shell_type {
|
match windows_shell_type {
|
||||||
WindowsShellType::Powershell => {
|
WindowsShellType::Powershell => {
|
||||||
|
@ -404,7 +404,7 @@ impl TerminalPanel {
|
||||||
#[cfg(not(target_os = "windows"))]
|
#[cfg(not(target_os = "windows"))]
|
||||||
command.push_str(&arg);
|
command.push_str(&arg);
|
||||||
#[cfg(target_os = "windows")]
|
#[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
|
command
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -412,7 +412,7 @@ impl TerminalPanel {
|
||||||
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
|
user_args.extend(["-i".to_owned(), "-c".to_owned(), combined_command]);
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
{
|
{
|
||||||
use terminal::terminal_settings::WindowsShellType;
|
use crate::terminal_panel::WindowsShellType;
|
||||||
|
|
||||||
match windows_shell_type {
|
match windows_shell_type {
|
||||||
WindowsShellType::Powershell => {
|
WindowsShellType::Powershell => {
|
||||||
|
@ -845,3 +845,93 @@ struct SerializedTerminalPanel {
|
||||||
width: Option<Pixels>,
|
width: Option<Pixels>,
|
||||||
height: 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,
|
ActiveItemChanged,
|
||||||
ContactRequestedJoin(u64),
|
ContactRequestedJoin(u64),
|
||||||
WorkspaceCreated(WeakView<Workspace>),
|
WorkspaceCreated(WeakView<Workspace>),
|
||||||
SpawnTask(SpawnInTerminal),
|
SpawnTask(Box<SpawnInTerminal>),
|
||||||
OpenBundledFile {
|
OpenBundledFile {
|
||||||
text: Cow<'static, str>,
|
text: Cow<'static, str>,
|
||||||
title: &'static str,
|
title: &'static str,
|
||||||
|
|
|
@ -1000,7 +1000,7 @@ mod tests {
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
time::Duration,
|
time::Duration,
|
||||||
};
|
};
|
||||||
use task::{RevealStrategy, SpawnInTerminal};
|
use task::{HideStrategy, RevealStrategy, Shell, SpawnInTerminal};
|
||||||
use theme::{ThemeRegistry, ThemeSettings};
|
use theme::{ThemeRegistry, ThemeSettings};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::{Item, ItemHandle},
|
item::{Item, ItemHandle},
|
||||||
|
@ -3349,6 +3349,8 @@ mod tests {
|
||||||
use_new_terminal: false,
|
use_new_terminal: false,
|
||||||
allow_concurrent_runs: false,
|
allow_concurrent_runs: false,
|
||||||
reveal: RevealStrategy::Always,
|
reveal: RevealStrategy::Always,
|
||||||
|
hide: HideStrategy::Never,
|
||||||
|
shell: Shell::System,
|
||||||
};
|
};
|
||||||
let project = Project::test(app_state.fs.clone(), [project_root.path()], cx).await;
|
let project = Project::test(app_state.fs.clone(), [project_root.path()], cx).await;
|
||||||
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
let window = cx.add_window(|cx| Workspace::test_new(project, cx));
|
||||||
|
@ -3356,7 +3358,7 @@ mod tests {
|
||||||
cx.update(|cx| {
|
cx.update(|cx| {
|
||||||
window
|
window
|
||||||
.update(cx, |_workspace, cx| {
|
.update(cx, |_workspace, cx| {
|
||||||
cx.emit(workspace::Event::SpawnTask(spawn_in_terminal));
|
cx.emit(workspace::Event::SpawnTask(Box::new(spawn_in_terminal)));
|
||||||
})
|
})
|
||||||
.unwrap();
|
.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:
|
// 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)
|
// * `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
|
// * `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…
Add table
Add a link
Reference in a new issue