Skip to content

Commit 88c0fba

Browse files
authored
Add native completions for --package on various commands (#16210)
### What does this PR try to resolve? Add dynamic completions for the various `-p <package>` arguments. Fixes #15004 ``` cargo bench|build|check|doc|fix|test -p <tab>` cargo package|publish -p <tab> cargo add|pkgid|remove|report|run|rustc|rustdoc -p <tab> cargo uninstall -p <tab> cargo tree -p <tab> -i <tab> cargo clean -p <tab> # not implemented ``` Supersedes #14553 and #15338 --- EDIT: the second implementation has been chosen In the first commit I implemented the different completions for different usages by splitting the `arg_package` methods into multiple ones if necessary: ``` * arg_package_spec * arg_package_spec_no_all * arg_installed_package_spec_no_all <- new (for cargo uninstall) * arg_dependency_package_spec_no_all <- new (for cargo tree) * arg_clean_package_spec_simple <- new (for cargo clean) * arg_package_spec_simple * arg_package ``` In the second commit I tried to instead pass an `ArgValueCandidates` into the function to prevent that duplication. ``` * arg_package_spec * arg_package_spec_no_all (ArgValueCandidates) * arg_package_spec_simple (ArgValueCandidates) * arg_package ``` I think I have a small preference towards the latter, but no strong opinion.
2 parents 88411f4 + 0819035 commit 88c0fba

File tree

6 files changed

+126
-12
lines changed

6 files changed

+126
-12
lines changed

src/bin/cargo/commands/clean.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,18 @@ use cargo::core::global_cache_tracker::GlobalCacheTracker;
66
use cargo::ops::CleanContext;
77
use cargo::ops::{self, CleanOptions};
88
use cargo::util::print_available_packages;
9+
use clap_complete::ArgValueCandidates;
910
use std::time::Duration;
1011

1112
pub fn cli() -> Command {
1213
subcommand("clean")
1314
.about("Remove artifacts that cargo has generated in the past")
1415
.arg_doc("Whether or not to clean just the documentation directory")
1516
.arg_silent_suggestion()
16-
.arg_package_spec_simple("Package to clean artifacts for")
17+
.arg_package_spec_simple(
18+
"Package to clean artifacts for",
19+
ArgValueCandidates::new(get_pkg_name_candidates),
20+
)
1721
.arg_release("Whether or not to clean release artifacts")
1822
.arg_profile("Clean artifacts of the specified profile")
1923
.arg_target_triple("Target triple to clean output for")

src/bin/cargo/commands/package.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::command_prelude::*;
33
use cargo::ops;
44
use cargo::ops::PackageMessageFormat;
55
use cargo::ops::PackageOpts;
6+
use clap_complete::ArgValueCandidates;
67

78
pub fn cli() -> Command {
89
subcommand("package")
@@ -44,6 +45,7 @@ pub fn cli() -> Command {
4445
"Package(s) to assemble",
4546
"Assemble all packages in the workspace",
4647
"Don't assemble specified packages",
48+
ArgValueCandidates::new(get_ws_member_candidates),
4749
)
4850
.arg_features()
4951
.arg_target_triple("Build for the target triple")

src/bin/cargo/commands/publish.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use crate::command_prelude::*;
22

33
use cargo::ops::{self, PublishOpts};
44
use cargo_credential::Secret;
5+
use clap_complete::ArgValueCandidates;
56

67
pub fn cli() -> Command {
78
subcommand("publish")
@@ -27,6 +28,7 @@ pub fn cli() -> Command {
2728
"Package(s) to publish",
2829
"Publish all packages in the workspace",
2930
"Don't publish specified packages",
31+
ArgValueCandidates::new(get_ws_member_candidates),
3032
)
3133
.arg_features()
3234
.arg_parallel()

src/bin/cargo/commands/tree.rs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use cargo::ops::Packages;
77
use cargo::ops::tree::{self, DisplayDepth, EdgeKind};
88
use cargo::util::CargoResult;
99
use cargo::util::print_available_packages;
10+
use clap_complete::ArgValueCandidates;
1011
use std::collections::HashSet;
1112
use std::str::FromStr;
1213

@@ -36,7 +37,10 @@ pub fn cli() -> Command {
3637
"SPEC",
3738
"Invert the tree direction and focus on the given package",
3839
)
39-
.short('i'),
40+
.short('i')
41+
.add(clap_complete::ArgValueCandidates::new(
42+
get_pkg_id_spec_candidates,
43+
)),
4044
)
4145
.arg(multi_opt(
4246
"prune",
@@ -88,6 +92,7 @@ pub fn cli() -> Command {
8892
"Package to be used as the root of the tree",
8993
"Display the tree for all packages in the workspace",
9094
"Exclude specific workspace members",
95+
ArgValueCandidates::new(get_pkg_id_spec_candidates),
9196
)
9297
.arg_features()
9398
.arg(flag("all-targets", "Deprecated, use --target=all instead").hide(true))

src/bin/cargo/commands/uninstall.rs

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
use crate::command_prelude::*;
22

3-
use cargo::ops;
3+
use cargo::{CargoResult, core::PackageId, ops};
4+
use clap_complete::ArgValueCandidates;
5+
6+
use std::collections::BTreeSet;
47

58
pub fn cli() -> Command {
69
subcommand("uninstall")
@@ -15,7 +18,10 @@ pub fn cli() -> Command {
1518
)
1619
.arg(opt("root", "Directory to uninstall packages from").value_name("DIR"))
1720
.arg_silent_suggestion()
18-
.arg_package_spec_simple("Package to uninstall")
21+
.arg_package_spec_simple(
22+
"Package to uninstall",
23+
ArgValueCandidates::new(get_installed_package_candidates),
24+
)
1925
.arg(
2026
multi_opt("bin", "NAME", "Only uninstall the binary NAME")
2127
.help_heading(heading::TARGET_SELECTION),
@@ -52,7 +58,7 @@ fn get_installed_crates() -> Vec<clap_complete::CompletionCandidate> {
5258
fn get_installed_crates_() -> Option<Vec<clap_complete::CompletionCandidate>> {
5359
let mut candidates = Vec::new();
5460

55-
let gctx = GlobalContext::default().ok()?;
61+
let gctx = new_gctx_for_completions().ok()?;
5662

5763
let root = ops::resolve_root(None, &gctx).ok()?;
5864

@@ -66,3 +72,33 @@ fn get_installed_crates_() -> Option<Vec<clap_complete::CompletionCandidate>> {
6672

6773
Some(candidates)
6874
}
75+
76+
fn get_installed_package_candidates() -> Vec<clap_complete::CompletionCandidate> {
77+
get_installed_packages()
78+
.unwrap_or_default()
79+
.into_iter()
80+
.map(|(pkg, bins)| {
81+
let single_binary = bins.iter().next().take_if(|_| bins.len() == 1);
82+
83+
let help = if single_binary.is_some_and(|bin| bin == pkg.name().as_str()) {
84+
None
85+
} else {
86+
let binaries = bins.into_iter().collect::<Vec<_>>().as_slice().join(", ");
87+
Some(binaries)
88+
};
89+
90+
clap_complete::CompletionCandidate::new(pkg.name().as_str()).help(help.map(From::from))
91+
})
92+
.collect()
93+
}
94+
95+
fn get_installed_packages() -> CargoResult<Vec<(PackageId, BTreeSet<String>)>> {
96+
let gctx = new_gctx_for_completions()?;
97+
let root = ops::resolve_root(None, &gctx)?;
98+
99+
let tracker = ops::InstallTracker::load(&gctx, &root)?;
100+
Ok(tracker
101+
.all_installed_bins()
102+
.map(|(package_id, bins)| (*package_id, bins.clone()))
103+
.collect())
104+
}

src/cargo/util/command_prelude.rs

Lines changed: 72 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,11 +23,12 @@ use cargo_util_schemas::manifest::ProfileName;
2323
use cargo_util_schemas::manifest::RegistryName;
2424
use cargo_util_schemas::manifest::StringOrVec;
2525
use clap::builder::UnknownArgumentValueParser;
26+
use clap_complete::ArgValueCandidates;
2627
use home::cargo_home_with_cwd;
2728
use indexmap::IndexSet;
2829
use itertools::Itertools;
2930
use semver::Version;
30-
use std::collections::{HashMap, HashSet};
31+
use std::collections::{BTreeMap, HashMap, HashSet};
3132
use std::ffi::{OsStr, OsString};
3233
use std::path::Path;
3334
use std::path::PathBuf;
@@ -60,7 +61,13 @@ pub trait CommandExt: Sized {
6061
all: &'static str,
6162
exclude: &'static str,
6263
) -> Self {
63-
self.arg_package_spec_no_all(package, all, exclude)._arg(
64+
self.arg_package_spec_no_all(
65+
package,
66+
all,
67+
exclude,
68+
ArgValueCandidates::new(get_ws_member_candidates),
69+
)
70+
._arg(
6471
flag("all", "Alias for --workspace (deprecated)")
6572
.help_heading(heading::PACKAGE_SELECTION),
6673
)
@@ -74,6 +81,7 @@ pub trait CommandExt: Sized {
7481
package: &'static str,
7582
all: &'static str,
7683
exclude: &'static str,
84+
package_completion: ArgValueCandidates,
7785
) -> Self {
7886
let unsupported_short_arg = {
7987
let value_parser = UnknownArgumentValueParser::suggest_arg("--exclude");
@@ -84,17 +92,28 @@ pub trait CommandExt: Sized {
8492
.action(ArgAction::SetTrue)
8593
.hide(true)
8694
};
87-
self.arg_package_spec_simple(package)
95+
self.arg_package_spec_simple(package, package_completion)
8896
._arg(flag("workspace", all).help_heading(heading::PACKAGE_SELECTION))
89-
._arg(multi_opt("exclude", "SPEC", exclude).help_heading(heading::PACKAGE_SELECTION))
97+
._arg(
98+
multi_opt("exclude", "SPEC", exclude)
99+
.help_heading(heading::PACKAGE_SELECTION)
100+
.add(clap_complete::ArgValueCandidates::new(
101+
get_ws_member_candidates,
102+
)),
103+
)
90104
._arg(unsupported_short_arg)
91105
}
92106

93-
fn arg_package_spec_simple(self, package: &'static str) -> Self {
107+
fn arg_package_spec_simple(
108+
self,
109+
package: &'static str,
110+
package_completion: ArgValueCandidates,
111+
) -> Self {
94112
self._arg(
95113
optional_multi_opt("package", "SPEC", package)
96114
.short('p')
97-
.help_heading(heading::PACKAGE_SELECTION),
115+
.help_heading(heading::PACKAGE_SELECTION)
116+
.add(package_completion),
98117
)
99118
}
100119

@@ -103,7 +122,10 @@ pub trait CommandExt: Sized {
103122
optional_opt("package", package)
104123
.short('p')
105124
.value_name("SPEC")
106-
.help_heading(heading::PACKAGE_SELECTION),
125+
.help_heading(heading::PACKAGE_SELECTION)
126+
.add(clap_complete::ArgValueCandidates::new(|| {
127+
get_ws_member_candidates()
128+
})),
107129
)
108130
}
109131

@@ -1313,6 +1335,29 @@ fn get_target_triples_from_rustc() -> CargoResult<Vec<clap_complete::CompletionC
13131335
.collect())
13141336
}
13151337

1338+
pub fn get_ws_member_candidates() -> Vec<clap_complete::CompletionCandidate> {
1339+
get_ws_member_packages()
1340+
.unwrap_or_default()
1341+
.into_iter()
1342+
.map(|pkg| {
1343+
clap_complete::CompletionCandidate::new(pkg.name().as_str()).help(
1344+
pkg.manifest()
1345+
.metadata()
1346+
.description
1347+
.to_owned()
1348+
.map(From::from),
1349+
)
1350+
})
1351+
.collect::<Vec<_>>()
1352+
}
1353+
1354+
fn get_ws_member_packages() -> CargoResult<Vec<Package>> {
1355+
let gctx = new_gctx_for_completions()?;
1356+
let ws = Workspace::new(&find_root_manifest_for_wd(gctx.cwd())?, &gctx)?;
1357+
let packages = ws.members().map(Clone::clone).collect::<Vec<_>>();
1358+
Ok(packages)
1359+
}
1360+
13161361
pub fn get_pkg_id_spec_candidates() -> Vec<clap_complete::CompletionCandidate> {
13171362
let mut candidates = vec![];
13181363

@@ -1400,6 +1445,26 @@ pub fn get_pkg_id_spec_candidates() -> Vec<clap_complete::CompletionCandidate> {
14001445
candidates
14011446
}
14021447

1448+
pub fn get_pkg_name_candidates() -> Vec<clap_complete::CompletionCandidate> {
1449+
let packages: BTreeMap<_, _> = get_packages()
1450+
.unwrap_or_default()
1451+
.into_iter()
1452+
.map(|package| {
1453+
(
1454+
package.name(),
1455+
package.manifest().metadata().description.clone(),
1456+
)
1457+
})
1458+
.collect();
1459+
1460+
packages
1461+
.into_iter()
1462+
.map(|(name, description)| {
1463+
clap_complete::CompletionCandidate::new(name.as_str()).help(description.map(From::from))
1464+
})
1465+
.collect()
1466+
}
1467+
14031468
fn get_packages() -> CargoResult<Vec<Package>> {
14041469
let gctx = new_gctx_for_completions()?;
14051470

0 commit comments

Comments
 (0)