From 373dcc2af6897224ce58af7f58b2f7ae7e3ec481 Mon Sep 17 00:00:00 2001 From: shannmu Date: Tue, 17 Sep 2024 15:11:29 +0800 Subject: [PATCH] feat: Add support for completing `cargo update ` --- src/bin/cargo/commands/update.rs | 5 +- src/cargo/util/command_prelude.rs | 161 +++++++++++++++++++++++++++++- 2 files changed, 161 insertions(+), 5 deletions(-) diff --git a/src/bin/cargo/commands/update.rs b/src/bin/cargo/commands/update.rs index 4c19bcb270d..a1733a50487 100644 --- a/src/bin/cargo/commands/update.rs +++ b/src/bin/cargo/commands/update.rs @@ -13,7 +13,10 @@ pub fn cli() -> Command { .value_name("SPEC") .help_heading(heading::PACKAGE_SELECTION) .group("package-group") - .help("Package to update")]) + .help("Package to update") + .add(clap_complete::ArgValueCandidates::new( + get_pkg_id_spec_candidates, + ))]) .arg( optional_multi_opt("package", "SPEC", "Package to update") .short('p') diff --git a/src/cargo/util/command_prelude.rs b/src/cargo/util/command_prelude.rs index 3069a379971..247143319f9 100644 --- a/src/cargo/util/command_prelude.rs +++ b/src/cargo/util/command_prelude.rs @@ -1,9 +1,11 @@ -use crate::core::compiler::{BuildConfig, MessageFormat, TimingOutput}; -use crate::core::resolver::CliFeatures; -use crate::core::{shell, Edition, Target, TargetKind, Workspace}; +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::ops::lockfile::LOCKFILE_NAME; use crate::ops::registry::RegistryOrIndex; -use crate::ops::{CompileFilter, CompileOptions, NewOptions, Packages, VersionControl}; +use crate::ops::{self, CompileFilter, CompileOptions, NewOptions, Packages, VersionControl}; use crate::util::important_paths::find_root_manifest_for_wd; use crate::util::interning::InternedString; use crate::util::is_rustup; @@ -20,6 +22,8 @@ use cargo_util_schemas::manifest::RegistryName; 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::ffi::{OsStr, OsString}; use std::path::Path; use std::path::PathBuf; @@ -1174,6 +1178,155 @@ fn get_target_triples_from_rustc() -> CargoResult Vec { + let mut candidates = vec![]; + + let package_map = HashMap::<&str, Vec>::new(); + let package_map = + get_packages() + .unwrap_or_default() + .into_iter() + .fold(package_map, |mut map, package| { + map.entry(package.name().as_str()) + .or_insert_with(Vec::new) + .push(package); + map + }); + + let unique_name_candidates = package_map + .iter() + .filter(|(_name, packages)| packages.len() == 1) + .map(|(name, packages)| { + clap_complete::CompletionCandidate::new(name.to_string()).help( + packages[0] + .manifest() + .metadata() + .description + .to_owned() + .map(From::from), + ) + }) + .collect::>(); + + let duplicate_name_pairs = package_map + .iter() + .filter(|(_name, packages)| packages.len() > 1) + .collect::>(); + + let mut duplicate_name_candidates = vec![]; + for (name, packages) in duplicate_name_pairs { + let mut version_count: HashMap<&Version, usize> = HashMap::new(); + + for package in packages { + *version_count.entry(package.version()).or_insert(0) += 1; + } + + for package in packages { + if let Some(&count) = version_count.get(package.version()) { + if count == 1 { + duplicate_name_candidates.push( + clap_complete::CompletionCandidate::new(format!( + "{}@{}", + name, + package.version() + )) + .help( + package + .manifest() + .metadata() + .description + .to_owned() + .map(From::from), + ), + ); + } else { + duplicate_name_candidates.push( + clap_complete::CompletionCandidate::new(format!( + "{}", + package.package_id().to_spec() + )) + .help( + package + .manifest() + .metadata() + .description + .to_owned() + .map(From::from), + ), + ) + } + } + } + } + + candidates.extend(unique_name_candidates); + candidates.extend(duplicate_name_candidates); + + candidates +} + +fn get_packages() -> CargoResult> { + let gctx = new_gctx_for_completions()?; + + let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?; + + let requested_kinds = CompileKind::from_requested_targets(ws.gctx(), &[])?; + let mut target_data = RustcTargetData::new(&ws, &requested_kinds)?; + // `cli_features.all_features` must be true in case that `specs` is empty. + let cli_features = CliFeatures::new_all(true); + let has_dev_units = HasDevUnits::Yes; + let force_all_targets = ForceAllTargets::No; + let dry_run = true; + + let ws_resolve = ops::resolve_ws_with_opts( + &ws, + &mut target_data, + &requested_kinds, + &cli_features, + &[], + has_dev_units, + force_all_targets, + dry_run, + )?; + + let packages = ws_resolve + .pkg_set + .packages() + .map(Clone::clone) + .collect::>(); + + Ok(packages) +} + +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)?); + + let verbose = 0; + let quiet = true; + let color = None; + let frozen = false; + let locked = true; + let offline = false; + let target_dir = None; + let unstable_flags = &[]; + let cli_config = &[]; + + gctx.configure( + verbose, + quiet, + color, + frozen, + locked, + offline, + &target_dir, + unstable_flags, + cli_config, + )?; + + Ok(gctx) +} + #[track_caller] pub fn ignore_unknown(r: Result) -> T { match r {