From eda7e0064593aae789a8045515a197541f5655aa Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 7 Sep 2023 19:39:30 +0200 Subject: [PATCH] Implement `SemanticIndex::status` and use it in project search Co-Authored-By: Kyle Caverly --- crates/search/src/project_search.rs | 76 ++++++++++----------- crates/semantic_index/src/semantic_index.rs | 67 ++++++++++++++++-- 2 files changed, 96 insertions(+), 47 deletions(-) diff --git a/crates/search/src/project_search.rs b/crates/search/src/project_search.rs index 593308ce12..c52be64141 100644 --- a/crates/search/src/project_search.rs +++ b/crates/search/src/project_search.rs @@ -20,12 +20,11 @@ use gpui::{ Task, View, ViewContext, ViewHandle, WeakModelHandle, WeakViewHandle, }; use menu::Confirm; -use postage::stream::Stream; use project::{ search::{PathMatcher, SearchInputs, SearchQuery}, Entry, Project, }; -use semantic_index::SemanticIndex; +use semantic_index::{SemanticIndex, SemanticIndexStatus}; use smallvec::SmallVec; use std::{ any::{Any, TypeId}, @@ -116,7 +115,7 @@ pub struct ProjectSearchView { model: ModelHandle, query_editor: ViewHandle, results_editor: ViewHandle, - semantic_state: Option, + semantic_state: Option, semantic_permissioned: Option, search_options: SearchOptions, panels_with_errors: HashSet, @@ -129,9 +128,9 @@ pub struct ProjectSearchView { current_mode: SearchMode, } -struct SemanticSearchState { - pending_file_count: usize, - _progress_task: Task<()>, +struct SemanticState { + index_status: SemanticIndexStatus, + _subscription: Subscription, } pub struct ProjectSearchBar { @@ -316,11 +315,18 @@ impl View for ProjectSearchView { } }; - let semantic_status = self.semantic_state.as_ref().map(|semantic| { - if semantic.pending_file_count > 0 { - format!("Remaining files to index: {}", semantic.pending_file_count) - } else { - "Indexing complete".to_string() + let semantic_status = self.semantic_state.as_ref().and_then(|semantic| { + let status = semantic.index_status; + match status { + SemanticIndexStatus::Indexed => Some("Indexing complete".to_string()), + SemanticIndexStatus::Indexing { remaining_files } => { + if remaining_files == 0 { + Some(format!("Indexing...")) + } else { + Some(format!("Remaining files to index: {}", remaining_files)) + } + } + SemanticIndexStatus::NotIndexed => None, } }); @@ -637,41 +643,29 @@ impl ProjectSearchView { let project = self.model.read(cx).project.clone(); - let mut pending_file_count_rx = semantic_index.update(cx, |semantic_index, cx| { + semantic_index.update(cx, |semantic_index, cx| { semantic_index .index_project(project.clone(), cx) .detach_and_log_err(cx); - semantic_index.pending_file_count(&project).unwrap() }); - cx.spawn(|search_view, mut cx| async move { - search_view.update(&mut cx, |search_view, cx| { - cx.notify(); - let pending_file_count = *pending_file_count_rx.borrow(); - search_view.semantic_state = Some(SemanticSearchState { - pending_file_count, - _progress_task: cx.spawn(|search_view, mut cx| async move { - while let Some(count) = pending_file_count_rx.recv().await { - search_view - .update(&mut cx, |search_view, cx| { - if let Some(semantic_search_state) = - &mut search_view.semantic_state - { - semantic_search_state.pending_file_count = count; - cx.notify(); - if count == 0 { - return; - } - } - }) - .ok(); - } - }), - }); - })?; - anyhow::Ok(()) - }) - .detach_and_log_err(cx); + self.semantic_state = Some(SemanticState { + index_status: semantic_index.read(cx).status(&project), + _subscription: cx.observe(&semantic_index, Self::semantic_index_changed), + }); + cx.notify(); + } + } + + fn semantic_index_changed( + &mut self, + semantic_index: ModelHandle, + cx: &mut ViewContext, + ) { + let project = self.model.read(cx).project.clone(); + if let Some(semantic_state) = self.semantic_state.as_mut() { + semantic_state.index_status = semantic_index.read(cx).status(&project); + cx.notify(); } } diff --git a/crates/semantic_index/src/semantic_index.rs b/crates/semantic_index/src/semantic_index.rs index ce8af3ba80..2ef409eb92 100644 --- a/crates/semantic_index/src/semantic_index.rs +++ b/crates/semantic_index/src/semantic_index.rs @@ -109,6 +109,13 @@ pub fn init( .detach(); } +#[derive(Copy, Clone, Debug)] +pub enum SemanticIndexStatus { + NotIndexed, + Indexed, + Indexing { remaining_files: usize }, +} + pub struct SemanticIndex { fs: Arc, db: VectorDatabase, @@ -124,7 +131,9 @@ struct ProjectState { worktrees: HashMap, pending_file_count_rx: watch::Receiver, pending_file_count_tx: Arc>>, + pending_index: usize, _subscription: gpui::Subscription, + _observe_pending_file_count: Task<()>, } enum WorktreeState { @@ -133,6 +142,10 @@ enum WorktreeState { } impl WorktreeState { + fn is_registered(&self) -> bool { + matches!(self, Self::Registered(_)) + } + fn paths_changed( &mut self, changes: Arc<[(Arc, ProjectEntryId, PathChange)]>, @@ -207,14 +220,25 @@ impl JobHandle { } impl ProjectState { - fn new(subscription: gpui::Subscription) -> Self { + fn new(subscription: gpui::Subscription, cx: &mut ModelContext) -> Self { let (pending_file_count_tx, pending_file_count_rx) = watch::channel_with(0); let pending_file_count_tx = Arc::new(Mutex::new(pending_file_count_tx)); Self { worktrees: Default::default(), - pending_file_count_rx, + pending_file_count_rx: pending_file_count_rx.clone(), pending_file_count_tx, + pending_index: 0, _subscription: subscription, + _observe_pending_file_count: cx.spawn_weak({ + let mut pending_file_count_rx = pending_file_count_rx.clone(); + |this, mut cx| async move { + while let Some(_) = pending_file_count_rx.next().await { + if let Some(this) = this.upgrade(&cx) { + this.update(&mut cx, |_, cx| cx.notify()); + } + } + } + }), } } @@ -257,6 +281,25 @@ impl SemanticIndex { && *RELEASE_CHANNEL != ReleaseChannel::Stable } + pub fn status(&self, project: &ModelHandle) -> SemanticIndexStatus { + if let Some(project_state) = self.projects.get(&project.downgrade()) { + if project_state + .worktrees + .values() + .all(|worktree| worktree.is_registered()) + && project_state.pending_index == 0 + { + SemanticIndexStatus::Indexed + } else { + SemanticIndexStatus::Indexing { + remaining_files: project_state.pending_file_count_rx.borrow().clone(), + } + } + } else { + SemanticIndexStatus::NotIndexed + } + } + async fn new( fs: Arc, database_path: PathBuf, @@ -800,13 +843,15 @@ impl SemanticIndex { } _ => {} }); - self.projects - .insert(project.downgrade(), ProjectState::new(subscription)); + let project_state = ProjectState::new(subscription, cx); + self.projects.insert(project.downgrade(), project_state); self.project_worktrees_changed(project.clone(), cx); } - let project_state = &self.projects[&project.downgrade()]; - let mut pending_file_count_rx = project_state.pending_file_count_rx.clone(); + let project_state = self.projects.get_mut(&project.downgrade()).unwrap(); + project_state.pending_index += 1; + cx.notify(); + let mut pending_file_count_rx = project_state.pending_file_count_rx.clone(); let db = self.db.clone(); let language_registry = self.language_registry.clone(); let parsing_files_tx = self.parsing_files_tx.clone(); @@ -917,6 +962,16 @@ impl SemanticIndex { }) .await; + this.update(&mut cx, |this, cx| { + let project_state = this + .projects + .get_mut(&project.downgrade()) + .ok_or_else(|| anyhow!("project was dropped"))?; + project_state.pending_index -= 1; + cx.notify(); + anyhow::Ok(()) + })?; + Ok(()) }) }