Use different icons for terminal tasks (#9876)

Kirill Bulatov created

Change summary

Cargo.lock                                |  4 
crates/project/src/terminals.rs           |  4 
crates/terminal/Cargo.toml                |  3 
crates/terminal/src/terminal.rs           | 55 ++++++++++++++++++------
crates/terminal_view/src/terminal_view.rs | 21 ++++++--
5 files changed, 60 insertions(+), 27 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -87,9 +87,9 @@ dependencies = [
 
 [[package]]
 name = "alacritty_terminal"
-version = "0.23.0-rc1"
+version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc2c16faa5425a10be102dda76f73d76049b44746e18ddeefc44d78bbe76cbce"
+checksum = "f6d1ea4484c8676f295307a4892d478c70ac8da1dbd8c7c10830a504b7f1022f"
 dependencies = [
  "base64 0.22.0",
  "bitflags 2.4.2",

crates/project/src/terminals.rs 🔗

@@ -6,7 +6,7 @@ use smol::channel::bounded;
 use std::path::{Path, PathBuf};
 use terminal::{
     terminal_settings::{self, Shell, TerminalSettings, VenvSettingsContent},
-    SpawnTask, TaskState, Terminal, TerminalBuilder,
+    SpawnTask, TaskState, TaskStatus, Terminal, TerminalBuilder,
 };
 use util::ResultExt;
 
@@ -53,7 +53,7 @@ impl Project {
                 Some(TaskState {
                     id: spawn_task.id,
                     label: spawn_task.label,
-                    completed: false,
+                    status: TaskStatus::Running,
                     completion_rx,
                 }),
                 Shell::WithArguments {

crates/terminal/Cargo.toml 🔗

@@ -14,8 +14,7 @@ doctest = false
 
 
 [dependencies]
-# TODO: when new version of this crate is released, change it
-alacritty_terminal = "0.23.0-rc1"
+alacritty_terminal = "0.23"
 anyhow.workspace = true
 collections.workspace = true
 dirs = "4.0.0"

crates/terminal/src/terminal.rs 🔗

@@ -595,10 +595,36 @@ pub struct Terminal {
 pub struct TaskState {
     pub id: TaskId,
     pub label: String,
-    pub completed: bool,
+    pub status: TaskStatus,
     pub completion_rx: Receiver<()>,
 }
 
+/// A status of the current terminal tab's task.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum TaskStatus {
+    /// The task had been started, but got cancelled or somehow otherwise it did not
+    /// report its exit code before the terminal event loop was shut down.
+    Unknown,
+    /// The task is started and running currently.
+    Running,
+    /// After the start, the task stopped running and reported its error code back.
+    Completed { success: bool },
+}
+
+impl TaskStatus {
+    fn register_terminal_exit(&mut self) {
+        if self == &Self::Running {
+            *self = Self::Unknown;
+        }
+    }
+
+    fn register_task_exit(&mut self, error_code: i32) {
+        *self = TaskStatus::Completed {
+            success: error_code == 0,
+        };
+    }
+}
+
 impl Terminal {
     fn process_event(&mut self, event: &AlacTermEvent, cx: &mut ModelContext<Self>) {
         match event {
@@ -630,7 +656,7 @@ impl Terminal {
             }
             AlacTermEvent::Exit => match &mut self.task {
                 Some(task) => {
-                    task.completed = true;
+                    task.status.register_terminal_exit();
                     self.completion_tx.try_send(()).ok();
                 }
                 None => cx.emit(Event::CloseTerminal),
@@ -649,8 +675,11 @@ impl Terminal {
                 self.events
                     .push_back(InternalEvent::ColorRequest(*idx, fun_ptr.clone()));
             }
-            AlacTermEvent::ChildExit(_) => {
-                // TODO: Handle child exit
+            AlacTermEvent::ChildExit(error_code) => {
+                if let Some(task) = &mut self.task {
+                    task.status.register_task_exit(*error_code);
+                    self.completion_tx.try_send(()).ok();
+                }
             }
         }
     }
@@ -1381,19 +1410,15 @@ impl Terminal {
     }
 
     pub fn wait_for_completed_task(&self, cx: &mut AppContext) -> Task<()> {
-        match self.task() {
-            Some(task) => {
-                if task.completed {
-                    Task::ready(())
-                } else {
-                    let mut completion_receiver = task.completion_rx.clone();
-                    cx.spawn(|_| async move {
-                        completion_receiver.next().await;
-                    })
-                }
+        if let Some(task) = self.task() {
+            if task.status == TaskStatus::Running {
+                let mut completion_receiver = task.completion_rx.clone();
+                return cx.spawn(|_| async move {
+                    completion_receiver.next().await;
+                });
             }
-            None => Task::ready(()),
         }
+        Task::ready(())
     }
 }
 

crates/terminal_view/src/terminal_view.rs 🔗

@@ -20,7 +20,7 @@ use terminal::{
         term::{search::RegexSearch, TermMode},
     },
     terminal_settings::{TerminalBlink, TerminalSettings, WorkingDirectory},
-    Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, Terminal,
+    Clear, Copy, Event, MaybeNavigationTarget, Paste, ShowCharacterPalette, TaskStatus, Terminal,
 };
 use terminal_element::TerminalElement;
 use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label};
@@ -788,10 +788,19 @@ impl Item for TerminalView {
     ) -> AnyElement {
         let terminal = self.terminal().read(cx);
         let title = terminal.title(true);
-        let icon = if terminal.task().is_some() {
-            IconName::Play
-        } else {
-            IconName::Terminal
+        let icon = match terminal.task() {
+            Some(terminal_task) => match &terminal_task.status {
+                TaskStatus::Unknown => IconName::ExclamationTriangle,
+                TaskStatus::Running => IconName::Play,
+                TaskStatus::Completed { success } => {
+                    if *success {
+                        IconName::Check
+                    } else {
+                        IconName::XCircle
+                    }
+                }
+            },
+            None => IconName::Terminal,
         };
         h_flex()
             .gap_2()
@@ -829,7 +838,7 @@ impl Item for TerminalView {
 
     fn is_dirty(&self, cx: &gpui::AppContext) -> bool {
         match self.terminal.read(cx).task() {
-            Some(task) => !task.completed,
+            Some(task) => task.status == TaskStatus::Running,
             None => self.has_bell(),
         }
     }