From dcdd1ece1cb06eba7c1cbf4b17c404d88ca487ac Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Fri, 15 Mar 2024 18:32:59 +0200 Subject: [PATCH] Small improvements of the task terminal spawn behavior (#9399) * Add a `reveal: always|never` field in task definitions from tasks.json , allowing to customize task terminal behavior on spawn * Ensure reveal: always reveals the terminal even if the old task is already running Release Notes: - Added a `reveal: always|never` (`always` is a default) field in task definitions from tasks.json , allowing to customize task terminal behavior on spawn --------- Co-authored-by: Piotr Osiewicz --- assets/settings/initial_tasks.json | 7 ++- crates/task/src/lib.rs | 3 ++ crates/task/src/oneshot_source.rs | 5 +- crates/task/src/static_source.rs | 18 +++++++ crates/terminal/src/terminal.rs | 3 +- crates/terminal_view/src/terminal_panel.rs | 62 ++++++++++++++++------ 6 files changed, 79 insertions(+), 19 deletions(-) diff --git a/assets/settings/initial_tasks.json b/assets/settings/initial_tasks.json index 0115bcd76f..45c02ebb2d 100644 --- a/assets/settings/initial_tasks.json +++ b/assets/settings/initial_tasks.json @@ -5,6 +5,7 @@ { "label": "Example task", "command": "for i in {1..5}; do echo \"Hello $i/5\"; sleep 1; done", + //"args": [], // Env overrides for the command, will be appended to the terminal's environment from the settings. "env": { "foo": "bar" }, // Current working directory to spawn the command into, defaults to current project root. @@ -12,6 +13,10 @@ // Whether to use a new terminal tab or reuse the existing one to spawn the process, defaults to `false`. "use_new_terminal": false, // Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish, defaults to `false`. - "allow_concurrent_runs": false + "allow_concurrent_runs": false, + // 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" } ] diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index 37107e2569..0119dc7a30 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -6,6 +6,7 @@ pub mod static_source; use collections::HashMap; use gpui::ModelContext; +use static_source::RevealStrategy; use std::any::Any; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -34,6 +35,8 @@ pub struct SpawnInTerminal { pub use_new_terminal: bool, /// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish. pub allow_concurrent_runs: bool, + /// What to do with the terminal pane and tab, after the command was started. + pub reveal: RevealStrategy, } /// Keeps track of the file associated with a task and context of tasks execution (i.e. current file or current function) diff --git a/crates/task/src/oneshot_source.rs b/crates/task/src/oneshot_source.rs index 85257bee54..5e62b0ba75 100644 --- a/crates/task/src/oneshot_source.rs +++ b/crates/task/src/oneshot_source.rs @@ -2,7 +2,9 @@ use std::sync::Arc; -use crate::{SpawnInTerminal, Task, TaskContext, TaskId, TaskSource}; +use crate::{ + static_source::RevealStrategy, SpawnInTerminal, Task, TaskContext, TaskId, TaskSource, +}; use gpui::{AppContext, Context, Model}; /// A storage and source of tasks generated out of user command prompt inputs. @@ -48,6 +50,7 @@ impl Task for OneshotTask { env, use_new_terminal: Default::default(), allow_concurrent_runs: Default::default(), + reveal: RevealStrategy::default(), }) } } diff --git a/crates/task/src/static_source.rs b/crates/task/src/static_source.rs index dedf5a6384..95c72e39fd 100644 --- a/crates/task/src/static_source.rs +++ b/crates/task/src/static_source.rs @@ -38,6 +38,7 @@ impl Task for StaticTask { label: self.definition.label.clone(), command: self.definition.command.clone(), args: self.definition.args.clone(), + reveal: self.definition.reveal, env: definition_env, }) } @@ -64,6 +65,7 @@ pub struct StaticSource { /// Static task definition from the tasks config file. #[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] pub(crate) struct Definition { /// Human readable name of the task to display in the UI. pub label: String, @@ -84,6 +86,22 @@ pub(crate) struct Definition { /// Whether to allow multiple instances of the same task to be run, or rather wait for the existing ones to finish. #[serde(default)] pub allow_concurrent_runs: bool, + /// 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 + #[serde(default)] + pub reveal: RevealStrategy, +} + +/// What to do with the terminal pane and tab, after the command was started. +#[derive(Default, Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum RevealStrategy { + /// Always show the terminal pane, add and focus the corresponding task's tab in it. + #[default] + Always, + /// Do not change terminal pane focus, but still add/reuse the task's tab there. + Never, } /// A group of Tasks defined in a JSON file. diff --git a/crates/terminal/src/terminal.rs b/crates/terminal/src/terminal.rs index 95d9a33265..e9324191f1 100644 --- a/crates/terminal/src/terminal.rs +++ b/crates/terminal/src/terminal.rs @@ -39,7 +39,7 @@ use pty_info::PtyProcessInfo; use serde::{Deserialize, Serialize}; use settings::Settings; use smol::channel::{Receiver, Sender}; -use task::TaskId; +use task::{static_source::RevealStrategy, TaskId}; use terminal_settings::{AlternateScroll, Shell, TerminalBlink, TerminalSettings}; use theme::{ActiveTheme, Theme}; use util::truncate_and_trailoff; @@ -289,6 +289,7 @@ pub struct SpawnTask { pub command: String, pub args: Vec, pub env: HashMap, + pub reveal: RevealStrategy, } // https://github.com/alacritty/alacritty/blob/cb3a79dbf6472740daca8440d5166c1d4af5029e/extra/man/alacritty.5.scd?plain=1#L207-L213 diff --git a/crates/terminal_view/src/terminal_panel.rs b/crates/terminal_view/src/terminal_panel.rs index 07e6460fa9..ef7c973b75 100644 --- a/crates/terminal_view/src/terminal_panel.rs +++ b/crates/terminal_view/src/terminal_panel.rs @@ -14,7 +14,7 @@ use project::{Fs, ProjectEntryId}; use search::{buffer_search::DivRegistrar, BufferSearchBar}; use serde::{Deserialize, Serialize}; use settings::Settings; -use task::{SpawnInTerminal, TaskId}; +use task::{static_source::RevealStrategy, SpawnInTerminal, TaskId}; use terminal::{ terminal_settings::{Shell, TerminalDockPosition, TerminalSettings}, SpawnTask, @@ -303,6 +303,7 @@ impl TerminalPanel { command: spawn_in_terminal.command.clone(), args: spawn_in_terminal.args.clone(), env: spawn_in_terminal.env.clone(), + reveal: spawn_in_terminal.reveal, }; // 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() { @@ -322,6 +323,7 @@ impl TerminalPanel { spawn_task.command = shell; user_args.extend(["-i".to_owned(), "-c".to_owned(), command]); spawn_task.args = user_args; + let reveal = spawn_task.reveal; let working_directory = spawn_in_terminal.cwd.clone(); let allow_concurrent_runs = spawn_in_terminal.allow_concurrent_runs; @@ -379,6 +381,20 @@ impl TerminalPanel { .ok(); }), ); + + match reveal { + RevealStrategy::Always => { + self.activate_terminal_view(existing_item_index, cx); + let task_workspace = self.workspace.clone(); + cx.spawn(|_, mut cx| async move { + task_workspace + .update(&mut cx, |workspace, cx| workspace.focus_panel::(cx)) + .ok() + }) + .detach(); + } + RevealStrategy::Never => {} + } } } @@ -388,14 +404,20 @@ impl TerminalPanel { working_directory: Option, cx: &mut ViewContext, ) { + let reveal = spawn_task.reveal; self.add_terminal(working_directory, Some(spawn_task), cx); - let task_workspace = self.workspace.clone(); - cx.spawn(|_, mut cx| async move { - task_workspace - .update(&mut cx, |workspace, cx| workspace.focus_panel::(cx)) - .ok() - }) - .detach(); + match reveal { + RevealStrategy::Always => { + let task_workspace = self.workspace.clone(); + cx.spawn(|_, mut cx| async move { + task_workspace + .update(&mut cx, |workspace, cx| workspace.focus_panel::(cx)) + .ok() + }) + .detach(); + } + RevealStrategy::Never => {} + } } ///Create a new Terminal in the current working directory or the user's home directory @@ -542,6 +564,7 @@ impl TerminalPanel { .workspace .update(cx, |workspace, _| workspace.project().clone()) .ok()?; + let reveal = spawn_task.reveal; let window = cx.window_handle(); let new_terminal = project.update(cx, |project, cx| { project @@ -551,14 +574,21 @@ impl TerminalPanel { terminal_to_replace.update(cx, |terminal_to_replace, cx| { terminal_to_replace.set_terminal(new_terminal, cx); }); - self.activate_terminal_view(terminal_item_index, cx); - let task_workspace = self.workspace.clone(); - cx.spawn(|_, mut cx| async move { - task_workspace - .update(&mut cx, |workspace, cx| workspace.focus_panel::(cx)) - .ok() - }) - .detach(); + + match reveal { + RevealStrategy::Always => { + self.activate_terminal_view(terminal_item_index, cx); + let task_workspace = self.workspace.clone(); + cx.spawn(|_, mut cx| async move { + task_workspace + .update(&mut cx, |workspace, cx| workspace.focus_panel::(cx)) + .ok() + }) + .detach(); + } + RevealStrategy::Never => {} + } + Some(()) } }