diff --git a/assets/icons/rerun.svg b/assets/icons/rerun.svg new file mode 100644 index 0000000000..4d22f924f5 --- /dev/null +++ b/assets/icons/rerun.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/crates/project/src/task_inventory.rs b/crates/project/src/task_inventory.rs index 98c1d1bbdf..3f91bd16c7 100644 --- a/crates/project/src/task_inventory.rs +++ b/crates/project/src/task_inventory.rs @@ -348,9 +348,20 @@ impl Inventory { }) } - /// Returns the last scheduled task, if any of the sources contains one with the matching id. - pub fn last_scheduled_task(&self) -> Option<(TaskSourceKind, ResolvedTask)> { - self.last_scheduled_tasks.back().cloned() + /// Returns the last scheduled task by task_id if provided. + /// Otherwise, returns the last scheduled task. + pub fn last_scheduled_task( + &self, + task_id: Option<&TaskId>, + ) -> Option<(TaskSourceKind, ResolvedTask)> { + if let Some(task_id) = task_id { + self.last_scheduled_tasks + .iter() + .find(|(_, task)| &task.id == task_id) + .cloned() + } else { + self.last_scheduled_tasks.back().cloned() + } } /// Registers task "usage" as being scheduled – to be used for LRU sorting when listing all tasks. diff --git a/crates/task/src/lib.rs b/crates/task/src/lib.rs index ccd39fb688..10b9b050a4 100644 --- a/crates/task/src/lib.rs +++ b/crates/task/src/lib.rs @@ -7,7 +7,7 @@ mod vscode_format; use collections::{hash_map, HashMap, HashSet}; use gpui::SharedString; -use serde::Serialize; +use serde::{Deserialize, Serialize}; use std::path::PathBuf; use std::str::FromStr; use std::{borrow::Cow, path::Path}; @@ -17,7 +17,7 @@ pub use vscode_format::VsCodeTaskFile; /// Task identifier, unique within the application. /// Based on it, task reruns and terminal tabs are managed. -#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Deserialize)] pub struct TaskId(pub String); /// TerminalWorkDir describes where a task should be run diff --git a/crates/tasks_ui/src/lib.rs b/crates/tasks_ui/src/lib.rs index 0877b1930e..2106cbe831 100644 --- a/crates/tasks_ui/src/lib.rs +++ b/crates/tasks_ui/src/lib.rs @@ -9,7 +9,7 @@ use workspace::{tasks::schedule_resolved_task, Workspace}; mod modal; mod settings; -pub use modal::Spawn; +pub use modal::{Rerun, Spawn}; pub fn init(cx: &mut AppContext) { settings::TaskSettings::register(cx); @@ -20,7 +20,10 @@ pub fn init(cx: &mut AppContext) { .register_action(move |workspace, action: &modal::Rerun, cx| { if let Some((task_source_kind, mut last_scheduled_task)) = workspace.project().update(cx, |project, cx| { - project.task_inventory().read(cx).last_scheduled_task() + project + .task_inventory() + .read(cx) + .last_scheduled_task(action.task_id.as_ref()) }) { if action.reevaluate_context { diff --git a/crates/tasks_ui/src/modal.rs b/crates/tasks_ui/src/modal.rs index 6cc7983ea9..6269043339 100644 --- a/crates/tasks_ui/src/modal.rs +++ b/crates/tasks_ui/src/modal.rs @@ -9,7 +9,7 @@ use gpui::{ }; use picker::{highlighted_match_with_paths::HighlightedText, Picker, PickerDelegate}; use project::{Project, TaskSourceKind}; -use task::{ResolvedTask, TaskContext, TaskTemplate}; +use task::{ResolvedTask, TaskContext, TaskId, TaskTemplate}; use ui::{ div, h_flex, v_flex, ActiveTheme, Button, ButtonCommon, ButtonSize, Clickable, Color, FluentBuilder as _, Icon, IconButton, IconButtonShape, IconName, IconSize, IntoElement, @@ -54,6 +54,9 @@ pub struct Rerun { /// Default: null #[serde(default)] pub use_new_terminal: Option, + + /// If present, rerun the task with this ID, otherwise rerun the last task. + pub task_id: Option, } impl_actions!(task, [Rerun, Spawn]); diff --git a/crates/terminal_view/src/terminal_view.rs b/crates/terminal_view/src/terminal_view.rs index 5fd7844300..d2348eb96c 100644 --- a/crates/terminal_view/src/terminal_view.rs +++ b/crates/terminal_view/src/terminal_view.rs @@ -24,7 +24,7 @@ use terminal::{ Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, TaskStatus, Terminal, }; use terminal_element::TerminalElement; -use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label}; +use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip}; use util::{paths::PathLikeWithPosition, ResultExt}; use workspace::{ item::{BreadcrumbText, Item, ItemEvent, TabContentParams}, @@ -787,23 +787,58 @@ impl Item for TerminalView { fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement { let terminal = self.terminal().read(cx); let title = terminal.title(true); - let (icon, icon_color) = match terminal.task() { + + let (icon, icon_color, rerun_btn) = match terminal.task() { Some(terminal_task) => match &terminal_task.status { - TaskStatus::Unknown => (IconName::ExclamationTriangle, Color::Warning), - TaskStatus::Running => (IconName::Play, Color::Default), + TaskStatus::Unknown => (IconName::ExclamationTriangle, Color::Warning, None), + TaskStatus::Running => (IconName::Play, Color::Disabled, None), TaskStatus::Completed { success } => { + let task_id = terminal_task.id.clone(); + let rerun_btn = IconButton::new("rerun-icon", IconName::Rerun) + .icon_size(IconSize::Small) + .size(ButtonSize::Compact) + .icon_color(Color::Default) + .shape(ui::IconButtonShape::Square) + .tooltip(|cx| Tooltip::text("Rerun task", cx)) + .on_click(move |_, cx| { + cx.dispatch_action(Box::new(tasks_ui::Rerun { + task_id: Some(task_id.clone()), + ..Default::default() + })); + }); + if *success { - (IconName::Check, Color::Success) + (IconName::Check, Color::Success, Some(rerun_btn)) } else { - (IconName::XCircle, Color::Error) + (IconName::XCircle, Color::Error, Some(rerun_btn)) } } }, - None => (IconName::Terminal, Color::Muted), + None => (IconName::Terminal, Color::Muted, None), }; + h_flex() .gap_2() - .child(Icon::new(icon).color(icon_color)) + .group("term-tab-icon") + .child( + h_flex() + .group("term-tab-icon") + .child( + div() + .when(rerun_btn.is_some(), |this| { + this.hover(|style| style.invisible().w_0()) + }) + .child(Icon::new(icon).color(icon_color)), + ) + .when_some(rerun_btn, |this, rerun_btn| { + this.child( + div() + .absolute() + .visible_on_hover("term-tab-icon") + .child(rerun_btn), + ) + }), + ) .child(Label::new(title).color(if params.selected { Color::Default } else { diff --git a/crates/ui/src/components/icon.rs b/crates/ui/src/components/icon.rs index a7a83a91ae..3810e32b7b 100644 --- a/crates/ui/src/components/icon.rs +++ b/crates/ui/src/components/icon.rs @@ -163,6 +163,7 @@ pub enum IconName { ReplaceAll, ReplaceNext, ReplyArrowRight, + Rerun, Return, Reveal, Save, @@ -284,6 +285,7 @@ impl IconName { IconName::ReplaceAll => "icons/replace_all.svg", IconName::ReplaceNext => "icons/replace_next.svg", IconName::ReplyArrowRight => "icons/reply_arrow_right.svg", + IconName::Rerun => "icons/rerun.svg", IconName::Return => "icons/return.svg", IconName::Save => "icons/save.svg", IconName::Screen => "icons/desktop.svg",