Respect `NO_COLOR` and always show the command as a header when paging `uv help` output (#16908)

## Summary

Fix #16879 and address an additional issue where the pager prompt would
be bold regardless of NO_COLOR.

<img width="1437" height="483" alt="image"
src="https://github.com/user-attachments/assets/7234129a-364b-40c6-834a-57ac34212925"
/>

## Test Plan

Ran the test suite and manually verified against `less` 679 and `busybox` v1.34.1.

Checked with and without `NO_COLOR` on both "normal" `less`, `busybox` `less`, and `more`.
This commit is contained in:
Tomasz Kramkowski 2025-12-01 18:00:43 +00:00 committed by GitHub
parent 05814f9cd5
commit efa47adefb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
1 changed files with 22 additions and 22 deletions

View File

@ -6,7 +6,7 @@ use std::{fmt::Display, fmt::Write};
use anstream::{ColorChoice, stream::IsTerminal}; use anstream::{ColorChoice, stream::IsTerminal};
use anyhow::{Result, anyhow}; use anyhow::{Result, anyhow};
use clap::CommandFactory; use clap::CommandFactory;
use itertools::{Either, Itertools}; use itertools::Itertools;
use owo_colors::OwoColorize; use owo_colors::OwoColorize;
use which::which; use which::which;
@ -70,9 +70,9 @@ pub(crate) fn help(query: &[String], printer: Printer, no_pager: bool) -> Result
.render_long_help() .render_long_help()
}; };
let help_ansi = match anstream::Stdout::choice(&std::io::stdout()) { let want_color = match anstream::Stdout::choice(&std::io::stdout()) {
ColorChoice::Always | ColorChoice::AlwaysAnsi => Either::Left(help.ansi()), ColorChoice::Always | ColorChoice::AlwaysAnsi => true,
ColorChoice::Never => Either::Right(help.clone()), ColorChoice::Never => false,
// We just asked anstream for a choice, that can't be auto // We just asked anstream for a choice, that can't be auto
ColorChoice::Auto => unreachable!(), ColorChoice::Auto => unreachable!(),
}; };
@ -80,22 +80,19 @@ pub(crate) fn help(query: &[String], printer: Printer, no_pager: bool) -> Result
let is_terminal = std::io::stdout().is_terminal(); let is_terminal = std::io::stdout().is_terminal();
let should_page = !no_pager && !is_root && is_terminal; let should_page = !no_pager && !is_root && is_terminal;
if should_page { if should_page && let Some(pager) = Pager::try_from_env() {
if let Some(pager) = Pager::try_from_env() { let query = query.join(" ");
let content = if pager.supports_colors() { if want_color && pager.supports_colors() {
help_ansi pager.spawn(format!("{}: {query}", "uv help".bold()), help.ansi())?;
} else {
Either::Right(help.clone())
};
pager.spawn(
format!("{}: {}", "uv help".bold(), query.join(" ")),
&content,
)?;
} else { } else {
writeln!(printer.stdout(), "{help_ansi}")?; pager.spawn(format!("uv help: {query}"), help)?;
} }
} else { } else {
writeln!(printer.stdout(), "{help_ansi}")?; if want_color {
writeln!(printer.stdout(), "{}", help.ansi())?;
} else {
writeln!(printer.stdout(), "{help}")?;
}
} }
Ok(ExitStatus::Success) Ok(ExitStatus::Success)
@ -131,9 +128,9 @@ struct Pager {
} }
impl PagerKind { impl PagerKind {
fn default_args(&self, prompt: String) -> Vec<String> { fn default_args(&self) -> Vec<String> {
match self { match self {
Self::Less => vec!["-R".to_string(), "-P".to_string(), prompt], Self::Less => vec!["-R".to_string()],
Self::More => vec![], Self::More => vec![],
Self::Other(_) => vec![], Self::Other(_) => vec![],
} }
@ -183,7 +180,7 @@ impl FromStr for Pager {
impl Pager { impl Pager {
/// Display `contents` using the pager. /// Display `contents` using the pager.
fn spawn(self, prompt: String, contents: impl Display) -> Result<()> { fn spawn(self, heading: String, contents: impl Display) -> Result<()> {
use std::io::Write; use std::io::Write;
let command = self let command = self
@ -193,7 +190,7 @@ impl Pager {
.unwrap_or(OsString::from(self.kind.to_string())); .unwrap_or(OsString::from(self.kind.to_string()));
let args = if self.args.is_empty() { let args = if self.args.is_empty() {
self.kind.default_args(prompt) self.kind.default_args()
} else { } else {
self.args self.args
}; };
@ -209,7 +206,10 @@ impl Pager {
.ok_or_else(|| anyhow!("Failed to take child process stdin"))?; .ok_or_else(|| anyhow!("Failed to take child process stdin"))?;
let contents = contents.to_string(); let contents = contents.to_string();
let writer = std::thread::spawn(move || stdin.write_all(contents.as_bytes())); let writer = std::thread::spawn(move || {
let _ = write!(stdin, "{heading}\n\n");
let _ = stdin.write_all(contents.as_bytes());
});
drop(child.wait()); drop(child.wait());
drop(writer.join()); drop(writer.join());