1use std::sync::atomic::AtomicBool;
2use std::sync::Arc;
3
4use anyhow::{anyhow, Result};
5use collections::HashMap;
6use fuzzy::StringMatchCandidate;
7use gpui::{AppContext, BackgroundExecutor, Global, ReadGlobal, Task, UpdateGlobal};
8use parking_lot::RwLock;
9
10use crate::crawler::{RustdocCrawler, RustdocProvider};
11use crate::RustdocItem;
12
13struct GlobalRustdocStore(Arc<RustdocStore>);
14
15impl Global for GlobalRustdocStore {}
16
17pub struct RustdocStore {
18 executor: BackgroundExecutor,
19 docs: Arc<RwLock<HashMap<(String, RustdocItem), String>>>,
20}
21
22impl RustdocStore {
23 pub fn global(cx: &AppContext) -> Arc<Self> {
24 GlobalRustdocStore::global(cx).0.clone()
25 }
26
27 pub fn init_global(cx: &mut AppContext) {
28 GlobalRustdocStore::set_global(
29 cx,
30 GlobalRustdocStore(Arc::new(Self::new(cx.background_executor().clone()))),
31 );
32 }
33
34 pub fn new(executor: BackgroundExecutor) -> Self {
35 Self {
36 executor,
37 docs: Arc::new(RwLock::new(HashMap::default())),
38 }
39 }
40
41 pub fn load(&self, crate_name: String, item_path: Option<String>) -> Task<Result<String>> {
42 let item_docs = self
43 .docs
44 .read()
45 .iter()
46 .find_map(|((item_crate_name, item), item_docs)| {
47 if item_crate_name == &crate_name && item_path == Some(item.display()) {
48 Some(item_docs.clone())
49 } else {
50 None
51 }
52 });
53
54 Task::ready(item_docs.ok_or_else(|| anyhow!("no docs found")))
55 }
56
57 pub fn index(
58 &self,
59 crate_name: String,
60 provider: Box<dyn RustdocProvider + Send + Sync + 'static>,
61 ) -> Task<Result<()>> {
62 let docs = self.docs.clone();
63 self.executor.spawn(async move {
64 let crawler = RustdocCrawler::new(provider);
65
66 println!("Indexing {crate_name}");
67
68 let Some(crate_docs) = crawler.crawl(crate_name.clone()).await? else {
69 return Ok(());
70 };
71
72 let mut lock = docs.write();
73
74 for (item, item_docs) in crate_docs.items {
75 lock.insert((crate_name.clone(), item), item_docs);
76 }
77
78 Ok(())
79 })
80 }
81
82 pub fn search(&self, query: String) -> Task<Vec<(String, RustdocItem)>> {
83 let executor = self.executor.clone();
84 let docs = self.docs.read().clone();
85 self.executor.spawn(async move {
86 if query.is_empty() {
87 return Vec::new();
88 }
89
90 let items = docs.keys().collect::<Vec<_>>();
91
92 let candidates = items
93 .iter()
94 .enumerate()
95 .map(|(ix, (crate_name, item))| {
96 StringMatchCandidate::new(ix, format!("{crate_name}::{}", item.display()))
97 })
98 .collect::<Vec<_>>();
99
100 let matches = fuzzy::match_strings(
101 &candidates,
102 &query,
103 false,
104 100,
105 &AtomicBool::default(),
106 executor,
107 )
108 .await;
109
110 matches
111 .into_iter()
112 .map(|mat| items[mat.candidate_id].clone())
113 .collect()
114 })
115 }
116}