Allow extensions to suggest packages for `/docs` completions (#16185)

Marshall Bowers created

This PR gives extensions the ability to suggest packages to show up in
`/docs` completions by default.

Release Notes:

- N/A

Change summary

crates/extension/src/extension_indexed_docs_provider.rs | 20 ++++++++++
crates/extension/src/wasm_host/wit.rs                   | 13 +++++++
crates/extension_api/src/extension_api.rs               | 14 +++++++
crates/extension_api/wit/since_v0.1.0/extension.wit     |  7 +++
extensions/gleam/src/gleam.rs                           | 11 ++++++
5 files changed, 64 insertions(+), 1 deletion(-)

Detailed changes

crates/extension/src/extension_indexed_docs_provider.rs 🔗

@@ -31,7 +31,25 @@ impl IndexedDocsProvider for ExtensionIndexedDocsProvider {
     }
 
     async fn suggest_packages(&self) -> Result<Vec<PackageName>> {
-        Ok(Vec::new())
+        self.extension
+            .call({
+                let id = self.id.clone();
+                |extension, store| {
+                    async move {
+                        let packages = extension
+                            .call_suggest_docs_packages(store, id.as_ref())
+                            .await?
+                            .map_err(|err| anyhow!("{err:?}"))?;
+
+                        Ok(packages
+                            .into_iter()
+                            .map(|package| PackageName::from(package.as_str()))
+                            .collect())
+                    }
+                    .boxed()
+                }
+            })
+            .await
     }
 
     async fn index(&self, package: PackageName, database: Arc<IndexedDocsDatabase>) -> Result<()> {

crates/extension/src/wasm_host/wit.rs 🔗

@@ -291,6 +291,19 @@ impl Extension {
         }
     }
 
+    pub async fn call_suggest_docs_packages(
+        &self,
+        store: &mut Store<WasmState>,
+        provider: &str,
+    ) -> Result<Result<Vec<String>, String>> {
+        match self {
+            Extension::V010(ext) => ext.call_suggest_docs_packages(store, provider).await,
+            Extension::V001(_) | Extension::V004(_) | Extension::V006(_) => Err(anyhow!(
+                "`suggest_docs_packages` not available prior to v0.1.0"
+            )),
+        }
+    }
+
     pub async fn call_index_docs(
         &self,
         store: &mut Store<WasmState>,

crates/extension_api/src/extension_api.rs 🔗

@@ -129,6 +129,16 @@ pub trait Extension: Send + Sync {
         Err("`run_slash_command` not implemented".to_string())
     }
 
+    /// Returns a list of package names as suggestions to be included in the
+    /// search results of the `/docs` slash command.
+    ///
+    /// This can be used to provide completions for known packages (e.g., from the
+    /// local project or a registry) before a package has been indexed.
+    fn suggest_docs_packages(&self, _provider: String) -> Result<Vec<String>, String> {
+        Ok(Vec::new())
+    }
+
+    /// Indexes the docs for the specified package.
     fn index_docs(
         &self,
         _provider: String,
@@ -260,6 +270,10 @@ impl wit::Guest for Component {
         extension().run_slash_command(command, argument, worktree)
     }
 
+    fn suggest_docs_packages(provider: String) -> Result<Vec<String>, String> {
+        extension().suggest_docs_packages(provider)
+    }
+
     fn index_docs(
         provider: String,
         package: String,

crates/extension_api/wit/since_v0.1.0/extension.wit 🔗

@@ -135,6 +135,13 @@ world extension {
     /// Returns the output from running the provided slash command.
     export run-slash-command: func(command: slash-command, argument: option<string>, worktree: option<borrow<worktree>>) -> result<slash-command-output, string>;
 
+    /// Returns a list of packages as suggestions to be included in the `/docs`
+    /// search results.
+    ///
+    /// This can be used to provide completions for known packages (e.g., from the
+    /// local project or a registry) before a package has been indexed.
+    export suggest-docs-packages: func(provider-name: string) -> result<list<string>, string>;
+
     /// Indexes the docs for the specified package.
     export index-docs: func(provider-name: string, package-name: string, database: borrow<key-value-store>) -> result<_, string>;
 }

extensions/gleam/src/gleam.rs 🔗

@@ -246,6 +246,17 @@ impl zed::Extension for GleamExtension {
         }
     }
 
+    fn suggest_docs_packages(&self, provider: String) -> Result<Vec<String>, String> {
+        match provider.as_str() {
+            "gleam-hexdocs" => Ok(vec![
+                "gleam_stdlib".to_string(),
+                "birdie".to_string(),
+                "startest".to_string(),
+            ]),
+            _ => Ok(Vec::new()),
+        }
+    }
+
     fn index_docs(
         &self,
         provider: String,