Properly handle ignored files in the file finder (#31542)
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
This commit is contained in:
parent
5b6b911946
commit
94c006236e
6 changed files with 229 additions and 33 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
@ -5381,8 +5381,10 @@ dependencies = [
|
||||||
"language",
|
"language",
|
||||||
"menu",
|
"menu",
|
||||||
"picker",
|
"picker",
|
||||||
|
"pretty_assertions",
|
||||||
"project",
|
"project",
|
||||||
"schemars",
|
"schemars",
|
||||||
|
"search",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_derive",
|
"serde_derive",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
|
|
@ -959,7 +959,17 @@
|
||||||
// "skip_focus_for_active_in_search": false
|
// "skip_focus_for_active_in_search": false
|
||||||
//
|
//
|
||||||
// Default: true
|
// 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
|
// Whether or not to remove any trailing whitespace from lines of a buffer
|
||||||
// before saving it.
|
// before saving it.
|
||||||
|
|
|
@ -24,6 +24,7 @@ menu.workspace = true
|
||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
schemars.workspace = true
|
schemars.workspace = true
|
||||||
|
search.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_derive.workspace = true
|
serde_derive.workspace = true
|
||||||
|
@ -40,6 +41,7 @@ editor = { workspace = true, features = ["test-support"] }
|
||||||
gpui = { workspace = true, features = ["test-support"] }
|
gpui = { workspace = true, features = ["test-support"] }
|
||||||
language = { workspace = true, features = ["test-support"] }
|
language = { workspace = true, features = ["test-support"] }
|
||||||
picker = { workspace = true, features = ["test-support"] }
|
picker = { workspace = true, features = ["test-support"] }
|
||||||
|
pretty_assertions.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
theme = { workspace = true, features = ["test-support"] }
|
theme = { workspace = true, features = ["test-support"] }
|
||||||
workspace = { 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 open_path_prompt::OpenPathPrompt;
|
||||||
use picker::{Picker, PickerDelegate};
|
use picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||||
|
use search::ToggleIncludeIgnored;
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
|
@ -37,8 +38,8 @@ use std::{
|
||||||
};
|
};
|
||||||
use text::Point;
|
use text::Point;
|
||||||
use ui::{
|
use ui::{
|
||||||
ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu, PopoverMenuHandle,
|
ContextMenu, HighlightedLabel, IconButtonShape, ListItem, ListItemSpacing, PopoverMenu,
|
||||||
prelude::*,
|
PopoverMenuHandle, Tooltip, prelude::*,
|
||||||
};
|
};
|
||||||
use util::{ResultExt, maybe, paths::PathWithPosition, post_inc};
|
use util::{ResultExt, maybe, paths::PathWithPosition, post_inc};
|
||||||
use workspace::{
|
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(
|
fn go_to_file_split_left(
|
||||||
&mut self,
|
&mut self,
|
||||||
_: &pane::SplitLeft,
|
_: &pane::SplitLeft,
|
||||||
|
@ -325,6 +346,7 @@ impl Render for FileFinder {
|
||||||
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
.on_modifiers_changed(cx.listener(Self::handle_modifiers_changed))
|
||||||
.on_action(cx.listener(Self::handle_select_prev))
|
.on_action(cx.listener(Self::handle_select_prev))
|
||||||
.on_action(cx.listener(Self::handle_toggle_menu))
|
.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_left))
|
||||||
.on_action(cx.listener(Self::go_to_file_split_right))
|
.on_action(cx.listener(Self::go_to_file_split_right))
|
||||||
.on_action(cx.listener(Self::go_to_file_split_up))
|
.on_action(cx.listener(Self::go_to_file_split_up))
|
||||||
|
@ -351,6 +373,8 @@ pub struct FileFinderDelegate {
|
||||||
first_update: bool,
|
first_update: bool,
|
||||||
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
|
popover_menu_handle: PopoverMenuHandle<ContextMenu>,
|
||||||
focus_handle: FocusHandle,
|
focus_handle: FocusHandle,
|
||||||
|
include_ignored: Option<bool>,
|
||||||
|
include_ignored_refresh: Task<()>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use a custom ordering for file finder: the regular one
|
/// Use a custom ordering for file finder: the regular one
|
||||||
|
@ -736,6 +760,8 @@ impl FileFinderDelegate {
|
||||||
first_update: true,
|
first_update: true,
|
||||||
popover_menu_handle: PopoverMenuHandle::default(),
|
popover_menu_handle: PopoverMenuHandle::default(),
|
||||||
focus_handle: cx.focus_handle(),
|
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);
|
let worktree = worktree.read(cx);
|
||||||
PathMatchCandidateSet {
|
PathMatchCandidateSet {
|
||||||
snapshot: worktree.snapshot(),
|
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,
|
include_root_name,
|
||||||
candidates: project::Candidates::Files,
|
candidates: project::Candidates::Files,
|
||||||
}
|
}
|
||||||
|
@ -1468,39 +1498,76 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
h_flex()
|
h_flex()
|
||||||
.w_full()
|
.w_full()
|
||||||
.p_2()
|
.p_2()
|
||||||
.gap_2()
|
.justify_between()
|
||||||
.justify_end()
|
|
||||||
.border_t_1()
|
.border_t_1()
|
||||||
.border_color(cx.theme().colors().border_variant)
|
.border_color(cx.theme().colors().border_variant)
|
||||||
.child(
|
.child(
|
||||||
Button::new("open-selection", "Open").on_click(|_, window, cx| {
|
IconButton::new("toggle-ignored", IconName::Sliders)
|
||||||
window.dispatch_action(menu::Confirm.boxed_clone(), cx)
|
.on_click({
|
||||||
}),
|
let focus_handle = self.focus_handle.clone();
|
||||||
)
|
move |_, window, cx| {
|
||||||
.child(
|
focus_handle.dispatch_action(&ToggleIncludeIgnored, window, cx);
|
||||||
PopoverMenu::new("menu-popover")
|
}
|
||||||
.with_handle(self.popover_menu_handle.clone())
|
})
|
||||||
.attach(gpui::Corner::TopRight)
|
.style(ButtonStyle::Subtle)
|
||||||
.anchor(gpui::Corner::BottomRight)
|
.shape(IconButtonShape::Square)
|
||||||
.trigger(
|
.toggle_state(self.include_ignored.unwrap_or(false))
|
||||||
Button::new("actions-trigger", "Split…")
|
.tooltip({
|
||||||
.selected_label_color(Color::Accent),
|
let focus_handle = self.focus_handle.clone();
|
||||||
)
|
|
||||||
.menu({
|
|
||||||
move |window, cx| {
|
move |window, cx| {
|
||||||
Some(ContextMenu::build(window, cx, {
|
Tooltip::for_action_in(
|
||||||
let context = context.clone();
|
"Use ignored files",
|
||||||
move |menu, _, _| {
|
&ToggleIncludeIgnored,
|
||||||
menu.context(context)
|
&focus_handle,
|
||||||
.action("Split Left", pane::SplitLeft.boxed_clone())
|
window,
|
||||||
.action("Split Right", pane::SplitRight.boxed_clone())
|
cx,
|
||||||
.action("Split Up", pane::SplitUp.boxed_clone())
|
)
|
||||||
.action("Split Down", pane::SplitDown.boxed_clone())
|
|
||||||
}
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
.child(
|
||||||
|
h_flex()
|
||||||
|
.p_2()
|
||||||
|
.gap_2()
|
||||||
|
.child(
|
||||||
|
Button::new("open-selection", "Open").on_click(|_, window, cx| {
|
||||||
|
window.dispatch_action(menu::Confirm.boxed_clone(), 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),
|
||||||
|
)
|
||||||
|
.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(),
|
.into_any(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ pub struct FileFinderSettings {
|
||||||
pub file_icons: bool,
|
pub file_icons: bool,
|
||||||
pub modal_max_width: Option<FileFinderWidth>,
|
pub modal_max_width: Option<FileFinderWidth>,
|
||||||
pub skip_focus_for_active_in_search: bool,
|
pub skip_focus_for_active_in_search: bool,
|
||||||
|
pub include_ignored: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
#[derive(Clone, Default, Serialize, Deserialize, JsonSchema, Debug)]
|
||||||
|
@ -24,6 +25,20 @@ pub struct FileFinderSettingsContent {
|
||||||
///
|
///
|
||||||
/// Default: true
|
/// Default: true
|
||||||
pub skip_focus_for_active_in_search: Option<bool>,
|
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 {
|
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 super::*;
|
||||||
use editor::Editor;
|
use editor::Editor;
|
||||||
use gpui::{Entity, TestAppContext, VisualTestContext};
|
use gpui::{Entity, TestAppContext, VisualTestContext};
|
||||||
use menu::{Confirm, SelectNext, SelectPrevious};
|
use menu::{Confirm, SelectNext, SelectPrevious};
|
||||||
|
use pretty_assertions::assert_eq;
|
||||||
use project::{FS_WATCH_LATENCY, RemoveOptions};
|
use project::{FS_WATCH_LATENCY, RemoveOptions};
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use util::path;
|
use util::path;
|
||||||
|
@ -646,6 +647,31 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
||||||
.await;
|
.await;
|
||||||
let (picker, workspace, cx) = build_find_picker(project, cx);
|
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
|
picker
|
||||||
.update_in(cx, |picker, window, cx| {
|
.update_in(cx, |picker, window, cx| {
|
||||||
picker
|
picker
|
||||||
|
@ -668,7 +694,29 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
||||||
PathBuf::from("ignored-root/happiness"),
|
PathBuf::from("ignored-root/happiness"),
|
||||||
PathBuf::from("tracked-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
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cx.run_until_parked();
|
||||||
workspace
|
workspace
|
||||||
.update_in(cx, |workspace, window, cx| {
|
.update_in(cx, |workspace, window, cx| {
|
||||||
workspace.active_pane().update(cx, |pane, cx| {
|
workspace.active_pane().update(cx, |pane, cx| {
|
||||||
|
@ -695,8 +744,37 @@ async fn test_ignored_root(cx: &mut TestAppContext) {
|
||||||
})
|
})
|
||||||
.await
|
.await
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
cx.run_until_parked();
|
||||||
|
|
||||||
picker
|
picker
|
||||||
.update_in(cx, |picker, window, cx| {
|
.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
|
picker
|
||||||
.delegate
|
.delegate
|
||||||
.spawn_search(test_path_position("hi"), window, cx)
|
.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("ignored-root/happiness"),
|
||||||
PathBuf::from("tracked-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"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue