From e9378be919c8e9d1d73612d9b2b24c7a385ec6ec Mon Sep 17 00:00:00 2001 From: bluss Date: Tue, 17 Sep 2024 05:27:19 +0200 Subject: [PATCH] Generate shell completion for `uvx` (#7388) ## Summary Generate shell completion for uvx. Create a `uvx` toplevel command just for completion by combining `uv tool uvx` (hidden alias for `uv tool run`) with global arguments. This explicit combination is needed otherwise global arguments are missing (if they are missing, clap debug assertions fail when `uv tool run` arguments refer to global arguments in directives like conflicts with). Fixes #7258 ## Test Plan - Tested using bash using `eval "$(cargo run --bin uv generate-shell-completion bash)"` --- crates/uv-cli/src/lib.rs | 7 ++++ crates/uv/src/lib.rs | 49 ++++++++++++++++++++-------- docs/getting-started/installation.md | 19 +++++++++++ 3 files changed, 62 insertions(+), 13 deletions(-) diff --git a/crates/uv-cli/src/lib.rs b/crates/uv-cli/src/lib.rs index c8efeb8c1..d10d16370 100644 --- a/crates/uv-cli/src/lib.rs +++ b/crates/uv-cli/src/lib.rs @@ -76,6 +76,13 @@ pub struct Cli { #[command(subcommand)] pub command: Box, + #[command(flatten)] + pub top_level: TopLevelArgs, +} + +#[derive(Parser)] +#[command(disable_help_flag = true, disable_version_flag = true)] +pub struct TopLevelArgs { #[command(flatten)] pub cache_args: Box, diff --git a/crates/uv/src/lib.rs b/crates/uv/src/lib.rs index 0ee3a7c27..f9305e878 100644 --- a/crates/uv/src/lib.rs +++ b/crates/uv/src/lib.rs @@ -16,7 +16,7 @@ use uv_cli::{ compat::CompatArgs, CacheCommand, CacheNamespace, Cli, Commands, PipCommand, PipNamespace, ProjectCommand, }; -use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace}; +use uv_cli::{PythonCommand, PythonNamespace, ToolCommand, ToolNamespace, TopLevelArgs}; #[cfg(feature = "self-update")] use uv_cli::{SelfCommand, SelfNamespace, SelfUpdateArgs}; use uv_fs::CWD; @@ -58,17 +58,17 @@ pub(crate) mod version; #[instrument(skip_all)] async fn run(cli: Cli) -> Result { // Enable flag to pick up warnings generated by workspace loading. - if !cli.global_args.quiet { + if !cli.top_level.global_args.quiet { uv_warnings::enable(); } // Switch directories as early as possible. - if let Some(directory) = cli.global_args.directory.as_ref() { + if let Some(directory) = cli.top_level.global_args.directory.as_ref() { std::env::set_current_dir(directory)?; } // The `--isolated` argument is deprecated on preview APIs, and warns on non-preview APIs. - let deprecated_isolated = if cli.global_args.isolated { + let deprecated_isolated = if cli.top_level.global_args.isolated { match &*cli.command { // Supports `--isolated` as its own argument, so we can't warn either way. Commands::Tool(ToolNamespace { @@ -106,7 +106,7 @@ async fn run(cli: Cli) -> Result { // If found, this file is combined with the user configuration file. // 3. The nearest configuration file (`uv.toml` or `pyproject.toml`) in the directory tree, // starting from the current directory. - let filesystem = if let Some(config_file) = cli.config_file.as_ref() { + let filesystem = if let Some(config_file) = cli.top_level.config_file.as_ref() { if config_file .file_name() .is_some_and(|file_name| file_name == "pyproject.toml") @@ -114,7 +114,7 @@ async fn run(cli: Cli) -> Result { warn_user!("The `--config-file` argument expects to receive a `uv.toml` file, not a `pyproject.toml`. If you're trying to run a command from another project, use the `--directory` argument instead."); } Some(FilesystemOptions::from_file(config_file)?) - } else if deprecated_isolated || cli.no_config { + } else if deprecated_isolated || cli.top_level.no_config { None } else if matches!(&*cli.command, Commands::Tool(_)) { // For commands that operate at the user-level, ignore local configuration. @@ -175,10 +175,10 @@ async fn run(cli: Cli) -> Result { .combine(filesystem); // Resolve the global settings. - let globals = GlobalSettings::resolve(&cli.global_args, filesystem.as_ref()); + let globals = GlobalSettings::resolve(&cli.top_level.global_args, filesystem.as_ref()); // Resolve the cache settings. - let cache_settings = CacheSettings::resolve(*cli.cache_args, filesystem.as_ref()); + let cache_settings = CacheSettings::resolve(*cli.top_level.cache_args, filesystem.as_ref()); // Configure the `tracing` crate, which controls internal logging. #[cfg(feature = "tracing-durations-export")] @@ -687,7 +687,7 @@ async fn run(cli: Cli) -> Result { args.hash_checking, args.python, args.settings, - cli.no_config, + cli.top_level.no_config, globals.python_preference, globals.python_downloads, globals.connectivity, @@ -743,7 +743,7 @@ async fn run(cli: Cli) -> Result { args.settings.exclude_newer, globals.concurrency, globals.native_tls, - cli.no_config, + cli.top_level.no_config, args.no_project, &cache, printer, @@ -757,7 +757,7 @@ async fn run(cli: Cli) -> Result { run_command, script, globals, - cli.no_config, + cli.top_level.no_config, filesystem, cache, printer, @@ -777,7 +777,30 @@ async fn run(cli: Cli) -> Result { Ok(ExitStatus::Success) } Commands::GenerateShellCompletion(args) => { + // uv args.shell.generate(&mut Cli::command(), &mut stdout()); + + // uvx: combine `uv tool uvx` with the top-level arguments + let mut uvx = Cli::command() + .find_subcommand("tool") + .unwrap() + .find_subcommand("uvx") + .unwrap() + .clone() + // Avoid duplicating the `--help` and `--version` flags from the top-level arguments. + .disable_help_flag(true) + .disable_version_flag(true) + .version(env!("CARGO_PKG_VERSION")); + + // Copy the top-level arguments into the `uvx` command. (Like `Args::augment_args`, but + // expanded to skip collisions.) + for arg in TopLevelArgs::command().get_arguments() { + if arg.get_id() != "isolated" { + uvx = uvx.arg(arg); + } + } + args.shell.generate(&mut uvx, &mut stdout()); + Ok(ExitStatus::Success) } Commands::Tool(ToolNamespace { @@ -974,7 +997,7 @@ async fn run(cli: Cli) -> Result { globals.python_downloads, globals.native_tls, globals.connectivity, - cli.no_config, + cli.top_level.no_config, printer, ) .await @@ -1000,7 +1023,7 @@ async fn run(cli: Cli) -> Result { commands::python_find( args.request, args.no_project, - cli.no_config, + cli.top_level.no_config, args.system, globals.python_preference, &cache, diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index 811921980..7c2cc9afa 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -181,6 +181,25 @@ To enable shell autocompletion for uv commands, run one of the following: Then restart the shell or source the shell config file. +You can also enable shell autocompletion for uvx by running the same commands, replacing `uv` with +`uvx`: + +=== "Linux and macOS" + + ```bash + # Determine your shell (e.g., with `echo $SHELL`), then run one of: + echo 'eval "$(uvx generate-shell-completion bash)"' >> ~/.bashrc + echo 'eval "$(uvx generate-shell-completion zsh)"' >> ~/.zshrc + echo 'uvx generate-shell-completion fish | source' >> ~/.config/fish/config.fish + echo 'eval (uvx generate-shell-completion elvish | slurp)' >> ~/.elvish/rc.elv + ``` + +=== "Windows" + + ```powershell + Add-Content -Path $PROFILE -Value '(& uvx generate-shell-completion powershell) | Out-String | Invoke-Expression' + ``` + ## Uninstallation If you need to remove uv from your system, just remove the `uv` and `uvx` binaries: