diff --git a/Cargo.lock b/Cargo.lock index 9f79f1863e76ebe23f2f03d21eb27f8978e171d8..e8298d384b55fad88abe6305ac941aea81a6b19e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5469,6 +5469,7 @@ version = "0.1.0" dependencies = [ "anyhow", "async-trait", + "cargo_metadata", "collections", "derive_more", "fs", diff --git a/crates/assistant/src/slash_command/docs_command.rs b/crates/assistant/src/slash_command/docs_command.rs index 3872f8f8d89528b476ba12ce664b9b6a4e1e1dc7..4e3bd646b3f4877a8ff805c5b71d84833dd784a4 100644 --- a/crates/assistant/src/slash_command/docs_command.rs +++ b/crates/assistant/src/slash_command/docs_command.rs @@ -72,6 +72,9 @@ impl DocsSlashCommand { }); if let Some((fs, cargo_workspace_root)) = index_provider_deps.log_err() { + // List the workspace crates once to prime the cache. + LocalRustdocProvider::list_workspace_crates().ok(); + indexed_docs_registry.register_provider(Box::new(LocalRustdocProvider::new( fs, cargo_workspace_root, @@ -230,6 +233,29 @@ impl SlashCommand for DocsSlashCommand { } let items = store.search(package).await; + + if provider == LocalRustdocProvider::id() { + let items = build_completions(provider.clone(), items); + let workspace_crates = LocalRustdocProvider::list_workspace_crates()?; + + let mut all_items = items; + let workspace_crate_completions = workspace_crates + .into_iter() + .filter(|crate_name| { + !all_items + .iter() + .any(|item| item.label.as_str() == crate_name.as_ref()) + }) + .map(|crate_name| ArgumentCompletion { + label: format!("{crate_name} (unindexed)"), + new_text: format!("{provider} {crate_name}"), + run_command: true, + }) + .collect::>(); + all_items.extend(workspace_crate_completions); + return Ok(all_items); + } + if items.is_empty() { if provider == DocsDotRsProvider::id() { return Ok(std::iter::once(ArgumentCompletion { diff --git a/crates/indexed_docs/Cargo.toml b/crates/indexed_docs/Cargo.toml index 2c71484e7fb3d8d2bdda1a8d7867357909a71fc5..145abd445e341cd95842a8e31a5f546cb03b6422 100644 --- a/crates/indexed_docs/Cargo.toml +++ b/crates/indexed_docs/Cargo.toml @@ -14,6 +14,7 @@ path = "src/indexed_docs.rs" [dependencies] anyhow.workspace = true async-trait.workspace = true +cargo_metadata.workspace = true collections.workspace = true derive_more.workspace = true fs.workspace = true diff --git a/crates/indexed_docs/src/providers/rustdoc.rs b/crates/indexed_docs/src/providers/rustdoc.rs index f2526f3a7343b3a42a88a3c8ad8ad7daee6eb227..6323d14a924b4ba99abb857ed3cf1e77fe96ce43 100644 --- a/crates/indexed_docs/src/providers/rustdoc.rs +++ b/crates/indexed_docs/src/providers/rustdoc.rs @@ -1,12 +1,16 @@ mod item; mod to_markdown; +use cargo_metadata::MetadataCommand; use futures::future::BoxFuture; pub use item::*; +use parking_lot::RwLock; pub use to_markdown::convert_rustdoc_to_markdown; +use std::collections::BTreeSet; use std::path::PathBuf; -use std::sync::Arc; +use std::sync::{Arc, LazyLock}; +use std::time::{Duration, Instant}; use anyhow::{bail, Context, Result}; use async_trait::async_trait; @@ -40,6 +44,34 @@ impl LocalRustdocProvider { cargo_workspace_root, } } + + /// Returns the list of all crates in the Cargo workspace. + /// + /// Includes the list of workspace crates as well as all dependency crates. + pub fn list_workspace_crates() -> Result>> { + static WORKSPACE_CRATES: LazyLock>, Instant)>>> = + LazyLock::new(|| RwLock::new(None)); + + if let Some((crates, fetched_at)) = &*WORKSPACE_CRATES.read() { + if fetched_at.elapsed() < Duration::from_secs(300) { + return Ok(crates.iter().cloned().collect()); + } + } + + let workspace = MetadataCommand::new() + .exec() + .context("failed to load cargo metadata")?; + + let workspace_crates = workspace + .packages + .into_iter() + .map(|package| package.name.into()) + .collect::>(); + + *WORKSPACE_CRATES.write() = Some((workspace_crates.clone(), Instant::now())); + + Ok(workspace_crates.iter().cloned().collect()) + } } #[async_trait]