feat: add /ps (#8279)

See snapshots for view of edge cases
This is still named `UnifiedExecSessions` for consistency across the
code but should be renamed to `BackgroundTerminals` in a follow-up

Example:
<img width="945" height="687" alt="Screenshot 2025-12-18 at 20 12 53"
src="https://github.com/user-attachments/assets/92f39ff2-243c-4006-b402-e3fa9e93c952"
/>
This commit is contained in:
jif-oai
2025-12-18 21:09:06 +00:00
committed by GitHub
parent 87abf06e78
commit 4fb0b547d6
11 changed files with 240 additions and 87 deletions

View File

@@ -0,0 +1,14 @@
---
source: tui/src/bottom_pane/unified_exec_footer.rs
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 50, height: 1 },
content: [
" 123 background terminals running · /ps to view ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 48, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}

View File

@@ -1,26 +1,14 @@
---
source: tui/src/bottom_pane/unified_exec_footer.rs
assertion_line: 123
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 50, height: 3 },
area: Rect { x: 0, y: 0, width: 50, height: 1 },
content: [
" Background terminal running: echo hello · rg ",
" "foo" src · 1 more ",
" running ",
" 1 background terminal running · /ps to view ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 30, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 31, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 41, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 44, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 46, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 31, y: 1, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 40, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 49, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 31, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 38, y: 2, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 45, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}

View File

@@ -1,22 +0,0 @@
---
source: tui/src/bottom_pane/unified_exec_footer.rs
assertion_line: 108
expression: "format!(\"{buf:?}\")"
---
Buffer {
area: Rect { x: 0, y: 0, width: 50, height: 2 },
content: [
" Background terminal running: echo hello · rg ",
" "foo" src ",
],
styles: [
x: 0, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 30, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 31, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 41, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: DIM,
x: 44, y: 0, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 46, y: 0, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
x: 31, y: 1, fg: Cyan, bg: Reset, underline: Reset, modifier: NONE,
x: 40, y: 1, fg: Reset, bg: Reset, underline: Reset, modifier: NONE,
]
}

View File

@@ -4,13 +4,8 @@ use ratatui::style::Stylize;
use ratatui::text::Line;
use ratatui::widgets::Paragraph;
use crate::live_wrap::take_prefix_by_width;
use crate::render::renderable::Renderable;
use crate::text_formatting::truncate_text;
use crate::wrapping::RtOptions;
use crate::wrapping::word_wrap_lines;
const MAX_SESSION_LABEL_GRAPHEMES: usize = 48;
const MAX_VISIBLE_SESSIONS: usize = 2;
pub(crate) struct UnifiedExecFooter {
sessions: Vec<String>,
@@ -40,34 +35,11 @@ impl UnifiedExecFooter {
return Vec::new();
}
let label = " Background terminal running:";
let mut spans = Vec::new();
spans.push(label.dim());
spans.push(" ".into());
let visible = self.sessions.iter().take(MAX_VISIBLE_SESSIONS);
let mut visible_count = 0usize;
for (idx, command) in visible.enumerate() {
if idx > 0 {
spans.push(" · ".dim());
}
let truncated = truncate_text(command, MAX_SESSION_LABEL_GRAPHEMES);
spans.push(truncated.cyan());
visible_count += 1;
}
let remaining = self.sessions.len().saturating_sub(visible_count);
if remaining > 0 {
spans.push(" · ".dim());
spans.push(format!("{remaining} more running").dim());
}
let indent = " ".repeat(label.len() + 1);
let line = Line::from(spans);
word_wrap_lines(
std::iter::once(line),
RtOptions::new(width as usize).subsequent_indent(Line::from(indent).dim()),
)
let count = self.sessions.len();
let plural = if count == 1 { "" } else { "s" };
let message = format!(" {count} background terminal{plural} running · /ps to view");
let (truncated, _, _) = take_prefix_by_width(&message, width as usize);
vec![Line::from(truncated.dim())]
}
}
@@ -97,29 +69,25 @@ mod tests {
assert_eq!(footer.desired_height(40), 0);
}
#[test]
fn render_two_sessions() {
let mut footer = UnifiedExecFooter::new();
footer.set_sessions(vec!["echo hello".to_string(), "rg \"foo\" src".to_string()]);
let width = 50;
let height = footer.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
footer.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_two_sessions", format!("{buf:?}"));
}
#[test]
fn render_more_sessions() {
let mut footer = UnifiedExecFooter::new();
footer.set_sessions(vec![
"echo hello".to_string(),
"rg \"foo\" src".to_string(),
"cat README.md".to_string(),
]);
footer.set_sessions(vec!["rg \"foo\" src".to_string()]);
let width = 50;
let height = footer.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
footer.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_more_sessions", format!("{buf:?}"));
}
#[test]
fn render_many_sessions() {
let mut footer = UnifiedExecFooter::new();
footer.set_sessions((0..123).map(|idx| format!("cmd {idx}")).collect());
let width = 50;
let height = footer.desired_height(width);
let mut buf = Buffer::empty(Rect::new(0, 0, width, height));
footer.render(Rect::new(0, 0, width, height), &mut buf);
assert_snapshot!("render_many_sessions", format!("{buf:?}"));
}
}