Skip to content

Commit

Permalink
Use paging for uv help display when available
Browse files Browse the repository at this point in the history
  • Loading branch information
zanieb committed Jul 8, 2024
1 parent aeb3de7 commit 7265779
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 94 deletions.
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions crates/uv/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ regex = { workspace = true }
rustc-hash = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tempfile = { workspace = true }
textwrap = { workspace = true }
thiserror = { workspace = true }
tokio = { workspace = true }
Expand All @@ -69,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" }
Expand Down
58 changes: 51 additions & 7 deletions crates/uv/src/commands/help.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
use std::fmt::Write;
use std::{fmt::Display, fmt::Write, path::PathBuf};

use anyhow::{anyhow, Result};
use clap::CommandFactory;
use itertools::Itertools;
use which::which;

use super::ExitStatus;
use crate::printer::Printer;
use uv_cli::Cli;

pub(crate) fn help(command: Vec<String>, printer: Printer) -> Result<ExitStatus> {
pub(crate) fn help(query: Vec<String>, printer: Printer) -> Result<ExitStatus> {
let uv = Cli::command();
let command = find_command(command.as_slice(), &uv).map_err(|(unmatched, nearest)| {
let missing = if unmatched.len() == command.len() {
format!("`{}` for `uv`", command.join(" "))
let command = find_command(query.as_slice(), &uv).map_err(|(unmatched, nearest)| {
let missing = if unmatched.len() == query.len() {
format!("`{}` for `uv`", query.join(" "))
} else {
format!("`{}` for `uv {}`", unmatched.join(" "), nearest.get_name())
};
Expand All @@ -22,15 +23,26 @@ pub(crate) fn help(command: Vec<String>, printer: Printer) -> Result<ExitStatus>
nearest
.get_subcommands()
.filter(|cmd| !cmd.is_hide_set())
.map(|cmd| cmd.get_name())
.map(clap::Command::get_name)
.filter(|name| *name != "help")
.join("\n "),
)
})?;

let mut command = command.clone();
let help = command.render_long_help();
writeln!(printer.stderr(), "{}", help.ansi())?;

if let Ok(less) = which("less") {
// When using less, we use the command name as the file name and can support colors
let prompt = format!("help: uv {}", query.join(" "));
write_and_spawn(&query.join("-"), &help.ansi(), less, &["-R", "-P", &prompt])?;
} else if let Ok(more) = which("more") {
// When using more, we skip the ANSI color codes
write_and_spawn(&query.join("-"), &help, more, &[])?;
} else {
// TODO(zanieb): Check if colors are enabled?
writeln!(printer.stderr(), "{}", help.ansi())?;
}

Ok(ExitStatus::Success)
}
Expand All @@ -49,3 +61,35 @@ fn find_command<'a>(
let subcommand = cmd.find_subcommand(next).ok_or((query, cmd))?;
find_command(&query[1..], subcommand)
}

/// Write the contents of documentation to disk and spawn the given command to
/// display it.
fn write_and_spawn(
name: &str,
contents: impl Display,
command: PathBuf,
args: &[&str],
) -> Result<()> {
// Note this implementation is based on Cargo's `help` command which displays a `man` page.
// See https://github.com/rust-lang/cargo/blob/e98f702d83d3075b2232b4502114f177826fe06d/src/bin/cargo/commands/help.rs
use std::io::Write;

let prefix = format!("uv-{name}.");
let mut tmp = tempfile::Builder::new().prefix(&prefix).tempfile()?;
let f = tmp.as_file_mut();
write!(f, "{contents}")?;
f.flush()?;

let path = tmp.path();
// Use a path relative to the temp directory so that it can work on
// cygwin/msys systems which don't handle windows-style paths.
let mut relative_name = std::ffi::OsString::from("./");
relative_name.push(path.file_name().unwrap());
let mut cmd = std::process::Command::new(command)
.args(args)
.arg(relative_name)
.current_dir(path.parent().unwrap())
.spawn()?;
drop(cmd.wait());
Ok(())
}
Loading

0 comments on commit 7265779

Please sign in to comment.