From cee6fd8dd3caed8dd4f695bc9d86a8b736de4524 Mon Sep 17 00:00:00 2001 From: Kirill Bulatov Date: Thu, 23 Nov 2023 10:44:05 +0200 Subject: [PATCH] Port to gpui2 --- crates/project2/src/project2.rs | 118 ++++++++++++++++++++++++++- crates/project2/src/worktree.rs | 2 +- crates/search2/src/project_search.rs | 21 ++++- 3 files changed, 135 insertions(+), 6 deletions(-) diff --git a/crates/project2/src/project2.rs b/crates/project2/src/project2.rs index 3f7c9b7188..2057b20818 100644 --- a/crates/project2/src/project2.rs +++ b/crates/project2/src/project2.rs @@ -13,7 +13,7 @@ mod worktree_tests; use anyhow::{anyhow, Context as _, Result}; use client::{proto, Client, Collaborator, TypedEnvelope, UserStore}; use clock::ReplicaId; -use collections::{hash_map, BTreeMap, HashMap, HashSet}; +use collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque}; use copilot::Copilot; use futures::{ channel::{ @@ -63,6 +63,7 @@ use settings::{Settings, SettingsStore}; use sha2::{Digest, Sha256}; use similar::{ChangeTag, TextDiff}; use smol::channel::{Receiver, Sender}; +use smol::lock::Semaphore; use std::{ cmp::{self, Ordering}, convert::TryInto, @@ -557,6 +558,7 @@ enum SearchMatchCandidate { }, Path { worktree_id: WorktreeId, + is_ignored: bool, path: Arc, }, } @@ -5815,11 +5817,15 @@ impl Project { } executor .scoped(|scope| { + let max_concurrent_workers = Arc::new(Semaphore::new(workers)); + for worker_ix in 0..workers { let worker_start_ix = worker_ix * paths_per_worker; let worker_end_ix = worker_start_ix + paths_per_worker; let unnamed_buffers = opened_buffers.clone(); + let limiter = Arc::clone(&max_concurrent_workers); scope.spawn(async move { + let _guard = limiter.acquire().await; let mut snapshot_start_ix = 0; let mut abs_path = PathBuf::new(); for snapshot in snapshots { @@ -5868,6 +5874,7 @@ impl Project { let project_path = SearchMatchCandidate::Path { worktree_id: snapshot.id(), path: entry.path.clone(), + is_ignored: entry.is_ignored, }; if matching_paths_tx.send(project_path).await.is_err() { break; @@ -5880,6 +5887,94 @@ impl Project { } }); } + + if query.include_ignored() { + for snapshot in snapshots { + for ignored_entry in snapshot + .entries(query.include_ignored()) + .filter(|e| e.is_ignored) + { + let limiter = Arc::clone(&max_concurrent_workers); + scope.spawn(async move { + let _guard = limiter.acquire().await; + let mut ignored_paths_to_process = + VecDeque::from([snapshot.abs_path().join(&ignored_entry.path)]); + while let Some(ignored_abs_path) = + ignored_paths_to_process.pop_front() + { + if !query.file_matches(Some(&ignored_abs_path)) + || snapshot.is_abs_path_excluded(&ignored_abs_path) + { + continue; + } + if let Some(fs_metadata) = fs + .metadata(&ignored_abs_path) + .await + .with_context(|| { + format!("fetching fs metadata for {ignored_abs_path:?}") + }) + .log_err() + .flatten() + { + if fs_metadata.is_dir { + if let Some(mut subfiles) = fs + .read_dir(&ignored_abs_path) + .await + .with_context(|| { + format!( + "listing ignored path {ignored_abs_path:?}" + ) + }) + .log_err() + { + while let Some(subfile) = subfiles.next().await { + if let Some(subfile) = subfile.log_err() { + ignored_paths_to_process.push_back(subfile); + } + } + } + } else if !fs_metadata.is_symlink { + let matches = if let Some(file) = fs + .open_sync(&ignored_abs_path) + .await + .with_context(|| { + format!( + "Opening ignored path {ignored_abs_path:?}" + ) + }) + .log_err() + { + query.detect(file).unwrap_or(false) + } else { + false + }; + if matches { + let project_path = SearchMatchCandidate::Path { + worktree_id: snapshot.id(), + path: Arc::from( + ignored_abs_path + .strip_prefix(snapshot.abs_path()) + .expect( + "scanning worktree-related files", + ), + ), + is_ignored: true, + }; + if matching_paths_tx + .send(project_path) + .await + .is_err() + { + return; + } + } + } + } + } + }); + } + } + } }) .await; } @@ -5986,11 +6081,24 @@ impl Project { let (buffers_tx, buffers_rx) = smol::channel::bounded(1024); let (sorted_buffers_tx, sorted_buffers_rx) = futures::channel::oneshot::channel(); cx.spawn(move |this, cx| async move { - let mut buffers = vec![]; + let mut buffers = Vec::new(); + let mut ignored_buffers = Vec::new(); while let Some(entry) = matching_paths_rx.next().await { - buffers.push(entry); + if matches!( + entry, + SearchMatchCandidate::Path { + is_ignored: true, + .. + } + ) { + ignored_buffers.push(entry); + } else { + buffers.push(entry); + } } buffers.sort_by_key(|candidate| candidate.path()); + ignored_buffers.sort_by_key(|candidate| candidate.path()); + buffers.extend(ignored_buffers); let matching_paths = buffers.clone(); let _ = sorted_buffers_tx.send(buffers); for (index, candidate) in matching_paths.into_iter().enumerate() { @@ -6002,7 +6110,9 @@ impl Project { cx.spawn(move |mut cx| async move { let buffer = match candidate { SearchMatchCandidate::OpenBuffer { buffer, .. } => Some(buffer), - SearchMatchCandidate::Path { worktree_id, path } => this + SearchMatchCandidate::Path { + worktree_id, path, .. + } => this .update(&mut cx, |this, cx| { this.open_buffer((worktree_id, path), cx) })? diff --git a/crates/project2/src/worktree.rs b/crates/project2/src/worktree.rs index fcb64c40b4..23aaa2a623 100644 --- a/crates/project2/src/worktree.rs +++ b/crates/project2/src/worktree.rs @@ -2222,7 +2222,7 @@ impl LocalSnapshot { paths } - fn is_abs_path_excluded(&self, abs_path: &Path) -> bool { + pub fn is_abs_path_excluded(&self, abs_path: &Path) -> bool { self.file_scan_exclusions .iter() .any(|exclude_matcher| exclude_matcher.is_match(abs_path)) diff --git a/crates/search2/src/project_search.rs b/crates/search2/src/project_search.rs index f6e17bbee5..41dd87d4d3 100644 --- a/crates/search2/src/project_search.rs +++ b/crates/search2/src/project_search.rs @@ -85,6 +85,7 @@ pub fn init(cx: &mut AppContext) { cx.capture_action(ProjectSearchView::replace_next); add_toggle_option_action::(SearchOptions::CASE_SENSITIVE, cx); add_toggle_option_action::(SearchOptions::WHOLE_WORD, cx); + add_toggle_option_action::(SearchOptions::INCLUDE_IGNORED, cx); add_toggle_filters_action::(cx); } @@ -1192,6 +1193,7 @@ impl ProjectSearchView { text, self.search_options.contains(SearchOptions::WHOLE_WORD), self.search_options.contains(SearchOptions::CASE_SENSITIVE), + self.search_options.contains(SearchOptions::INCLUDE_IGNORED), included_files, excluded_files, ) { @@ -1210,6 +1212,7 @@ impl ProjectSearchView { text, self.search_options.contains(SearchOptions::WHOLE_WORD), self.search_options.contains(SearchOptions::CASE_SENSITIVE), + self.search_options.contains(SearchOptions::INCLUDE_IGNORED), included_files, excluded_files, ) { @@ -1764,6 +1767,14 @@ impl View for ProjectSearchBar { render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx) }); + let include_ignored = is_semantic_disabled.then(|| { + render_option_button_icon( + "icons/file_icons/git.svg", + SearchOptions::INCLUDE_IGNORED, + cx, + ) + }); + let search_button_for_mode = |mode, side, cx: &mut ViewContext| { let is_active = if let Some(search) = self.active_project_search.as_ref() { let search = search.read(cx); @@ -1879,7 +1890,15 @@ impl View for ProjectSearchBar { .with_children(search.filters_enabled.then(|| { Flex::row() .with_child( - ChildView::new(&search.included_files_editor, cx) + Flex::row() + .with_child( + ChildView::new(&search.included_files_editor, cx) + .contained() + .constrained() + .with_height(theme.search.search_bar_row_height) + .flex(1., true), + ) + .with_children(include_ignored) .contained() .with_style(include_container_style) .constrained()