mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
Improve file finder match results (#12103)
This commit is contained in:
parent
c290d924f1
commit
3382e79ef9
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -3973,6 +3973,7 @@ dependencies = [
|
|||||||
"menu",
|
"menu",
|
||||||
"picker",
|
"picker",
|
||||||
"project",
|
"project",
|
||||||
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"settings",
|
"settings",
|
||||||
"text",
|
"text",
|
||||||
|
@ -24,6 +24,7 @@ menu.workspace = true
|
|||||||
picker.workspace = true
|
picker.workspace = true
|
||||||
project.workspace = true
|
project.workspace = true
|
||||||
settings.workspace = true
|
settings.workspace = true
|
||||||
|
serde.workspace = true
|
||||||
text.workspace = true
|
text.workspace = true
|
||||||
theme.workspace = true
|
theme.workspace = true
|
||||||
ui.workspace = true
|
ui.workspace = true
|
||||||
|
@ -3,13 +3,13 @@ mod file_finder_tests;
|
|||||||
|
|
||||||
mod new_path_prompt;
|
mod new_path_prompt;
|
||||||
|
|
||||||
use collections::{HashMap, HashSet};
|
use collections::{BTreeSet, HashMap};
|
||||||
use editor::{scroll::Autoscroll, Bias, Editor};
|
use editor::{scroll::Autoscroll, Bias, Editor};
|
||||||
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
use fuzzy::{CharBag, PathMatch, PathMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
actions, rems, Action, AppContext, DismissEvent, EventEmitter, FocusHandle, FocusableView,
|
actions, impl_actions, rems, Action, AnyElement, AppContext, DismissEvent, EventEmitter,
|
||||||
Model, Modifiers, ModifiersChangedEvent, ParentElement, Render, Styled, Task, View,
|
FocusHandle, FocusableView, Model, Modifiers, ModifiersChangedEvent, ParentElement, Render,
|
||||||
ViewContext, VisualContext, WeakView,
|
Styled, Task, View, ViewContext, VisualContext, WeakView,
|
||||||
};
|
};
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use new_path_prompt::NewPathPrompt;
|
use new_path_prompt::NewPathPrompt;
|
||||||
@ -29,7 +29,14 @@ use ui::{prelude::*, HighlightedLabel, ListItem, ListItemSpacing};
|
|||||||
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
use util::{paths::PathLikeWithPosition, post_inc, ResultExt};
|
||||||
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
|
use workspace::{item::PreviewTabsSettings, ModalView, Workspace};
|
||||||
|
|
||||||
actions!(file_finder, [Toggle, SelectPrev]);
|
actions!(file_finder, [SelectPrev]);
|
||||||
|
impl_actions!(file_finder, [Toggle]);
|
||||||
|
|
||||||
|
#[derive(Default, PartialEq, Eq, Clone, serde::Deserialize)]
|
||||||
|
pub struct Toggle {
|
||||||
|
#[serde(default)]
|
||||||
|
pub separate_history: bool,
|
||||||
|
}
|
||||||
|
|
||||||
impl ModalView for FileFinder {}
|
impl ModalView for FileFinder {}
|
||||||
|
|
||||||
@ -45,9 +52,9 @@ pub fn init(cx: &mut AppContext) {
|
|||||||
|
|
||||||
impl FileFinder {
|
impl FileFinder {
|
||||||
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
fn register(workspace: &mut Workspace, _: &mut ViewContext<Workspace>) {
|
||||||
workspace.register_action(|workspace, _: &Toggle, cx| {
|
workspace.register_action(|workspace, action: &Toggle, cx| {
|
||||||
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
|
let Some(file_finder) = workspace.active_modal::<Self>(cx) else {
|
||||||
Self::open(workspace, cx);
|
Self::open(workspace, action.separate_history, cx);
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -60,7 +67,7 @@ impl FileFinder {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) {
|
fn open(workspace: &mut Workspace, separate_history: bool, cx: &mut ViewContext<Workspace>) {
|
||||||
let project = workspace.project().read(cx);
|
let project = workspace.project().read(cx);
|
||||||
|
|
||||||
let currently_opened_path = workspace
|
let currently_opened_path = workspace
|
||||||
@ -92,6 +99,7 @@ impl FileFinder {
|
|||||||
project,
|
project,
|
||||||
currently_opened_path,
|
currently_opened_path,
|
||||||
history_items,
|
history_items,
|
||||||
|
separate_history,
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -161,6 +169,7 @@ pub struct FileFinderDelegate {
|
|||||||
has_changed_selected_index: bool,
|
has_changed_selected_index: bool,
|
||||||
cancel_flag: Arc<AtomicBool>,
|
cancel_flag: Arc<AtomicBool>,
|
||||||
history_items: Vec<FoundPath>,
|
history_items: Vec<FoundPath>,
|
||||||
|
separate_history: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Use a custom ordering for file finder: the regular one
|
/// Use a custom ordering for file finder: the regular one
|
||||||
@ -198,104 +207,117 @@ impl PartialOrd for ProjectPanelOrdMatch {
|
|||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Matches {
|
struct Matches {
|
||||||
history: Vec<(FoundPath, Option<ProjectPanelOrdMatch>)>,
|
separate_history: bool,
|
||||||
search: Vec<ProjectPanelOrdMatch>,
|
matches: Vec<Match>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone)]
|
||||||
enum Match<'a> {
|
enum Match {
|
||||||
History(&'a FoundPath, Option<&'a ProjectPanelOrdMatch>),
|
History(FoundPath, Option<ProjectPanelOrdMatch>),
|
||||||
Search(&'a ProjectPanelOrdMatch),
|
Search(ProjectPanelOrdMatch),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Matches {
|
impl Matches {
|
||||||
fn len(&self) -> usize {
|
fn len(&self) -> usize {
|
||||||
self.history.len() + self.search.len()
|
self.matches.len()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(&self, index: usize) -> Option<Match<'_>> {
|
fn get(&self, index: usize) -> Option<&Match> {
|
||||||
if index < self.history.len() {
|
self.matches.get(index)
|
||||||
self.history
|
|
||||||
.get(index)
|
|
||||||
.map(|(path, path_match)| Match::History(path, path_match.as_ref()))
|
|
||||||
} else {
|
|
||||||
self.search
|
|
||||||
.get(index - self.history.len())
|
|
||||||
.map(Match::Search)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn push_new_matches(
|
fn push_new_matches<'a>(
|
||||||
&mut self,
|
&'a mut self,
|
||||||
history_items: &Vec<FoundPath>,
|
history_items: impl IntoIterator<Item = &'a FoundPath> + Clone,
|
||||||
currently_opened: Option<&FoundPath>,
|
currently_opened: Option<&'a FoundPath>,
|
||||||
query: &PathLikeWithPosition<FileSearchQuery>,
|
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
||||||
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
|
new_search_matches: impl Iterator<Item = ProjectPanelOrdMatch>,
|
||||||
extend_old_matches: bool,
|
extend_old_matches: bool,
|
||||||
) {
|
) {
|
||||||
|
let no_history_score = 0;
|
||||||
let matching_history_paths =
|
let matching_history_paths =
|
||||||
matching_history_item_paths(history_items, currently_opened, query);
|
matching_history_item_paths(history_items.clone(), currently_opened, query);
|
||||||
let new_search_matches = new_search_matches
|
let new_search_matches = new_search_matches
|
||||||
.filter(|path_match| !matching_history_paths.contains_key(&path_match.0.path));
|
.filter(|path_match| !matching_history_paths.contains_key(&path_match.0.path))
|
||||||
|
.map(Match::Search)
|
||||||
self.set_new_history(
|
.map(|m| (no_history_score, m));
|
||||||
currently_opened,
|
let old_search_matches = self
|
||||||
Some(&matching_history_paths),
|
.matches
|
||||||
history_items,
|
.drain(..)
|
||||||
);
|
.filter(|_| extend_old_matches)
|
||||||
if extend_old_matches {
|
.filter(|m| matches!(m, Match::Search(_)))
|
||||||
self.search
|
.map(|m| (no_history_score, m));
|
||||||
.retain(|path_match| !matching_history_paths.contains_key(&path_match.0.path));
|
let history_matches = history_items
|
||||||
} else {
|
|
||||||
self.search.clear();
|
|
||||||
}
|
|
||||||
util::extend_sorted(&mut self.search, new_search_matches, 100, |a, b| b.cmp(a));
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_new_history<'a>(
|
|
||||||
&mut self,
|
|
||||||
currently_opened: Option<&'a FoundPath>,
|
|
||||||
query_matches: Option<&'a HashMap<Arc<Path>, ProjectPanelOrdMatch>>,
|
|
||||||
history_items: impl IntoIterator<Item = &'a FoundPath> + 'a,
|
|
||||||
) {
|
|
||||||
let mut processed_paths = HashSet::default();
|
|
||||||
self.history = history_items
|
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.chain(currently_opened)
|
.chain(currently_opened)
|
||||||
.filter(|&path| processed_paths.insert(path))
|
|
||||||
.filter_map(|history_item| match &query_matches {
|
|
||||||
Some(query_matches) => Some((
|
|
||||||
history_item.clone(),
|
|
||||||
Some(query_matches.get(&history_item.project.path)?.clone()),
|
|
||||||
)),
|
|
||||||
None => Some((history_item.clone(), None)),
|
|
||||||
})
|
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.sorted_by(
|
.filter_map(|(i, history_item)| {
|
||||||
|(index_a, (path_a, match_a)), (index_b, (path_b, match_b))| match (
|
let query_match = matching_history_paths
|
||||||
Some(path_a) == currently_opened,
|
.get(&history_item.project.path)
|
||||||
Some(path_b) == currently_opened,
|
.cloned();
|
||||||
) {
|
let query_match = if query.is_some() {
|
||||||
|
query_match?
|
||||||
|
} else {
|
||||||
|
query_match.flatten()
|
||||||
|
};
|
||||||
|
Some((i + 1, Match::History(history_item.clone(), query_match)))
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut unique_matches = BTreeSet::new();
|
||||||
|
self.matches = old_search_matches
|
||||||
|
.chain(history_matches)
|
||||||
|
.chain(new_search_matches)
|
||||||
|
.filter(|(_, m)| unique_matches.insert(m.clone()))
|
||||||
|
.sorted_by(|(history_score_a, a), (history_score_b, b)| {
|
||||||
|
match (a, b) {
|
||||||
// bubble currently opened files to the top
|
// bubble currently opened files to the top
|
||||||
(true, false) => cmp::Ordering::Less,
|
(Match::History(path, _), _) if Some(path) == currently_opened => {
|
||||||
(false, true) => cmp::Ordering::Greater,
|
cmp::Ordering::Less
|
||||||
// arrange the files by their score (best score on top) and by their occurrence in the history
|
}
|
||||||
// (history items visited later are on the top)
|
(_, Match::History(path, _)) if Some(path) == currently_opened => {
|
||||||
_ => match_b.cmp(match_a).then(index_a.cmp(index_b)),
|
cmp::Ordering::Greater
|
||||||
},
|
}
|
||||||
)
|
|
||||||
.map(|(_, paths)| paths)
|
(Match::History(_, _), Match::Search(_)) if self.separate_history => {
|
||||||
|
cmp::Ordering::Less
|
||||||
|
}
|
||||||
|
(Match::Search(_), Match::History(_, _)) if self.separate_history => {
|
||||||
|
cmp::Ordering::Greater
|
||||||
|
}
|
||||||
|
|
||||||
|
(Match::History(_, match_a), Match::History(_, match_b)) => match_b
|
||||||
|
.cmp(match_a)
|
||||||
|
.then(history_score_a.cmp(history_score_b)),
|
||||||
|
(Match::History(_, match_a), Match::Search(match_b)) => {
|
||||||
|
Some(match_b).cmp(&match_a.as_ref())
|
||||||
|
}
|
||||||
|
(Match::Search(match_a), Match::History(_, match_b)) => {
|
||||||
|
match_b.as_ref().cmp(&Some(match_a))
|
||||||
|
}
|
||||||
|
(Match::Search(match_a), Match::Search(match_b)) => match_b.cmp(match_a),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.take(100)
|
||||||
|
.map(|(_, m)| m)
|
||||||
.collect();
|
.collect();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn matching_history_item_paths(
|
fn matching_history_item_paths<'a>(
|
||||||
history_items: &Vec<FoundPath>,
|
history_items: impl IntoIterator<Item = &'a FoundPath>,
|
||||||
currently_opened: Option<&FoundPath>,
|
currently_opened: Option<&'a FoundPath>,
|
||||||
query: &PathLikeWithPosition<FileSearchQuery>,
|
query: Option<&PathLikeWithPosition<FileSearchQuery>>,
|
||||||
) -> HashMap<Arc<Path>, ProjectPanelOrdMatch> {
|
) -> HashMap<Arc<Path>, Option<ProjectPanelOrdMatch>> {
|
||||||
|
let Some(query) = query else {
|
||||||
|
return history_items
|
||||||
|
.into_iter()
|
||||||
|
.chain(currently_opened)
|
||||||
|
.map(|found_path| (Arc::clone(&found_path.project.path), None))
|
||||||
|
.collect();
|
||||||
|
};
|
||||||
|
|
||||||
let history_items_by_worktrees = history_items
|
let history_items_by_worktrees = history_items
|
||||||
.iter()
|
.into_iter()
|
||||||
.chain(currently_opened)
|
.chain(currently_opened)
|
||||||
.filter_map(|found_path| {
|
.filter_map(|found_path| {
|
||||||
let candidate = PathMatchCandidate {
|
let candidate = PathMatchCandidate {
|
||||||
@ -340,7 +362,7 @@ fn matching_history_item_paths(
|
|||||||
.map(|path_match| {
|
.map(|path_match| {
|
||||||
(
|
(
|
||||||
Arc::clone(&path_match.path),
|
Arc::clone(&path_match.path),
|
||||||
ProjectPanelOrdMatch(path_match),
|
Some(ProjectPanelOrdMatch(path_match)),
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -399,6 +421,7 @@ impl FileFinderDelegate {
|
|||||||
project: Model<Project>,
|
project: Model<Project>,
|
||||||
currently_opened_path: Option<FoundPath>,
|
currently_opened_path: Option<FoundPath>,
|
||||||
history_items: Vec<FoundPath>,
|
history_items: Vec<FoundPath>,
|
||||||
|
separate_history: bool,
|
||||||
cx: &mut ViewContext<FileFinder>,
|
cx: &mut ViewContext<FileFinder>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self::subscribe_to_updates(&project, cx);
|
Self::subscribe_to_updates(&project, cx);
|
||||||
@ -416,6 +439,7 @@ impl FileFinderDelegate {
|
|||||||
selected_index: 0,
|
selected_index: 0,
|
||||||
cancel_flag: Arc::new(AtomicBool::new(false)),
|
cancel_flag: Arc::new(AtomicBool::new(false)),
|
||||||
history_items,
|
history_items,
|
||||||
|
separate_history,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -510,7 +534,7 @@ impl FileFinderDelegate {
|
|||||||
self.matches.push_new_matches(
|
self.matches.push_new_matches(
|
||||||
&self.history_items,
|
&self.history_items,
|
||||||
self.currently_opened_path.as_ref(),
|
self.currently_opened_path.as_ref(),
|
||||||
&query,
|
Some(&query),
|
||||||
matches.into_iter(),
|
matches.into_iter(),
|
||||||
extend_old_matches,
|
extend_old_matches,
|
||||||
);
|
);
|
||||||
@ -523,7 +547,7 @@ impl FileFinderDelegate {
|
|||||||
|
|
||||||
fn labels_for_match(
|
fn labels_for_match(
|
||||||
&self,
|
&self,
|
||||||
path_match: Match,
|
path_match: &Match,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
) -> (String, Vec<usize>, String, Vec<usize>) {
|
) -> (String, Vec<usize>, String, Vec<usize>) {
|
||||||
@ -727,12 +751,21 @@ impl PickerDelegate for FileFinderDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn separators_after_indices(&self) -> Vec<usize> {
|
fn separators_after_indices(&self) -> Vec<usize> {
|
||||||
let history_items = self.matches.history.len();
|
if self.separate_history {
|
||||||
if history_items == 0 || self.matches.search.is_empty() {
|
let first_non_history_index = self
|
||||||
Vec::new()
|
.matches
|
||||||
} else {
|
.matches
|
||||||
vec![history_items - 1]
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.find(|(_, m)| !matches!(m, Match::History(_, _)))
|
||||||
|
.map(|(i, _)| i);
|
||||||
|
if let Some(first_non_history_index) = first_non_history_index {
|
||||||
|
if first_non_history_index > 0 {
|
||||||
|
return vec![first_non_history_index - 1];
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Vec::new()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn update_matches(
|
fn update_matches(
|
||||||
@ -746,18 +779,20 @@ impl PickerDelegate for FileFinderDelegate {
|
|||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
self.latest_search_id = post_inc(&mut self.search_count);
|
self.latest_search_id = post_inc(&mut self.search_count);
|
||||||
self.matches = Matches {
|
self.matches = Matches {
|
||||||
history: Vec::new(),
|
separate_history: self.separate_history,
|
||||||
search: Vec::new(),
|
..Matches::default()
|
||||||
};
|
};
|
||||||
self.matches.set_new_history(
|
self.matches.push_new_matches(
|
||||||
self.currently_opened_path.as_ref(),
|
|
||||||
None,
|
|
||||||
self.history_items.iter().filter(|history_item| {
|
self.history_items.iter().filter(|history_item| {
|
||||||
project
|
project
|
||||||
.worktree_for_id(history_item.project.worktree_id, cx)
|
.worktree_for_id(history_item.project.worktree_id, cx)
|
||||||
.is_some()
|
.is_some()
|
||||||
|| (project.is_local() && history_item.absolute.is_some())
|
|| (project.is_local() && history_item.absolute.is_some())
|
||||||
}),
|
}),
|
||||||
|
self.currently_opened_path.as_ref(),
|
||||||
|
None,
|
||||||
|
None.into_iter(),
|
||||||
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
self.selected_index = 0;
|
self.selected_index = 0;
|
||||||
@ -919,12 +954,23 @@ impl PickerDelegate for FileFinderDelegate {
|
|||||||
.get(ix)
|
.get(ix)
|
||||||
.expect("Invalid matches state: no element for index {ix}");
|
.expect("Invalid matches state: no element for index {ix}");
|
||||||
|
|
||||||
|
let icon = match &path_match {
|
||||||
|
Match::History(_, _) => Icon::new(IconName::HistoryRerun)
|
||||||
|
.color(Color::Muted)
|
||||||
|
.size(IconSize::Small)
|
||||||
|
.into_any_element(),
|
||||||
|
Match::Search(_) => v_flex()
|
||||||
|
.flex_none()
|
||||||
|
.size(IconSize::Small.rems())
|
||||||
|
.into_any_element(),
|
||||||
|
};
|
||||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||||
self.labels_for_match(path_match, cx, ix);
|
self.labels_for_match(path_match, cx, ix);
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
.spacing(ListItemSpacing::Sparse)
|
.spacing(ListItemSpacing::Sparse)
|
||||||
|
.end_slot::<AnyElement>(Some(icon))
|
||||||
.inset(true)
|
.inset(true)
|
||||||
.selected(selected)
|
.selected(selected)
|
||||||
.child(
|
.child(
|
||||||
|
@ -116,7 +116,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
|
|||||||
.await;
|
.await;
|
||||||
picker.update(cx, |picker, _| {
|
picker.update(cx, |picker, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collect_search_matches(picker).search_only(),
|
collect_search_matches(picker).search_paths_only(),
|
||||||
vec![PathBuf::from("a/b/file2.txt")],
|
vec![PathBuf::from("a/b/file2.txt")],
|
||||||
"Matching abs path should be the only match"
|
"Matching abs path should be the only match"
|
||||||
)
|
)
|
||||||
@ -138,7 +138,7 @@ async fn test_absolute_paths(cx: &mut TestAppContext) {
|
|||||||
.await;
|
.await;
|
||||||
picker.update(cx, |picker, _| {
|
picker.update(cx, |picker, _| {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collect_search_matches(picker).search_only(),
|
collect_search_matches(picker).search_paths_only(),
|
||||||
Vec::<PathBuf>::new(),
|
Vec::<PathBuf>::new(),
|
||||||
"Mismatching abs path should produce no matches"
|
"Mismatching abs path should produce no matches"
|
||||||
)
|
)
|
||||||
@ -171,7 +171,7 @@ async fn test_complex_path(cx: &mut TestAppContext) {
|
|||||||
picker.update(cx, |picker, _| {
|
picker.update(cx, |picker, _| {
|
||||||
assert_eq!(picker.delegate.matches.len(), 1);
|
assert_eq!(picker.delegate.matches.len(), 1);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
collect_search_matches(picker).search_only(),
|
collect_search_matches(picker).search_paths_only(),
|
||||||
vec![PathBuf::from("其他/S数据表格/task.xlsx")],
|
vec![PathBuf::from("其他/S数据表格/task.xlsx")],
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
@ -369,12 +369,8 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
picker.update(cx, |picker, cx| {
|
picker.update(cx, |picker, cx| {
|
||||||
|
let matches = collect_search_matches(picker).search_matches_only();
|
||||||
let delegate = &mut picker.delegate;
|
let delegate = &mut picker.delegate;
|
||||||
assert!(
|
|
||||||
delegate.matches.history.is_empty(),
|
|
||||||
"Search matches expected"
|
|
||||||
);
|
|
||||||
let matches = delegate.matches.search.clone();
|
|
||||||
|
|
||||||
// Simulate a search being cancelled after the time limit,
|
// Simulate a search being cancelled after the time limit,
|
||||||
// returning only a subset of the matches that would have been found.
|
// returning only a subset of the matches that would have been found.
|
||||||
@ -383,7 +379,10 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
|
|||||||
delegate.latest_search_id,
|
delegate.latest_search_id,
|
||||||
true, // did-cancel
|
true, // did-cancel
|
||||||
query.clone(),
|
query.clone(),
|
||||||
vec![matches[1].clone(), matches[3].clone()],
|
vec![
|
||||||
|
ProjectPanelOrdMatch(matches[1].clone()),
|
||||||
|
ProjectPanelOrdMatch(matches[3].clone()),
|
||||||
|
],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -393,15 +392,20 @@ async fn test_matching_cancellation(cx: &mut TestAppContext) {
|
|||||||
delegate.latest_search_id,
|
delegate.latest_search_id,
|
||||||
true, // did-cancel
|
true, // did-cancel
|
||||||
query.clone(),
|
query.clone(),
|
||||||
vec![matches[0].clone(), matches[2].clone(), matches[3].clone()],
|
vec![
|
||||||
|
ProjectPanelOrdMatch(matches[0].clone()),
|
||||||
|
ProjectPanelOrdMatch(matches[2].clone()),
|
||||||
|
ProjectPanelOrdMatch(matches[3].clone()),
|
||||||
|
],
|
||||||
cx,
|
cx,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(
|
assert_eq!(
|
||||||
delegate.matches.history.is_empty(),
|
collect_search_matches(picker)
|
||||||
"Search matches expected"
|
.search_matches_only()
|
||||||
|
.as_slice(),
|
||||||
|
&matches[0..4]
|
||||||
);
|
);
|
||||||
assert_eq!(delegate.matches.search.as_slice(), &matches[0..4]);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -480,15 +484,11 @@ async fn test_single_file_worktrees(cx: &mut TestAppContext) {
|
|||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
let picker = picker.read(cx);
|
let picker = picker.read(cx);
|
||||||
let delegate = &picker.delegate;
|
let delegate = &picker.delegate;
|
||||||
assert!(
|
let matches = collect_search_matches(picker).search_matches_only();
|
||||||
delegate.matches.history.is_empty(),
|
|
||||||
"Search matches expected"
|
|
||||||
);
|
|
||||||
let matches = delegate.matches.search.clone();
|
|
||||||
assert_eq!(matches.len(), 1);
|
assert_eq!(matches.len(), 1);
|
||||||
|
|
||||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
let (file_name, file_name_positions, full_path, full_path_positions) =
|
||||||
delegate.labels_for_path_match(&matches[0].0);
|
delegate.labels_for_path_match(&matches[0]);
|
||||||
assert_eq!(file_name, "the-file");
|
assert_eq!(file_name, "the-file");
|
||||||
assert_eq!(file_name_positions, &[0, 1, 4]);
|
assert_eq!(file_name_positions, &[0, 1, 4]);
|
||||||
assert_eq!(full_path, "");
|
assert_eq!(full_path, "");
|
||||||
@ -552,15 +552,10 @@ async fn test_path_distance_ordering(cx: &mut TestAppContext) {
|
|||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
finder.update(cx, |f, _| {
|
finder.update(cx, |picker, _| {
|
||||||
let delegate = &f.delegate;
|
let matches = collect_search_matches(picker).search_paths_only();
|
||||||
assert!(
|
assert_eq!(matches[0].as_path(), Path::new("dir2/a.txt"));
|
||||||
delegate.matches.history.is_empty(),
|
assert_eq!(matches[1].as_path(), Path::new("dir1/a.txt"));
|
||||||
"Search matches expected"
|
|
||||||
);
|
|
||||||
let matches = &delegate.matches.search;
|
|
||||||
assert_eq!(matches[0].0.path.as_ref(), Path::new("dir2/a.txt"));
|
|
||||||
assert_eq!(matches[1].0.path.as_ref(), Path::new("dir1/a.txt"));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -877,7 +872,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
|
|||||||
let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
let current_history = open_close_queried_buffer("sec", 1, "second.rs", &workspace, cx).await;
|
||||||
|
|
||||||
for expected_selected_index in 0..current_history.len() {
|
for expected_selected_index in 0..current_history.len() {
|
||||||
cx.dispatch_action(Toggle);
|
cx.dispatch_action(Toggle::default());
|
||||||
let picker = active_file_picker(&workspace, cx);
|
let picker = active_file_picker(&workspace, cx);
|
||||||
let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
|
let selected_index = picker.update(cx, |picker, _| picker.delegate.selected_index());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@ -886,7 +881,7 @@ async fn test_toggle_panel_new_selections(cx: &mut gpui::TestAppContext) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
cx.dispatch_action(Toggle);
|
cx.dispatch_action(Toggle::default());
|
||||||
let selected_index = workspace.update(cx, |workspace, cx| {
|
let selected_index = workspace.update(cx, |workspace, cx| {
|
||||||
workspace
|
workspace
|
||||||
.active_modal::<FileFinder>(cx)
|
.active_modal::<FileFinder>(cx)
|
||||||
@ -945,20 +940,19 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||||||
finder.delegate.update_matches(first_query.to_string(), cx)
|
finder.delegate.update_matches(first_query.to_string(), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
finder.update(cx, |finder, _| {
|
finder.update(cx, |picker, _| {
|
||||||
let delegate = &finder.delegate;
|
let matches = collect_search_matches(picker);
|
||||||
assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
|
assert_eq!(matches.history.len(), 1, "Only one history item contains {first_query}, it should be present and others should be filtered out");
|
||||||
let history_match = delegate.matches.history.first().unwrap();
|
let history_match = matches.history_found_paths.first().expect("Should have path matches for history items after querying");
|
||||||
assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
|
assert_eq!(history_match, &FoundPath::new(
|
||||||
assert_eq!(history_match.0, FoundPath::new(
|
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("test/first.rs")),
|
path: Arc::from(Path::new("test/first.rs")),
|
||||||
},
|
},
|
||||||
Some(PathBuf::from("/src/test/first.rs"))
|
Some(PathBuf::from("/src/test/first.rs"))
|
||||||
));
|
));
|
||||||
assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
|
assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query}, it should be present");
|
||||||
assert_eq!(delegate.matches.search.first().unwrap().0.path.as_ref(), Path::new("test/fourth.rs"));
|
assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs"));
|
||||||
});
|
});
|
||||||
|
|
||||||
let second_query = "fsdasdsa";
|
let second_query = "fsdasdsa";
|
||||||
@ -968,14 +962,11 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||||||
finder.delegate.update_matches(second_query.to_string(), cx)
|
finder.delegate.update_matches(second_query.to_string(), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
finder.update(cx, |finder, _| {
|
finder.update(cx, |picker, _| {
|
||||||
let delegate = &finder.delegate;
|
|
||||||
assert!(
|
assert!(
|
||||||
delegate.matches.history.is_empty(),
|
collect_search_matches(picker)
|
||||||
"No history entries should match {second_query}"
|
.search_paths_only()
|
||||||
);
|
.is_empty(),
|
||||||
assert!(
|
|
||||||
delegate.matches.search.is_empty(),
|
|
||||||
"No search entries should match {second_query}"
|
"No search entries should match {second_query}"
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -990,20 +981,19 @@ async fn test_search_preserves_history_items(cx: &mut gpui::TestAppContext) {
|
|||||||
.update_matches(first_query_again.to_string(), cx)
|
.update_matches(first_query_again.to_string(), cx)
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
finder.update(cx, |finder, _| {
|
finder.update(cx, |picker, _| {
|
||||||
let delegate = &finder.delegate;
|
let matches = collect_search_matches(picker);
|
||||||
assert_eq!(delegate.matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
|
assert_eq!(matches.history.len(), 1, "Only one history item contains {first_query_again}, it should be present and others should be filtered out, even after non-matching query");
|
||||||
let history_match = delegate.matches.history.first().unwrap();
|
let history_match = matches.history_found_paths.first().expect("Should have path matches for history items after querying");
|
||||||
assert!(history_match.1.is_some(), "Should have path matches for history items after querying");
|
assert_eq!(history_match, &FoundPath::new(
|
||||||
assert_eq!(history_match.0, FoundPath::new(
|
|
||||||
ProjectPath {
|
ProjectPath {
|
||||||
worktree_id,
|
worktree_id,
|
||||||
path: Arc::from(Path::new("test/first.rs")),
|
path: Arc::from(Path::new("test/first.rs")),
|
||||||
},
|
},
|
||||||
Some(PathBuf::from("/src/test/first.rs"))
|
Some(PathBuf::from("/src/test/first.rs"))
|
||||||
));
|
));
|
||||||
assert_eq!(delegate.matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
|
assert_eq!(matches.search.len(), 1, "Only one non-history item contains {first_query_again}, it should be present, even after non-matching query");
|
||||||
assert_eq!(delegate.matches.search.first().unwrap().0.path.as_ref(), Path::new("test/fourth.rs"));
|
assert_eq!(matches.search.first().unwrap(), Path::new("test/fourth.rs"));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1139,6 +1129,9 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
|||||||
assert_eq!(finder.delegate.matches.len(), 5);
|
assert_eq!(finder.delegate.matches.len(), 5);
|
||||||
assert_match_at_position(finder, 0, "main.rs");
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
assert_match_selection(finder, 1, "bar.rs");
|
assert_match_selection(finder, 1, "bar.rs");
|
||||||
|
assert_match_at_position(finder, 2, "lib.rs");
|
||||||
|
assert_match_at_position(finder, 3, "moo.rs");
|
||||||
|
assert_match_at_position(finder, 4, "maaa.rs");
|
||||||
});
|
});
|
||||||
|
|
||||||
// main.rs is not among matches, select top item
|
// main.rs is not among matches, select top item
|
||||||
@ -1150,6 +1143,7 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
|||||||
picker.update(cx, |finder, _| {
|
picker.update(cx, |finder, _| {
|
||||||
assert_eq!(finder.delegate.matches.len(), 2);
|
assert_eq!(finder.delegate.matches.len(), 2);
|
||||||
assert_match_at_position(finder, 0, "bar.rs");
|
assert_match_at_position(finder, 0, "bar.rs");
|
||||||
|
assert_match_at_position(finder, 1, "lib.rs");
|
||||||
});
|
});
|
||||||
|
|
||||||
// main.rs is back, put it on top and select next item
|
// main.rs is back, put it on top and select next item
|
||||||
@ -1162,6 +1156,7 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
|||||||
assert_eq!(finder.delegate.matches.len(), 3);
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
assert_match_at_position(finder, 0, "main.rs");
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
assert_match_selection(finder, 1, "moo.rs");
|
assert_match_selection(finder, 1, "moo.rs");
|
||||||
|
assert_match_at_position(finder, 2, "maaa.rs");
|
||||||
});
|
});
|
||||||
|
|
||||||
// get back to the initial state
|
// get back to the initial state
|
||||||
@ -1174,6 +1169,99 @@ async fn test_keep_opened_file_on_top_of_search_results_and_select_next_one(
|
|||||||
assert_eq!(finder.delegate.matches.len(), 3);
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
assert_match_selection(finder, 0, "main.rs");
|
assert_match_selection(finder, 0, "main.rs");
|
||||||
assert_match_at_position(finder, 1, "lib.rs");
|
assert_match_at_position(finder, 1, "lib.rs");
|
||||||
|
assert_match_at_position(finder, 2, "bar.rs");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[gpui::test]
|
||||||
|
async fn test_non_separate_history_items(cx: &mut TestAppContext) {
|
||||||
|
let app_state = init_test(cx);
|
||||||
|
|
||||||
|
app_state
|
||||||
|
.fs
|
||||||
|
.as_fake()
|
||||||
|
.insert_tree(
|
||||||
|
"/src",
|
||||||
|
json!({
|
||||||
|
"test": {
|
||||||
|
"bar.rs": "// Bar file",
|
||||||
|
"lib.rs": "// Lib file",
|
||||||
|
"maaa.rs": "// Maaaaaaa",
|
||||||
|
"main.rs": "// Main file",
|
||||||
|
"moo.rs": "// Moooooo",
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
let project = Project::test(app_state.fs.clone(), ["/src".as_ref()], cx).await;
|
||||||
|
let (workspace, cx) = cx.add_window_view(|cx| Workspace::test_new(project, cx));
|
||||||
|
|
||||||
|
open_close_queried_buffer("bar", 1, "bar.rs", &workspace, cx).await;
|
||||||
|
open_close_queried_buffer("lib", 1, "lib.rs", &workspace, cx).await;
|
||||||
|
open_queried_buffer("main", 1, "main.rs", &workspace, cx).await;
|
||||||
|
|
||||||
|
cx.dispatch_action(Toggle::default());
|
||||||
|
let picker = active_file_picker(&workspace, cx);
|
||||||
|
// main.rs is on top, previously used is selected
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_selection(finder, 0, "main.rs");
|
||||||
|
assert_match_at_position(finder, 1, "lib.rs");
|
||||||
|
assert_match_at_position(finder, 2, "bar.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// all files match, main.rs is still on top, but the second item is selected
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches(".rs".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 5);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "moo.rs");
|
||||||
|
assert_match_at_position(finder, 2, "bar.rs");
|
||||||
|
assert_match_at_position(finder, 3, "lib.rs");
|
||||||
|
assert_match_at_position(finder, 4, "maaa.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// main.rs is not among matches, select top item
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("b".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 2);
|
||||||
|
assert_match_at_position(finder, 0, "bar.rs");
|
||||||
|
assert_match_at_position(finder, 1, "lib.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// main.rs is back, put it on top and select next item
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("m".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_at_position(finder, 0, "main.rs");
|
||||||
|
assert_match_selection(finder, 1, "moo.rs");
|
||||||
|
assert_match_at_position(finder, 2, "maaa.rs");
|
||||||
|
});
|
||||||
|
|
||||||
|
// get back to the initial state
|
||||||
|
picker
|
||||||
|
.update(cx, |finder, cx| {
|
||||||
|
finder.delegate.update_matches("".to_string(), cx)
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
picker.update(cx, |finder, _| {
|
||||||
|
assert_eq!(finder.delegate.matches.len(), 3);
|
||||||
|
assert_match_selection(finder, 0, "main.rs");
|
||||||
|
assert_match_at_position(finder, 1, "lib.rs");
|
||||||
|
assert_match_at_position(finder, 2, "bar.rs");
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1266,19 +1354,8 @@ async fn test_history_items_vs_very_good_external_match(cx: &mut gpui::TestAppCo
|
|||||||
let finder = open_file_picker(&workspace, cx);
|
let finder = open_file_picker(&workspace, cx);
|
||||||
let query = "collab_ui";
|
let query = "collab_ui";
|
||||||
cx.simulate_input(query);
|
cx.simulate_input(query);
|
||||||
finder.update(cx, |finder, _| {
|
finder.update(cx, |picker, _| {
|
||||||
let delegate = &finder.delegate;
|
let search_entries = collect_search_matches(picker).search_paths_only();
|
||||||
assert!(
|
|
||||||
delegate.matches.history.is_empty(),
|
|
||||||
"History items should not math query {query}, they should be matched by name only"
|
|
||||||
);
|
|
||||||
|
|
||||||
let search_entries = delegate
|
|
||||||
.matches
|
|
||||||
.search
|
|
||||||
.iter()
|
|
||||||
.map(|path_match| path_match.0.path.to_path_buf())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
search_entries,
|
search_entries,
|
||||||
vec![
|
vec![
|
||||||
@ -1321,15 +1398,9 @@ async fn test_nonexistent_history_items_not_shown(cx: &mut gpui::TestAppContext)
|
|||||||
let picker = open_file_picker(&workspace, cx);
|
let picker = open_file_picker(&workspace, cx);
|
||||||
cx.simulate_input("rs");
|
cx.simulate_input("rs");
|
||||||
|
|
||||||
picker.update(cx, |finder, _| {
|
picker.update(cx, |picker, _| {
|
||||||
let history_entries = finder.delegate
|
|
||||||
.matches
|
|
||||||
.history
|
|
||||||
.iter()
|
|
||||||
.map(|(_, path_match)| path_match.as_ref().expect("should have a path match").0.path.to_path_buf())
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
history_entries,
|
collect_search_matches(picker).history,
|
||||||
vec![
|
vec![
|
||||||
PathBuf::from("test/first.rs"),
|
PathBuf::from("test/first.rs"),
|
||||||
PathBuf::from("test/third.rs"),
|
PathBuf::from("test/third.rs"),
|
||||||
@ -1582,7 +1653,7 @@ async fn test_switches_between_release_norelease_modes_on_forward_nav(
|
|||||||
// Back to navigation with initial shortcut
|
// Back to navigation with initial shortcut
|
||||||
// Open file on modifiers release
|
// Open file on modifiers release
|
||||||
cx.simulate_modifiers_change(Modifiers::secondary_key());
|
cx.simulate_modifiers_change(Modifiers::secondary_key());
|
||||||
cx.dispatch_action(Toggle);
|
cx.dispatch_action(Toggle::default());
|
||||||
cx.simulate_modifiers_change(Modifiers::none());
|
cx.simulate_modifiers_change(Modifiers::none());
|
||||||
cx.read(|cx| {
|
cx.read(|cx| {
|
||||||
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
let active_editor = workspace.read(cx).active_item_as::<Editor>(cx).unwrap();
|
||||||
@ -1776,7 +1847,9 @@ fn open_file_picker(
|
|||||||
workspace: &View<Workspace>,
|
workspace: &View<Workspace>,
|
||||||
cx: &mut VisualTestContext,
|
cx: &mut VisualTestContext,
|
||||||
) -> View<Picker<FileFinderDelegate>> {
|
) -> View<Picker<FileFinderDelegate>> {
|
||||||
cx.dispatch_action(Toggle);
|
cx.dispatch_action(Toggle {
|
||||||
|
separate_history: true,
|
||||||
|
});
|
||||||
active_file_picker(workspace, cx)
|
active_file_picker(workspace, cx)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1795,15 +1868,17 @@ fn active_file_picker(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
struct SearchEntries {
|
struct SearchEntries {
|
||||||
history: Vec<PathBuf>,
|
history: Vec<PathBuf>,
|
||||||
|
history_found_paths: Vec<FoundPath>,
|
||||||
search: Vec<PathBuf>,
|
search: Vec<PathBuf>,
|
||||||
|
search_matches: Vec<PathMatch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SearchEntries {
|
impl SearchEntries {
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
fn search_only(self) -> Vec<PathBuf> {
|
fn search_paths_only(self) -> Vec<PathBuf> {
|
||||||
assert!(
|
assert!(
|
||||||
self.history.is_empty(),
|
self.history.is_empty(),
|
||||||
"Should have no history matches, but got: {:?}",
|
"Should have no history matches, but got: {:?}",
|
||||||
@ -1811,35 +1886,50 @@ impl SearchEntries {
|
|||||||
);
|
);
|
||||||
self.search
|
self.search
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[track_caller]
|
||||||
|
fn search_matches_only(self) -> Vec<PathMatch> {
|
||||||
|
assert!(
|
||||||
|
self.history.is_empty(),
|
||||||
|
"Should have no history matches, but got: {:?}",
|
||||||
|
self.history
|
||||||
|
);
|
||||||
|
self.search_matches
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries {
|
fn collect_search_matches(picker: &Picker<FileFinderDelegate>) -> SearchEntries {
|
||||||
let matches = &picker.delegate.matches;
|
let mut search_entries = SearchEntries::default();
|
||||||
SearchEntries {
|
for m in &picker.delegate.matches.matches {
|
||||||
history: matches
|
match m {
|
||||||
.history
|
Match::History(history_path, path_match) => {
|
||||||
.iter()
|
search_entries.history.push(
|
||||||
.map(|(history_path, path_match)| {
|
path_match
|
||||||
path_match
|
.as_ref()
|
||||||
.as_ref()
|
.map(|path_match| {
|
||||||
.map(|path_match| {
|
Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path)
|
||||||
Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path)
|
})
|
||||||
})
|
.unwrap_or_else(|| {
|
||||||
.unwrap_or_else(|| {
|
history_path
|
||||||
history_path
|
.absolute
|
||||||
.absolute
|
.as_deref()
|
||||||
.as_deref()
|
.unwrap_or_else(|| &history_path.project.path)
|
||||||
.unwrap_or_else(|| &history_path.project.path)
|
.to_path_buf()
|
||||||
.to_path_buf()
|
}),
|
||||||
})
|
);
|
||||||
})
|
search_entries
|
||||||
.collect(),
|
.history_found_paths
|
||||||
search: matches
|
.push(history_path.clone());
|
||||||
.search
|
}
|
||||||
.iter()
|
Match::Search(path_match) => {
|
||||||
.map(|path_match| Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path))
|
search_entries
|
||||||
.collect(),
|
.search
|
||||||
|
.push(Path::new(path_match.0.path_prefix.as_ref()).join(&path_match.0.path));
|
||||||
|
search_entries.search_matches.push(path_match.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
search_entries
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
|
@ -42,13 +42,9 @@ const MIN_INPUT_WIDTH_REMS: f32 = 10.;
|
|||||||
const MAX_INPUT_WIDTH_REMS: f32 = 30.;
|
const MAX_INPUT_WIDTH_REMS: f32 = 30.;
|
||||||
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
|
const MAX_BUFFER_SEARCH_HISTORY_SIZE: usize = 50;
|
||||||
|
|
||||||
const fn true_value() -> bool {
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(PartialEq, Clone, Deserialize)]
|
#[derive(PartialEq, Clone, Deserialize)]
|
||||||
pub struct Deploy {
|
pub struct Deploy {
|
||||||
#[serde(default = "true_value")]
|
#[serde(default = "util::serde::default_true")]
|
||||||
pub focus: bool,
|
pub focus: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub replace_enabled: bool,
|
pub replace_enabled: bool,
|
||||||
|
@ -138,7 +138,7 @@ pub fn app_menus() -> Vec<Menu<'static>> {
|
|||||||
MenuItem::separator(),
|
MenuItem::separator(),
|
||||||
MenuItem::action("Command Palette...", command_palette::Toggle),
|
MenuItem::action("Command Palette...", command_palette::Toggle),
|
||||||
MenuItem::separator(),
|
MenuItem::separator(),
|
||||||
MenuItem::action("Go to File...", file_finder::Toggle),
|
MenuItem::action("Go to File...", file_finder::Toggle::default()),
|
||||||
// MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
|
// MenuItem::action("Go to Symbol in Project", project_symbols::Toggle),
|
||||||
MenuItem::action("Go to Symbol in Editor...", outline::Toggle),
|
MenuItem::action("Go to Symbol in Editor...", outline::Toggle),
|
||||||
MenuItem::action("Go to Line/Column...", go_to_line::Toggle),
|
MenuItem::action("Go to Line/Column...", go_to_line::Toggle),
|
||||||
|
Loading…
Reference in New Issue
Block a user