store.rs

  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}