From dc4cea112509afd6831013a8c5cd67642527ab4b Mon Sep 17 00:00:00 2001 From: Zanie Blue Date: Mon, 8 Jul 2024 16:31:55 -0500 Subject: [PATCH] Use paging for `uv help` display when available --- Cargo.lock | 1 + crates/uv/Cargo.toml | 1 + crates/uv/src/commands/help.rs | 49 +++++++++++++++++++++++++++++++--- crates/uv/tests/help.rs | 16 +++++------ 4 files changed, 56 insertions(+), 11 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d1eb626b2bf1..9742d37ef437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4507,6 +4507,7 @@ dependencies = [ "uv-types", "uv-virtualenv", "uv-warnings", + "which", ] [[package]] diff --git a/crates/uv/Cargo.toml b/crates/uv/Cargo.toml index 2070e63614f0..491b93f76947 100644 --- a/crates/uv/Cargo.toml +++ b/crates/uv/Cargo.toml @@ -70,6 +70,7 @@ tracing-subscriber = { workspace = true, features = ["json"] } tracing-tree = { workspace = true } unicode-width = { workspace = true } url = { workspace = true } +which = { workspace = true } [target.'cfg(target_os = "windows")'.dependencies] mimalloc = { version = "0.1.39" } diff --git a/crates/uv/src/commands/help.rs b/crates/uv/src/commands/help.rs index 431513750f3a..f90f552c9b3f 100644 --- a/crates/uv/src/commands/help.rs +++ b/crates/uv/src/commands/help.rs @@ -1,8 +1,10 @@ -use std::fmt::Write; +use std::{fmt::Display, fmt::Write}; +use anstream::{stream::IsTerminal, ColorChoice}; use anyhow::{anyhow, Result}; use clap::CommandFactory; -use itertools::Itertools; +use itertools::{Either, Itertools}; +use which::which; use super::ExitStatus; use crate::printer::Printer; @@ -35,7 +37,25 @@ pub(crate) fn help(query: &[String], printer: Printer) -> Result { let mut command = command.clone(); let help = command.render_long_help(); - writeln!(printer.stderr(), "{}", help.ansi())?; + + let help_ansi = match anstream::Stdout::choice(&std::io::stdout()) { + ColorChoice::Always | ColorChoice::AlwaysAnsi => Either::Left(help.ansi()), + ColorChoice::Never => Either::Right(help.clone()), + // We just asked anstream for a choice, that can't be auto + ColorChoice::Auto => unreachable!(), + }; + + let is_terminal = std::io::stdout().is_terminal(); + if is_terminal && which("less").is_ok() { + // When using less, we use the command name as the file name and can support colors + let prompt = format!("help: uv {}", query.join(" ")); + spawn_pager("less", &["-R", "-P", &prompt], &help_ansi)?; + } else if is_terminal && which("more").is_ok() { + // When using more, we skip the ANSI color codes + spawn_pager("more", &[], &help)?; + } else { + writeln!(printer.stdout(), "{help_ansi}")?; + } Ok(ExitStatus::Success) } @@ -54,3 +74,26 @@ fn find_command<'a>( let subcommand = cmd.find_subcommand(next).ok_or((query, cmd))?; find_command(&query[1..], subcommand) } + +/// Spawn a paging command to display contents. +fn spawn_pager(command: &str, args: &[&str], contents: impl Display) -> Result<()> { + use std::io::Write; + + let mut child = std::process::Command::new(command) + .args(args) + .stdin(std::process::Stdio::piped()) + .spawn()?; + + let mut stdin = child + .stdin + .take() + .ok_or_else(|| anyhow!("Failed to take child process stdin"))?; + + let contents = contents.to_string(); + let writer = std::thread::spawn(move || stdin.write_all(contents.as_bytes())); + + drop(child.wait()); + drop(writer.join()); + + Ok(()) +} diff --git a/crates/uv/tests/help.rs b/crates/uv/tests/help.rs index 6c9d838d8746..6e12710bec2b 100644 --- a/crates/uv/tests/help.rs +++ b/crates/uv/tests/help.rs @@ -11,8 +11,6 @@ fn help() { success: true exit_code: 0 ----- stdout ----- - - ----- stderr ----- An extremely fast Python package manager. Usage: uv [OPTIONS] @@ -109,6 +107,8 @@ fn help() { -V, --version Print version + + ----- stderr ----- "###); } @@ -230,8 +230,6 @@ fn help_subcommand() { success: true exit_code: 0 ----- stdout ----- - - ----- stderr ----- Manage Python installations Usage: uv python [OPTIONS] @@ -326,6 +324,8 @@ fn help_subcommand() { -V, --version Print version + + ----- stderr ----- "###); } @@ -337,8 +337,6 @@ fn help_subsubcommand() { success: true exit_code: 0 ----- stdout ----- - - ----- stderr ----- Download and install Python versions Usage: uv python install [OPTIONS] [TARGETS]... @@ -437,6 +435,8 @@ fn help_subsubcommand() { -V, --version Print version + + ----- stderr ----- "###); } @@ -606,8 +606,6 @@ fn help_with_global_option() { success: true exit_code: 0 ----- stdout ----- - - ----- stderr ----- An extremely fast Python package manager. Usage: uv [OPTIONS] @@ -704,6 +702,8 @@ fn help_with_global_option() { -V, --version Print version + + ----- stderr ----- "###); }