cargo/ops/registry/
cargo_search.rs1use crate::sources::CRATES_IO_DOMAIN;
6use std::cmp;
7
8use anyhow::Context as _;
9use url::Url;
10
11use crate::CargoResult;
12use crate::GlobalContext;
13use crate::util::style;
14use crate::util::style::LITERAL;
15use crate::util::truncate_with_ellipsis;
16
17use super::RegistryOrIndex;
18
19pub fn search(
20 query: &str,
21 gctx: &GlobalContext,
22 reg_or_index: Option<RegistryOrIndex>,
23 limit: u32,
24) -> CargoResult<()> {
25 let source_ids = super::get_source_id(gctx, reg_or_index.as_ref())?;
26 let (mut registry, _) =
27 super::registry(gctx, &source_ids, None, reg_or_index.as_ref(), false, None)?;
28 let (crates, total_crates) = registry.search(query, limit).with_context(|| {
29 format!(
30 "failed to retrieve search results from the registry at {}",
31 registry.host()
32 )
33 })?;
34
35 let names = crates
36 .iter()
37 .map(|krate| format!("{} = \"{}\"", krate.name, krate.max_version))
38 .collect::<Vec<String>>();
39
40 let description_margin = names.iter().map(|s| s.len()).max().unwrap_or_default() + 4;
41
42 let description_length = cmp::max(80, 128 - description_margin);
43
44 let descriptions = crates.iter().map(|krate| {
45 krate
46 .description
47 .as_ref()
48 .map(|desc| truncate_with_ellipsis(&desc.replace("\n", " "), description_length))
49 });
50
51 let mut shell = gctx.shell();
52
53 let links = crates
54 .iter()
55 .map(|krate| {
56 registry
57 .host_is_crates_io()
58 .then(|| {
59 shell.out_hyperlink(format!(
60 "https://{CRATES_IO_DOMAIN}/crates/{}/{}",
61 krate.name, krate.max_version
62 ))
63 })
64 .unwrap_or_default()
65 })
66 .collect::<Vec<_>>();
67
68 let stdout = shell.out();
69 let good = style::GOOD;
70
71 let write_with_highlighting = |stdout: &mut dyn std::io::Write, text: &str| {
72 let mut fragments = text.split(query).peekable();
73 while let Some(fragment) = fragments.next() {
74 let _ = write!(stdout, "{fragment}");
75 if fragments.peek().is_some() {
76 let _ = write!(stdout, "{good}{query}{good:#}");
77 }
78 }
79 };
80
81 for (name, (description, link)) in names.into_iter().zip(descriptions.zip(links)) {
82 let _ = write!(stdout, "{link}");
83 write_with_highlighting(stdout, &name);
84 let _ = write!(stdout, "{link:#}");
85
86 if let Some(description) = description {
87 let margin = " ".repeat(description_margin.saturating_sub(name.len()));
88 let _ = write!(stdout, "{margin}# ");
89 write_with_highlighting(stdout, &description);
90 }
91
92 let _ = writeln!(stdout);
93 }
94
95 let search_max_limit = 100;
96 if total_crates > limit && limit < search_max_limit {
97 let _ = writeln!(
98 stdout,
99 "... and {} crates more (use --limit N to see more)",
100 total_crates - limit
101 );
102 } else if total_crates > limit && limit >= search_max_limit {
103 let extra = if source_ids.original.is_crates_io() {
104 let url = Url::parse_with_params("https://crates.io/search", &[("q", query)])?;
105 format!(" (go to {url} to see more)")
106 } else {
107 String::new()
108 };
109 let _ = writeln!(
110 stdout,
111 "... and {} crates more{}",
112 total_crates - limit,
113 extra
114 );
115 }
116
117 if total_crates > 0 {
118 let literal = LITERAL;
119 shell.note(format_args!(
120 "to learn more about a package, run `{literal}cargo info <name>{literal:#}`",
121 ))?;
122 }
123
124 Ok(())
125}