status_indicator.rs

 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}