Add process ID to terminal tab tooltips (#21955)

Angelk90 , Marshall Bowers , and Danilo Leal created

Closes #12807

| Before | After |
|--------|--------|
| <img width="1336" alt="Screenshot 2025-01-09 at 2 14 15 PM"
src="https://github.com/user-attachments/assets/8396cf41-74eb-4b5c-89e3-287e4f2ddd1d"
/> | <img width="1336" alt="Screenshot 2025-01-09 at 2 13 34 PM"
src="https://github.com/user-attachments/assets/b39c51e8-fd2c-41fe-9493-396057bd71db"
/> |

Release Notes:

- Added the process ID (PID) to terminal tab tooltips.

---------

Co-authored-by: Marshall Bowers <elliott.codes@gmail.com>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

crates/terminal/src/pty_info.rs                  | 14 ++++++
crates/terminal_view/src/terminal_tab_tooltip.rs | 36 ++++++++++++++++++
crates/terminal_view/src/terminal_view.rs        | 19 ++++++++-
3 files changed, 65 insertions(+), 4 deletions(-)

Detailed changes

crates/terminal/src/pty_info.rs 🔗

@@ -10,7 +10,7 @@ use windows::Win32::{Foundation::HANDLE, System::Threading::GetProcessId};
 
 use sysinfo::{Pid, Process, ProcessRefreshKind, RefreshKind, System, UpdateKind};
 
-struct ProcessIdGetter {
+pub struct ProcessIdGetter {
     handle: i32,
     fallback_pid: u32,
 }
@@ -31,6 +31,10 @@ impl ProcessIdGetter {
         }
         Some(Pid::from_u32(pid as u32))
     }
+
+    pub fn fallback_pid(&self) -> u32 {
+        self.fallback_pid
+    }
 }
 
 #[cfg(windows)]
@@ -62,6 +66,10 @@ impl ProcessIdGetter {
         }
         Some(Pid::from_u32(pid))
     }
+
+    pub fn fallback_pid(&self) -> u32 {
+        self.fallback_pid
+    }
 }
 
 #[derive(Clone, Debug)]
@@ -96,6 +104,10 @@ impl PtyProcessInfo {
         }
     }
 
+    pub fn pid_getter(&self) -> &ProcessIdGetter {
+        &self.pid_getter
+    }
+
     fn refresh(&mut self) -> Option<&Process> {
         let pid = self.pid_getter.pid()?;
         if self.system.refresh_processes_specifics(

crates/terminal_view/src/terminal_tab_tooltip.rs 🔗

@@ -0,0 +1,36 @@
+use gpui::{IntoElement, Render, ViewContext};
+use ui::{prelude::*, tooltip_container, Divider};
+
+pub struct TerminalTooltip {
+    title: SharedString,
+    pid: u32,
+}
+
+impl TerminalTooltip {
+    pub fn new(title: impl Into<SharedString>, pid: u32) -> Self {
+        Self {
+            title: title.into(),
+            pid,
+        }
+    }
+}
+
+impl Render for TerminalTooltip {
+    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
+        tooltip_container(cx, move |this, _cx| {
+            this.occlude()
+                .on_mouse_move(|_, cx| cx.stop_propagation())
+                .child(
+                    v_flex()
+                        .gap_1()
+                        .child(Label::new(self.title.clone()))
+                        .child(Divider::horizontal())
+                        .child(
+                            Label::new(format!("Process ID (PID): {}", self.pid))
+                                .color(Color::Muted)
+                                .size(LabelSize::Small),
+                        ),
+                )
+        })
+    }
+}

crates/terminal_view/src/terminal_view.rs 🔗

@@ -1,6 +1,7 @@
 mod persistence;
 pub mod terminal_element;
 pub mod terminal_panel;
+pub mod terminal_tab_tooltip;
 
 use collections::HashSet;
 use editor::{actions::SelectAll, scroll::Autoscroll, Editor};
@@ -26,13 +27,16 @@ use terminal::{
 };
 use terminal_element::{is_blank, TerminalElement};
 use terminal_panel::TerminalPanel;
+use terminal_tab_tooltip::TerminalTooltip;
 use ui::{h_flex, prelude::*, ContextMenu, Icon, IconName, Label, Tooltip};
 use util::{
     paths::{PathWithPosition, SanitizedPath},
     ResultExt,
 };
 use workspace::{
-    item::{BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams},
+    item::{
+        BreadcrumbText, Item, ItemEvent, SerializableItem, TabContentParams, TabTooltipContent,
+    },
     register_serializable_item,
     searchable::{SearchEvent, SearchOptions, SearchableItem, SearchableItemHandle},
     CloseActiveItem, NewCenterTerminal, NewTerminal, OpenVisible, ToolbarItemLocation, Workspace,
@@ -996,8 +1000,17 @@ impl Render for TerminalView {
 impl Item for TerminalView {
     type Event = ItemEvent;
 
-    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
-        Some(self.terminal().read(cx).title(false).into())
+    fn tab_tooltip_content(&self, cx: &AppContext) -> Option<TabTooltipContent> {
+        let terminal = self.terminal().read(cx);
+        let title = terminal.title(false);
+        let pid = terminal.pty_info.pid_getter().fallback_pid();
+
+        Some(TabTooltipContent::Custom(Box::new(
+            move |cx: &mut WindowContext| {
+                cx.new_view(|_| TerminalTooltip::new(title.clone(), pid))
+                    .into()
+            },
+        )))
     }
 
     fn tab_content(&self, params: TabContentParams, cx: &WindowContext) -> AnyElement {