Skip to content

Commit 6f102bf

Browse files
authored
more: gate tty-only writes; extract clear_line/highlight_text (#12224)
Pager unconditionally wrote ANSI sequences (Attribute::Reverse, Clear, MoveTo) even when stdout was not a tty, leaking escape codes into pipes. Gate each on stdout.is_tty() through two helpers and apply them at every call site: error prompts, multi-file header, screen update, line clear, pattern highlight, and the status bar.
1 parent f057cc6 commit 6f102bf

1 file changed

Lines changed: 42 additions & 31 deletions

File tree

src/uu/more/src/more.rs

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -509,19 +509,36 @@ impl<'a> Pager<'a> {
509509
}
510510
}
511511

512+
/// Queue a line clear only when stdout is a tty; no-op otherwise so ANSI bytes
513+
/// don't leak into downstream pipes.
514+
fn clear_line(&mut self) -> std::io::Result<()> {
515+
if self.stdout.is_tty() {
516+
self.stdout.queue(Clear(ClearType::CurrentLine))?;
517+
}
518+
Ok(())
519+
}
520+
521+
/// Wrap `text` in the reverse-video attribute when stdout is a tty; return
522+
/// the plain text otherwise.
523+
fn highlight_text(&self, text: &str) -> String {
524+
if self.stdout.is_tty() {
525+
format!("{}{text}{}", Attribute::Reverse, Attribute::Reset)
526+
} else {
527+
text.to_string()
528+
}
529+
}
530+
512531
fn handle_from_line(&mut self) -> UResult<()> {
513532
if !self.read_until_line(self.upper_mark)? {
514-
write!(
515-
self.stdout,
516-
"\r{}{} ({}){}",
517-
Attribute::Reverse,
533+
let msg = format!(
534+
"{} ({})",
518535
translate!(
519536
"more-error-cannot-seek-to-line",
520537
"line" => (self.upper_mark + 1)
521538
),
522539
translate!("more-press-return"),
523-
Attribute::Reset,
524-
)?;
540+
);
541+
write!(self.stdout, "\r{}", self.highlight_text(&msg))?;
525542
self.stdout.flush()?;
526543
self.wait_for_enter_key()?;
527544
self.upper_mark = 0;
@@ -576,14 +593,12 @@ impl<'a> Pager<'a> {
576593
self.upper_mark = line;
577594
} else {
578595
self.pattern = None;
579-
write!(
580-
self.stdout,
581-
"\r{}{} ({}){}",
582-
Attribute::Reverse,
596+
let msg = format!(
597+
"{} ({})",
583598
translate!("more-error-pattern-not-found"),
584599
translate!("more-press-return"),
585-
Attribute::Reset,
586-
)?;
600+
);
601+
write!(self.stdout, "\r{}", self.highlight_text(&msg))?;
587602
self.stdout.flush()?;
588603
self.wait_for_enter_key()?;
589604
}
@@ -610,7 +625,7 @@ impl<'a> Pager<'a> {
610625
}
611626

612627
fn display_multi_file_header(&mut self) -> UResult<()> {
613-
self.stdout.queue(Clear(ClearType::CurrentLine))?;
628+
self.clear_line()?;
614629
self.stdout.write_all(
615630
MULTI_FILE_TOP_PROMPT
616631
.replace("{}", self.file_name.unwrap_or_default())
@@ -629,6 +644,9 @@ impl<'a> Pager<'a> {
629644
}
630645

631646
fn update_display(&mut self, options: &Options) -> UResult<()> {
647+
if !self.stdout.is_tty() {
648+
return Ok(());
649+
}
632650
if options.print_over {
633651
self.stdout
634652
.execute(MoveTo(0, 0))?
@@ -774,14 +792,14 @@ impl<'a> Pager<'a> {
774792

775793
fn draw(&mut self, wrong_key: Option<char>) -> UResult<()> {
776794
self.draw_lines()?;
777-
self.draw_status_bar(wrong_key);
795+
self.draw_status_bar(wrong_key)?;
778796
self.stdout.flush()?;
779797
Ok(())
780798
}
781799

782800
fn draw_lines(&mut self) -> UResult<()> {
783801
// Clear current prompt line
784-
self.stdout.queue(Clear(ClearType::CurrentLine))?;
802+
self.clear_line()?;
785803
// Reset squeezed lines counter
786804
self.lines_squeezed = 0;
787805
// Display lines until we've filled the screen
@@ -803,11 +821,8 @@ impl<'a> Pager<'a> {
803821
// Display the line
804822
let mut line = self.lines[index].clone();
805823
if let Some(pattern) = &self.pattern {
806-
// Highlight the pattern in the line
807-
line = line.replace(
808-
pattern,
809-
&format!("{}{pattern}{}", Attribute::Reverse, Attribute::Reset),
810-
);
824+
let highlighted = self.highlight_text(pattern);
825+
line = line.replace(pattern, &highlighted);
811826
}
812827
self.stdout.write_all(format!("\r{line}\n").as_bytes())?;
813828
lines_printed += 1;
@@ -833,7 +848,7 @@ impl<'a> Pager<'a> {
833848
}
834849
}
835850

836-
fn draw_status_bar(&mut self, wrong_key: Option<char>) {
851+
fn draw_status_bar(&mut self, wrong_key: Option<char>) -> std::io::Result<()> {
837852
// Calculate the index of the last visible line
838853
let lower_mark =
839854
(self.upper_mark + self.content_rows).min(self.lines.len().saturating_sub(1));
@@ -885,13 +900,9 @@ impl<'a> Pager<'a> {
885900
(false, None) => status,
886901
};
887902
// Draw the status bar at the bottom of the screen
888-
write!(
889-
self.stdout,
890-
"\r{}{banner}{}",
891-
Attribute::Reverse,
892-
Attribute::Reset
893-
)
894-
.unwrap();
903+
let styled = self.highlight_text(&banner);
904+
write!(self.stdout, "\r{styled}")?;
905+
Ok(())
895906
}
896907
}
897908

@@ -1075,7 +1086,7 @@ mod tests {
10751086
.from_line(3)
10761087
.silent()
10771088
.build();
1078-
pager.draw_status_bar(None);
1089+
pager.draw_status_bar(None).unwrap();
10791090
let stdout = String::from_utf8_lossy(&pager.stdout);
10801091
assert!(stdout.contains(&translate!("more-help-message")));
10811092
}
@@ -1182,15 +1193,15 @@ mod tests {
11821193
#[test]
11831194
fn test_wrong_key() {
11841195
let mut pager = TestPagerBuilder::default().silent().build();
1185-
pager.draw_status_bar(Some('x'));
1196+
pager.draw_status_bar(Some('x')).unwrap();
11861197
let stdout = String::from_utf8_lossy(&pager.stdout);
11871198
assert!(stdout.contains(&translate!(
11881199
"more-error-unknown-key",
11891200
"key" => "x"
11901201
)));
11911202

11921203
pager = TestPagerBuilder::default().build();
1193-
pager.draw_status_bar(Some('x'));
1204+
pager.draw_status_bar(Some('x')).unwrap();
11941205
let stdout = String::from_utf8_lossy(&pager.stdout);
11951206
assert!(stdout.contains(&BELL.to_string()));
11961207
}

0 commit comments

Comments
 (0)