diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index 247143319f9..e534061488e 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -2,7 +2,7 @@ use crate::core::compiler::{ BuildConfig, CompileKind, MessageFormat, RustcTargetData, TimingOutput, }; use crate::core::resolver::{CliFeatures, ForceAllTargets, HasDevUnits}; -use crate::core::{shell, Edition, Package, Target, TargetKind, Workspace}; +use crate::core::{shell, Edition, Package, PackageId, Target, TargetKind, Workspace}; use crate::ops::lockfile::LOCKFILE_NAME; use crate::ops::registry::RegistryOrIndex; use crate::ops::{self, CompileFilter, CompileOptions, NewOptions, Packages, VersionControl}; @@ -23,7 +23,7 @@ use cargo_util_schemas::manifest::StringOrVec; use clap::builder::UnknownArgumentValueParser; use home::cargo_home_with_cwd; use semver::Version; -use std::collections::HashMap; +use std::collections::{BTreeSet, HashMap}; use std::ffi::{OsStr, OsString}; use std::path::Path; use std::path::PathBuf; @@ -48,6 +48,8 @@ pub mod heading { pub trait CommandExt: Sized { fn _arg(self, arg: Arg) -> Self; + fn _name(&self) -> String; + /// Do not use this method, it is only for backwards compatibility. /// Use `arg_package_spec_no_all` instead. fn arg_package_spec( @@ -87,10 +89,34 @@ pub trait CommandExt: Sized { } fn arg_package_spec_simple(self, package: &'static str) -> Self { + let name = self._name(); + // Use the specialized functions based on the command name + match name.as_str() { + "build" => self.arg_package_spec_with_completer(package, get_ws_member_candidates), + "tree" => self.arg_package_spec_with_completer(package, get_dependency_graph_candidates), + "uninstall" => self.arg_package_spec_with_completer(package, get_installed_package_candidates), + _ => self._arg( + optional_multi_opt("package", "SPEC", package) + .short('p') + .help_heading(heading::PACKAGE_SELECTION), + ), + } + } + + // Internal generic function for package arg with custom completer + fn arg_package_spec_with_completer( + self, + package: &'static str, + completer: F + ) -> Self + where + F: Fn() -> Vec + 'static + Clone + Send + std::marker::Sync, + { self._arg( optional_multi_opt("package", "SPEC", package) .short('p') - .help_heading(heading::PACKAGE_SELECTION), + .help_heading(heading::PACKAGE_SELECTION) + .add(clap_complete::ArgValueCandidates::new(completer)), ) } @@ -99,7 +125,10 @@ pub trait CommandExt: Sized { optional_opt("package", package) .short('p') .value_name("SPEC") - .help_heading(heading::PACKAGE_SELECTION), + .help_heading(heading::PACKAGE_SELECTION) + .add(clap_complete::ArgValueCandidates::new( + get_ws_member_candidates, + )), ) } @@ -480,6 +509,10 @@ impl CommandExt for Command { fn _arg(self, arg: Arg) -> Self { self.arg(arg) } + + fn _name(&self) -> String { + self.get_name().to_string() + } } pub fn flag(name: &'static str, help: &'static str) -> Arg { @@ -1298,6 +1331,78 @@ fn get_packages() -> CargoResult> { Ok(packages) } +fn get_ws_member_candidates() -> Vec { + get_ws_member_packages() + .unwrap_or_default() + .into_iter() + .map(|pkg| { + clap_complete::CompletionCandidate::new(pkg.name().as_str()).help( + pkg.manifest() + .metadata() + .description + .to_owned() + .map(From::from), + ) + }) + .collect::>() +} + +fn get_ws_member_packages() -> CargoResult> { + let gctx = new_gctx_for_completions()?; + let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; + + let packages = ws.members().map(|pkg| pkg.to_owned()).collect::>(); + + Ok(packages) +} + +fn get_dependency_graph_candidates() -> Vec { + get_packages() + .unwrap_or_default() + .into_iter() + .map(|pkg| { + clap_complete::CompletionCandidate::new(pkg.name().as_str()).help( + pkg.manifest() + .metadata() + .description + .to_owned() + .map(From::from), + ) + }) + .collect::>() +} + +fn get_installed_package_candidates() -> Vec { + match get_installed_packages() { + Ok(packages) => packages + .into_iter() + .map(|(pkg_id, bins)| { + let bin_list = bins.iter().collect::>().join(", "); + let help = if bins.len() == 1 { + format!("Installed binary: {}", bin_list) + } else { + format!("Installed binaries: {}", bin_list) + }; + clap_complete::CompletionCandidate::new(pkg_id.name().as_str()).help(Some(help.into())) + }) + .collect(), + Err(_) => vec![], + } +} + +fn get_installed_packages() -> CargoResult)>> { + let gctx = new_gctx_for_completions()?; + let root = ops::resolve_root(None, &gctx)?; + let tracker = ops::InstallTracker::load(&gctx, &root)?; + + let installed = tracker + .all_installed_bins() + .map(|(pkg_id, bins)| (*pkg_id, bins.clone())) + .collect(); + + Ok(installed) +} + fn new_gctx_for_completions() -> CargoResult { let cwd = std::env::current_dir()?; let mut gctx = GlobalContext::new(shell::Shell::new(), cwd.clone(), cargo_home_with_cwd(&cwd)?);