Skip to content

Commit

Permalink
Extend task templates with shell and hide fields to use custom sh…
Browse files Browse the repository at this point in the history
…ells and custom close behavior (zed-industries#15031)
  • Loading branch information
SomeoneToIgnore authored and CharlesChen0823 committed Jul 29, 2024
1 parent 4b03210 commit 3067689
Show file tree
Hide file tree
Showing 15 changed files with 302 additions and 148 deletions.
23 changes: 22 additions & 1 deletion assets/settings/initial_tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
}
]
51 changes: 45 additions & 6 deletions crates/project/src/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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 }
Expand Down Expand Up @@ -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,
Expand All @@ -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))
Expand Down
6 changes: 4 additions & 2 deletions crates/project/src/terminals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
}),
Expand All @@ -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,
}),
Expand Down
27 changes: 25 additions & 2 deletions crates/proto/proto/zed.proto
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion crates/recent_projects/src/dev_servers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use rpc::{
proto::{CreateDevServerResponse, DevServerStatus},
ErrorCode, ErrorExt,
};
use task::HideStrategy;
use task::RevealStrategy;
use task::SpawnInTerminal;
use task::TerminalWorkDir;
Expand Down Expand Up @@ -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,
)
Expand Down
10 changes: 5 additions & 5 deletions crates/remote/src/ssh_session.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
}
Expand Down
25 changes: 24 additions & 1 deletion crates/task/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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>,
},
}
27 changes: 25 additions & 2 deletions crates/task/src/task_template.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
};

Expand Down Expand Up @@ -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.
Expand All @@ -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>);
Expand Down Expand Up @@ -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(),
}),
})
}
Expand Down
30 changes: 21 additions & 9 deletions crates/terminal/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
Loading

0 comments on commit 3067689

Please sign in to comment.