Show multiline command previews in /ps (#48101)

## Why

`/ps` previously showed only the first line of each background command, hiding useful details when a command began with an opening parenthesis.

## What changed

Render LF and CRLF line breaks as `↵` in command previews. Include subsequent lines within the existing 80-grapheme limit, and show the truncation marker only when the command exceeds that limit.

## Testing

Update snapshot coverage for multiline commands, including parenthesized commands and CRLF line endings, and add a snapshot for a multiline command exceeding the preview limit.

GitOrigin-RevId: 92544e5636821fc94bc1ca4af389c3c646127c0e
This commit is contained in:
Eric Traut
2026-09-25 14:26:57 +00:00
committed by copyberry
parent 6d083e3bc4
commit e75b36efde
4 changed files with 41 additions and 12 deletions
+10 -10
View File
@@ -159,17 +159,17 @@ impl HistoryCell for UnifiedExecProcessesCell {
}
let command = &process.command_display;
let (snippet, snippet_truncated) = {
let (first_line, has_more_lines) = match command.split_once('\n') {
Some((first, _)) => (first, true),
None => (command.as_str(), false),
};
let max_graphemes = 80;
let mut graphemes = first_line.grapheme_indices(true);
if let Some((byte_index, _)) = graphemes.nth(max_graphemes) {
(first_line[..byte_index].to_string(), true)
} else {
(first_line.to_string(), has_more_lines)
}
let mut graphemes = command.graphemes(true);
let snippet: String = graphemes
.by_ref()
.take(max_graphemes)
.map(|grapheme| match grapheme {
"\n" | "\r\n" => "↵",
_ => grapheme,
})
.collect();
(snippet, graphemes.next().is_some())
};
if wrap_width <= prefix_width {
out.push(Line::from(prefix.dim()));
@@ -0,0 +1,9 @@
---
source: tui/src/history_cell/tests.rs
expression: rendered
---
/ps
Background terminals
• (↵ xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx [...]
@@ -1,13 +1,15 @@
---
source: tui/src/history_cell.rs
source: tui/src/history_cell/tests.rs
expression: rendered
---
/ps
Background terminals
• echo hello [...]
• echo hello↵and then some extra text
↳ hello
done
• (↵ sleep 120↵)
• sleep 1↵sleep 120
• rg "foo" src
↳ src/main.rs:12:foo
+18
View File
@@ -878,6 +878,14 @@ fn ps_output_multiline_snapshot() {
command_display: "echo hello\nand then some extra text".to_string(),
recent_chunks: vec!["hello".to_string(), "done".to_string()],
},
UnifiedExecProcessDetails {
command_display: "(\n sleep 120\n)".to_string(),
recent_chunks: Vec::new(),
},
UnifiedExecProcessDetails {
command_display: "sleep 1\r\nsleep 120".to_string(),
recent_chunks: Vec::new(),
},
UnifiedExecProcessDetails {
command_display: "rg \"foo\" src".to_string(),
recent_chunks: vec!["src/main.rs:12:foo".to_string()],
@@ -887,6 +895,16 @@ fn ps_output_multiline_snapshot() {
insta::assert_snapshot!(rendered);
}
#[test]
fn ps_output_multiline_long_command_snapshot() {
let cell = new_unified_exec_processes_output(vec![UnifiedExecProcessDetails {
command_display: format!("(\n {}\n)", "x".repeat(100)),
recent_chunks: Vec::new(),
}]);
let rendered = render_lines(&cell.display_lines(/*width*/ 100)).join("\n");
insta::assert_snapshot!(rendered);
}
#[test]
fn cyber_policy_error_event_snapshot() {
let cell = new_cyber_policy_error_event(crate::daybreak::Notice::Apply);