If enabled, search in ignored files during project search

Kirill Bulatov created

Change summary

.github/workflows/ci.yml            |  2 
crates/project/src/project.rs       | 83 ++++++++++++++++++++++++++++++
crates/project/src/worktree.rs      |  2 
crates/search/src/project_search.rs |  5 -
crates/util/src/channel.rs          |  1 
crates/zed/Cargo.toml               |  1 
script/bump-zed-minor-versions      |  1 
7 files changed, 85 insertions(+), 10 deletions(-)

Detailed changes

.github/workflows/ci.yml 🔗

@@ -134,8 +134,6 @@ jobs:
 
       - uses: softprops/action-gh-release@v1
         name: Upload app bundle to release
-        # TODO kb seems that zed.dev relies on GitHub releases for release version tracking.
-        # Find alternatives for `nightly` or just go on with more releases?
         if: ${{ env.RELEASE_CHANNEL == 'preview' || env.RELEASE_CHANNEL == 'stable' }}
         with:
           draft: true

crates/project/src/project.rs 🔗

@@ -13,7 +13,7 @@ mod worktree_tests;
 use anyhow::{anyhow, Context, 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::{
@@ -5742,8 +5742,89 @@ impl Project {
                 .await
                 .log_err();
         }
+
         background
             .scoped(|scope| {
+                if query.include_ignored() {
+                    scope.spawn(async move {
+                        for snapshot in snapshots {
+                            for ignored_entry in snapshot
+                                .entries(query.include_ignored())
+                                .filter(|e| e.is_ignored)
+                            {
+                                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_front(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: ignored_entry.path.clone(),
+                                                };
+                                                if matching_paths_tx
+                                                    .send(project_path)
+                                                    .await
+                                                    .is_err()
+                                                {
+                                                    return;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    });
+                }
+
                 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;

crates/project/src/worktree.rs 🔗

@@ -2226,7 +2226,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))

crates/search/src/project_search.rs 🔗

@@ -219,6 +219,7 @@ impl ProjectSearch {
                 this.no_results = Some(true);
             });
 
+            // TODO kb check for cancellations here and actually stop the search
             while let Some((buffer, anchors)) = matches.next().await {
                 let mut ranges = this.update(&mut cx, |this, cx| {
                     this.no_results = Some(false);
@@ -1767,7 +1768,7 @@ impl View for ProjectSearchBar {
                 render_option_button_icon("icons/word_search.svg", SearchOptions::WHOLE_WORD, cx)
             });
 
-            let mut include_ignored = is_semantic_disabled.then(|| {
+            let include_ignored = is_semantic_disabled.then(|| {
                 render_option_button_icon(
                     // TODO proper icon
                     "icons/case_insensitive.svg",
@@ -1775,8 +1776,6 @@ impl View for ProjectSearchBar {
                     cx,
                 )
             });
-            // TODO not implemented yet
-            let _ = include_ignored.take();
 
             let search_button_for_mode = |mode, side, cx: &mut ViewContext<ProjectSearchBar>| {
                 let is_active = if let Some(search) = self.active_project_search.as_ref() {

crates/util/src/channel.rs 🔗

@@ -59,7 +59,6 @@ impl ReleaseChannel {
     pub fn link_prefix(&self) -> &'static str {
         match self {
             ReleaseChannel::Dev => "https://zed.dev/dev/",
-            // TODO kb need to add server handling
             ReleaseChannel::Nightly => "https://zed.dev/nightly/",
             ReleaseChannel::Preview => "https://zed.dev/preview/",
             ReleaseChannel::Stable => "https://zed.dev/",

crates/zed/Cargo.toml 🔗

@@ -171,7 +171,6 @@ osx_info_plist_exts = ["resources/info/*"]
 osx_url_schemes = ["zed-dev"]
 
 [package.metadata.bundle-nightly]
-# TODO kb different icon?
 icon = ["resources/app-icon-preview@2x.png", "resources/app-icon-preview.png"]
 identifier = "dev.zed.Zed-Nightly"
 name = "Zed Nightly"

script/bump-zed-minor-versions 🔗

@@ -59,7 +59,6 @@ if ! git show-ref --quiet refs/heads/${prev_minor_branch_name}; then
   echo "previous branch ${minor_branch_name} doesn't exist"
   exit 1
 fi
-# TODO kb anything else for RELEASE_CHANNEL == nightly needs to be done below?
 if [[ $(git show ${prev_minor_branch_name}:crates/zed/RELEASE_CHANNEL) != preview ]]; then
   echo "release channel on branch ${prev_minor_branch_name} should be preview"
   exit 1