Use paging for uv help display when available (#4909)

Extends https://github.com/astral-sh/uv/pull/4906

Adds paged display of "long' help to `uv help` invocations when `less`
or `more` is available.
This commit is contained in:
Zanie Blue 2024-07-09 14:06:27 -04:00 committed by GitHub
parent 8da91f5a97
commit bd7a25f604
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 56 additions and 11 deletions

View file

@ -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" }

View file

@ -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<ExitStatus> {
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(())
}

View file

@ -11,8 +11,6 @@ fn help() {
success: true
exit_code: 0
----- stdout -----
----- stderr -----
An extremely fast Python package manager.
Usage: uv [OPTIONS] <COMMAND>
@ -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] <COMMAND>
@ -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] <COMMAND>
@ -704,6 +702,8 @@ fn help_with_global_option() {
-V, --version
Print version
----- stderr -----
"###);
}