assistant: Auto-suggest crates for `/docs` when using the docs.rs provider (#16169)

Marshall Bowers created

This PR improves the initial experience of using `/docs docs-rs` with an
empty index.

We now show a brief explainer of what is expected (a crate name) as well
as list some popular Rust crates to try:

<img width="540" alt="Screenshot 2024-08-13 at 12 25 39 PM"
src="https://github.com/user-attachments/assets/942de250-2901-45df-9e3e-52ff3b3fc517">

Release Notes:

- N/A

Change summary

crates/assistant/src/slash_command/docs_command.rs | 49 ++++++++++++++-
crates/indexed_docs/src/providers/rustdoc.rs       | 20 ++++++
2 files changed, 63 insertions(+), 6 deletions(-)

Detailed changes

crates/assistant/src/slash_command/docs_command.rs 🔗

@@ -230,6 +230,36 @@ impl SlashCommand for DocsSlashCommand {
                     }
 
                     let items = store.search(package).await;
+                    if items.is_empty() {
+                        if provider == DocsDotRsProvider::id() {
+                            return Ok(std::iter::once(ArgumentCompletion {
+                                label: format!(
+                                    "Enter a {package_term} name or try one of these:",
+                                    package_term = package_term(&provider)
+                                ),
+                                new_text: provider.to_string(),
+                                run_command: false,
+                            })
+                            .chain(DocsDotRsProvider::AUTO_SUGGESTED_CRATES.into_iter().map(
+                                |crate_name| ArgumentCompletion {
+                                    label: crate_name.to_string(),
+                                    new_text: format!("{provider} {crate_name}"),
+                                    run_command: true,
+                                },
+                            ))
+                            .collect());
+                        }
+
+                        return Ok(vec![ArgumentCompletion {
+                            label: format!(
+                                "Enter a {package_term} name.",
+                                package_term = package_term(&provider)
+                            ),
+                            new_text: provider.to_string(),
+                            run_command: false,
+                        }]);
+                    }
+
                     Ok(build_completions(provider, items))
                 }
                 DocsSlashCommandArgs::SearchItemDocs {
@@ -277,12 +307,10 @@ impl SlashCommand for DocsSlashCommand {
                 };
 
                 if key.trim().is_empty() {
-                    let package_term = match provider.as_ref() {
-                        "docs-rs" | "rustdoc" => "crate",
-                        _ => "package",
-                    };
-
-                    bail!("no {package_term} name provided");
+                    bail!(
+                        "no {package_term} name provided",
+                        package_term = package_term(&provider)
+                    );
                 }
 
                 let store = store?;
@@ -407,6 +435,15 @@ impl DocsSlashCommandArgs {
     }
 }
 
+/// Returns the term used to refer to a package.
+fn package_term(provider: &ProviderId) -> &'static str {
+    if provider == &DocsDotRsProvider::id() || provider == &LocalRustdocProvider::id() {
+        return "crate";
+    }
+
+    "package"
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;

crates/indexed_docs/src/providers/rustdoc.rs 🔗

@@ -98,6 +98,26 @@ pub struct DocsDotRsProvider {
 }
 
 impl DocsDotRsProvider {
+    /// The list of crates to auto-suggest for the docs.rs provider when
+    /// the index is empty.
+    ///
+    /// List has been chosen loosely based on [this list](https://lib.rs/std) of
+    /// popular Rust libraries.
+    ///
+    /// Keep this alphabetized.
+    pub const AUTO_SUGGESTED_CRATES: &'static [&'static str] = &[
+        "anyhow",
+        "axum",
+        "chrono",
+        "itertools",
+        "rand",
+        "regex",
+        "serde",
+        "strum",
+        "thiserror",
+        "tokio",
+    ];
+
     pub fn id() -> ProviderId {
         ProviderId("docs-rs".into())
     }