Extend task templates with shell and hide fields to use custom shells and custom close behavior (#15031)

This commit is contained in:
Kirill Bulatov 2024-07-23 22:58:36 +03:00 committed by GitHub
parent 4a43084cb7
commit b2b9d4ccb6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 302 additions and 148 deletions

View file

@ -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.

View file

@ -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
}
}