Cargo.lock π
@@ -5381,8 +5381,10 @@ dependencies = [
"language",
"menu",
"picker",
+ "pretty_assertions",
"project",
"schemars",
+ "search",
"serde",
"serde_derive",
"serde_json",
Kirill Bulatov created
Follow-up of https://github.com/zed-industries/zed/pull/31457
Add a button and also allows to use `search::ToggleIncludeIgnored`
action in the file finder to toggle whether to show gitignored files or
not.
By default, returns back to the gitignored treatment before the PR
above.

Release Notes:
- Improved file finder to include indexed gitignored files in its search
results
Cargo.lock | 2
assets/settings/default.json | 12 +
crates/file_finder/Cargo.toml | 2
crates/file_finder/src/file_finder.rs | 123 +++++++++++++++----
crates/file_finder/src/file_finder_settings.rs | 15 ++
crates/file_finder/src/file_finder_tests.rs | 106 ++++++++++++++++
6 files changed, 228 insertions(+), 32 deletions(-)
@@ -5381,8 +5381,10 @@ dependencies = [
"language",
"menu",
"picker",
+ "pretty_assertions",
"project",
"schemars",
+ "search",
"serde",
"serde_derive",
"serde_json",
@@ -959,7 +959,17 @@
// "skip_focus_for_active_in_search": false
//
// Default: true
- "skip_focus_for_active_in_search": true
+ "skip_focus_for_active_in_search": true,
+ // Whether to show the git status in the file finder.
+ "git_status": true,
+ // Whether to use gitignored files when searching.
+ // Only the file Zed had indexed will be used, not necessary all the gitignored files.
+ //
+ // Can accept 3 values:
+ // * `true`: Use all gitignored files
+ // * `false`: Use only the files Zed had indexed
+ // * `null`: Be smart and search for ignored when called from a gitignored worktree
+ "include_ignored": null
},
// Whether or not to remove any trailing whitespace from lines of a buffer
// before saving it.
@@ -24,6 +24,7 @@ menu.workspace = true
picker.workspace = true
project.workspace = true
schemars.workspace = true
+search.workspace = true
settings.workspace = true
serde.workspace = true
serde_derive.workspace = true
@@ -40,6 +41,7 @@ editor = { workspace = true, features = ["test-support"] }
gpui = { workspace = true, features = ["test-support"] }
language = { workspace = true, features = ["test-support"] }
picker = { workspace = true, features = ["test-support"] }
+pretty_assertions.workspace = true
serde_json.workspace = true
theme = { workspace = true, features = ["test-support"] }
workspace = { workspace = true, features = ["test-support"] }
@@ -24,6 +24,7 @@ use new_path_prompt::NewPathPrompt;
use open_path_prompt::OpenPathPrompt;
use picker::{Picker, PickerDelegate};
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
+use search::ToggleIncludeIgnored;
use settings::Settings;
use std::{
borrow::Cow,
@@ -37,8 +38,8 @@ use std::{
};
use text::Point;
use ui::{
- ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle,
- prelude::*,
+ ContextMenu, HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, PopoverMenu,
+ PopoverMenuHandle, Tooltip, prelude::*,
};
use util::{ResultExt, maybe, paths::PathWithPosition, post_inc};
use workspace::{
@@ -222,6 +223,26 @@ impl FileFinder {
});
}
+ fn handle_toggle_ignored(
+ &mut self,
+ _: &ToggleIncludeIgnored,
+ window: &mut Window,
+ cx: &mut Context<Self>,
+ ) {
+ self.picker.update(cx, |picker, cx| {
+ picker.delegate.include_ignored = match picker.delegate.include_ignored {
+ Some(true) => match FileFinderSettings::get_global(cx).include_ignored {
+ Some(_) => Some(false),
+ None => None,
+ },
+ Some(false) => Some(true),
+ None => Some(true),
+ };
+ picker.delegate.include_ignored_refresh =
+ picker.delegate.update_matches(picker.query(cx), window, cx);
+ });
+ }
+
fn go_to_file_split_left(
&mut self,
_: &pane::SplitLeft,
@@ -325,6 +346,7 @@ impl Render for FileFinder {
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
.on_action(cx.listener(Self::handle_select_prev))
.on_action(cx.listener(Self::handle_toggle_menu))
+ .on_action(cx.listener(Self::handle_toggle_ignored))
.on_action(cx.listener(Self::go_to_file_split_left))
.on_action(cx.listener(Self::go_to_file_split_right))
.on_action(cx.listener(Self::go_to_file_split_up))
@@ -351,6 +373,8 @@ pub struct FileFinderDelegate {
first_update: bool,
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
focus_handle: FocusHandle,
+ include_ignored: Option<bool>,
+ include_ignored_refresh: Task<()>,
}
/// Use a custom ordering for file finder: the regular one
@@ -736,6 +760,8 @@ impl FileFinderDelegate {
first_update: true,
popover_menu_handle: PopoverMenuHandle::default(),
focus_handle: cx.focus_handle(),
+ include_ignored: FileFinderSettings::get_global(cx).include_ignored,
+ include_ignored_refresh: Task::ready(()),
}
}
@@ -779,7 +805,11 @@ impl FileFinderDelegate {
let worktree = worktree.read(cx);
PathMatchCandidateSet {
snapshot: worktree.snapshot(),
- include_ignored: true,
+ include_ignored: self.include_ignored.unwrap_or_else(|| {
+ worktree
+ .root_entry()
+ .map_or(false, |entry| entry.is_ignored)
+ }),
include_root_name,
candidates: project::Candidates::Files,
}
@@ -1468,38 +1498,75 @@ impl PickerDelegate for FileFinderDelegate {
h_flex()
.w_full()
.p_2()
- .gap_2()
- .justify_end()
+ .justify_between()
.border_t_1()
.border_color(cx.theme().colors().border_variant)
.child(
- Button::new("open-selection", "Open").on_click(|_, window, cx| {
- window.dispatch_action(menu::Confirm.boxed_clone(), cx)
- }),
+ IconButton::new("toggle-ignored", IconName::Sliders)
+ .on_click({
+ let focus_handle = self.focus_handle.clone();
+ move |_, window, cx| {
+ focus_handle.dispatch_action(&ToggleIncludeIgnored, window, cx);
+ }
+ })
+ .style(ButtonStyle::Subtle)
+ .shape(IconButtonShape::Square)
+ .toggle_state(self.include_ignored.unwrap_or(false))
+ .tooltip({
+ let focus_handle = self.focus_handle.clone();
+ move |window, cx| {
+ Tooltip::for_action_in(
+ "Use ignored files",
+ &ToggleIncludeIgnored,
+ &focus_handle,
+ window,
+ cx,
+ )
+ }
+ }),
)
.child(
- PopoverMenu::new("menu-popover")
- .with_handle(self.popover_menu_handle.clone())
- .attach(gpui::Corner::TopRight)
- .anchor(gpui::Corner::BottomRight)
- .trigger(
- Button::new("actions-trigger", "Splitβ¦")
- .selected_label_color(Color::Accent),
+ h_flex()
+ .p_2()
+ .gap_2()
+ .child(
+ Button::new("open-selection", "Open").on_click(|_, window, cx| {
+ window.dispatch_action(menu::Confirm.boxed_clone(), cx)
+ }),
)
- .menu({
- move |window, cx| {
- Some(ContextMenu::build(window, cx, {
- let context = context.clone();
- move |menu, _, _| {
- menu.context(context)
- .action("Split Left", pane::SplitLeft.boxed_clone())
- .action("Split Right", pane::SplitRight.boxed_clone())
- .action("Split Up", pane::SplitUp.boxed_clone())
- .action("Split Down", pane::SplitDown.boxed_clone())
+ .child(
+ PopoverMenu::new("menu-popover")
+ .with_handle(self.popover_menu_handle.clone())
+ .attach(gpui::Corner::TopRight)
+ .anchor(gpui::Corner::BottomRight)
+ .trigger(
+ Button::new("actions-trigger", "Splitβ¦")
+ .selected_label_color(Color::Accent),
+ )
+ .menu({
+ move |window, cx| {
+ Some(ContextMenu::build(window, cx, {
+ let context = context.clone();
+ move |menu, _, _| {
+ menu.context(context)
+ .action(
+ "Split Left",
+ pane::SplitLeft.boxed_clone(),
+ )
+ .action(
+ "Split Right",
+ pane::SplitRight.boxed_clone(),
+ )
+ .action("Split Up", pane::SplitUp.boxed_clone())
+ .action(
+ "Split Down",
+ pane::SplitDown.boxed_clone(),
+ )
+ }
+ }))
}
- }))
- }
- }),
+ }),
+ ),
)
.into_any(),
)
@@ -8,6 +8,7 @@ pub struct FileFinderSettings {
pub file_icons: bool,
pub modal_max_width: Option<FileFinderWidth>,
pub skip_focus_for_active_in_search: bool,
+ pub include_ignored: Option<bool>,
}
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
@@ -24,6 +25,20 @@ pub struct FileFinderSettingsContent {
///
/// Default: true
pub skip_focus_for_active_in_search: Option<bool>,
+ /// Determines whether to show the git status in the file finder
+ ///
+ /// Default: true
+ pub git_status: Option<bool>,
+ /// Whether to use gitignored files when searching.
+ /// Only the file Zed had indexed will be used, not necessary all the gitignored files.
+ ///
+ /// Can accept 3 values:
+ /// * `Some(true)`: Use all gitignored files
+ /// * `Some(false)`: Use only the files Zed had indexed
+ /// * `None`: Be smart and search for ignored when called from a gitignored worktree
+ ///
+ /// Default: None
+ pub include_ignored: Option<Option<bool>>,
}
impl Settings for FileFinderSettings {
@@ -1,9 +1,10 @@
-use std::{assert_eq, future::IntoFuture, path::Path, time::Duration};
+use std::{future::IntoFuture, path::Path, time::Duration};
use super::*;
use editor::Editor;
use gpui::{Entity, TestAppContext, VisualTestContext};
use menu::{Confirm, SelectNext, SelectPrevious};
+use pretty_assertions::assert_eq;
use project::{FS_WATCH_LATENCY, RemoveOptions};
use serde_json::json;
use util::path;
@@ -646,6 +647,31 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
.await;
let (picker, workspace, cx) = build_find_picker(project, cx);
+ picker
+ .update_in(cx, |picker, window, cx| {
+ picker
+ .delegate
+ .spawn_search(test_path_position("hi"), window, cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ let matches = collect_search_matches(picker);
+ assert_eq!(matches.history.len(), 0);
+ assert_eq!(
+ matches.search,
+ vec![
+ PathBuf::from("ignored-root/hi"),
+ PathBuf::from("tracked-root/hi"),
+ PathBuf::from("ignored-root/hiccup"),
+ PathBuf::from("tracked-root/hiccup"),
+ PathBuf::from("ignored-root/height"),
+ PathBuf::from("ignored-root/happiness"),
+ PathBuf::from("tracked-root/happiness"),
+ ],
+ "All ignored files that were indexed are found for default ignored mode"
+ );
+ });
+ cx.dispatch_action(ToggleIncludeIgnored);
picker
.update_in(cx, |picker, window, cx| {
picker
@@ -668,7 +694,29 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
PathBuf::from("ignored-root/happiness"),
PathBuf::from("tracked-root/happiness"),
],
- "All ignored files that were indexed are found"
+ "All ignored files should be found, for the toggled on ignored mode"
+ );
+ });
+
+ picker
+ .update_in(cx, |picker, window, cx| {
+ picker.delegate.include_ignored = Some(false);
+ picker
+ .delegate
+ .spawn_search(test_path_position("hi"), window, cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ let matches = collect_search_matches(picker);
+ assert_eq!(matches.history.len(), 0);
+ assert_eq!(
+ matches.search,
+ vec![
+ PathBuf::from("tracked-root/hi"),
+ PathBuf::from("tracked-root/hiccup"),
+ PathBuf::from("tracked-root/happiness"),
+ ],
+ "Only non-ignored files should be found for the turned off ignored mode"
);
});
@@ -686,6 +734,7 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
})
.await
.unwrap();
+ cx.run_until_parked();
workspace
.update_in(cx, |workspace, window, cx| {
workspace.active_pane().update(cx, |pane, cx| {
@@ -695,8 +744,37 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
})
.await
.unwrap();
+ cx.run_until_parked();
+
picker
.update_in(cx, |picker, window, cx| {
+ picker.delegate.include_ignored = None;
+ picker
+ .delegate
+ .spawn_search(test_path_position("hi"), window, cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ let matches = collect_search_matches(picker);
+ assert_eq!(matches.history.len(), 0);
+ assert_eq!(
+ matches.search,
+ vec![
+ PathBuf::from("ignored-root/hi"),
+ PathBuf::from("tracked-root/hi"),
+ PathBuf::from("ignored-root/hiccup"),
+ PathBuf::from("tracked-root/hiccup"),
+ PathBuf::from("ignored-root/height"),
+ PathBuf::from("ignored-root/happiness"),
+ PathBuf::from("tracked-root/happiness"),
+ ],
+ "Only for the worktree with the ignored root, all indexed ignored files are found in the auto ignored mode"
+ );
+ });
+
+ picker
+ .update_in(cx, |picker, window, cx| {
+ picker.delegate.include_ignored = Some(true);
picker
.delegate
.spawn_search(test_path_position("hi"), window, cx)
@@ -719,7 +797,29 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
PathBuf::from("ignored-root/happiness"),
PathBuf::from("tracked-root/happiness"),
],
- "All ignored files that were indexed are found"
+ "All ignored files that were indexed are found in the turned on ignored mode"
+ );
+ });
+
+ picker
+ .update_in(cx, |picker, window, cx| {
+ picker.delegate.include_ignored = Some(false);
+ picker
+ .delegate
+ .spawn_search(test_path_position("hi"), window, cx)
+ })
+ .await;
+ picker.update(cx, |picker, _| {
+ let matches = collect_search_matches(picker);
+ assert_eq!(matches.history.len(), 0);
+ assert_eq!(
+ matches.search,
+ vec![
+ PathBuf::from("tracked-root/hi"),
+ PathBuf::from("tracked-root/hiccup"),
+ PathBuf::from("tracked-root/happiness"),
+ ],
+ "Only non-ignored files should be found for the turned off ignored mode"
);
});
}