1use gpui::{IntoElement, Render, View, WeakView};
2use settings::Settings;
3use ui::{
4 div, ButtonCommon, Clickable, Color, FluentBuilder, IconButton, IconName, Tooltip,
5 VisualContext, WindowContext,
6};
7use workspace::{item::ItemHandle, StatusItemView, Workspace};
8
9use crate::{modal::Spawn, settings::TaskSettings};
10
11enum TaskStatus {
12 Failed,
13 Running,
14 Succeeded,
15}
16
17/// A status bar icon that surfaces the status of running tasks.
18/// It has a different color depending on the state of running tasks:
19/// - red if any open task tab failed
20/// - else, yellow if any open task tab is still running
21/// - else, green if there tasks tabs open, and they have all succeeded
22/// - else, no indicator if there are no open task tabs
23pub struct TaskStatusIndicator {
24 workspace: WeakView<Workspace>,
25}
26
27impl TaskStatusIndicator {
28 pub fn new(workspace: WeakView<Workspace>, cx: &mut WindowContext) -> View<Self> {
29 cx.new_view(|_| Self { workspace })
30 }
31 fn current_status(&self, cx: &mut WindowContext) -> Option<TaskStatus> {
32 self.workspace
33 .update(cx, |this, cx| {
34 let mut status = None;
35 let project = this.project().read(cx);
36
37 for handle in project.local_terminal_handles() {
38 let Some(handle) = handle.upgrade() else {
39 continue;
40 };
41 let handle = handle.read(cx);
42 let task_state = handle.task();
43 if let Some(state) = task_state {
44 match state.status {
45 terminal::TaskStatus::Running => {
46 let _ = status.insert(TaskStatus::Running);
47 }
48 terminal::TaskStatus::Completed { success } => {
49 if !success {
50 let _ = status.insert(TaskStatus::Failed);
51 return status;
52 }
53 status.get_or_insert(TaskStatus::Succeeded);
54 }
55 _ => {}
56 };
57 }
58 }
59 status
60 })
61 .ok()
62 .flatten()
63 }
64}
65
66impl Render for TaskStatusIndicator {
67 fn render(&mut self, cx: &mut gpui::ViewContext<Self>) -> impl IntoElement {
68 if !TaskSettings::get_global(cx).show_status_indicator {
69 return div().into_any_element();
70 }
71 let current_status = self.current_status(cx);
72 let color = current_status.map(|status| match status {
73 TaskStatus::Failed => Color::Error,
74 TaskStatus::Running => Color::Warning,
75 TaskStatus::Succeeded => Color::Success,
76 });
77 IconButton::new("tasks-activity-indicator", IconName::Play)
78 .when_some(color, |this, color| this.icon_color(color))
79 .on_click(cx.listener(|this, _, cx| {
80 this.workspace
81 .update(cx, |this, cx| {
82 crate::spawn_task_or_modal(this, &Spawn::modal(), cx)
83 })
84 .ok();
85 }))
86 .tooltip(|cx| Tooltip::for_action("Spawn tasks", &Spawn { task_name: None }, cx))
87 .into_any_element()
88 }
89}
90
91impl StatusItemView for TaskStatusIndicator {
92 fn set_active_pane_item(
93 &mut self,
94 _: Option<&dyn ItemHandle>,
95 _: &mut ui::prelude::ViewContext<Self>,
96 ) {
97 }
98}