Shorten overflowing paths in file finder (#25303)
Closes #7711 This PR changes the file finder to shorten the path portion of each match by replacing a segment with `...`, if it would otherwise overflow horizontally. Details: - The overflow calculation is based on a crude linear width estimate for ASCII text at the current em width. No elision is done for non-ASCII paths. - A path component will not be elided if it contains a matching position for the file finder's search, or if it's the first or last component. - Elision is only applied when it is successful in shortening the path enough to not overflow. Release Notes: - Improved the appearance of the file finder when long paths are shown by eliding path segments
This commit is contained in:
parent
7ff40091d8
commit
aba89ba12a
2 changed files with 397 additions and 175 deletions
|
@ -24,8 +24,10 @@ use picker::{Picker, PickerDelegate};
|
||||||
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
use project::{PathMatchCandidateSet, Project, ProjectPath, WorktreeId};
|
||||||
use settings::Settings;
|
use settings::Settings;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
cmp,
|
cmp,
|
||||||
path::{Path, PathBuf},
|
ops::Range,
|
||||||
|
path::{Component, Path, PathBuf},
|
||||||
sync::{
|
sync::{
|
||||||
atomic::{self, AtomicBool},
|
atomic::{self, AtomicBool},
|
||||||
Arc,
|
Arc,
|
||||||
|
@ -36,7 +38,7 @@ use ui::{
|
||||||
prelude::*, ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu,
|
prelude::*, ContextMenu, HighlightedLabel, ListItem, ListItemSpacing, PopoverMenu,
|
||||||
PopoverMenuHandle,
|
PopoverMenuHandle,
|
||||||
};
|
};
|
||||||
use util::{paths::PathWithPosition, post_inc, ResultExt};
|
use util::{maybe, paths::PathWithPosition, post_inc, ResultExt};
|
||||||
use workspace::{
|
use workspace::{
|
||||||
item::PreviewTabsSettings, notifications::NotifyResultExt, pane, ModalView, SplitDirection,
|
item::PreviewTabsSettings, notifications::NotifyResultExt, pane, ModalView, SplitDirection,
|
||||||
Workspace,
|
Workspace,
|
||||||
|
@ -805,10 +807,12 @@ impl FileFinderDelegate {
|
||||||
fn labels_for_match(
|
fn labels_for_match(
|
||||||
&self,
|
&self,
|
||||||
path_match: &Match,
|
path_match: &Match,
|
||||||
|
window: &mut Window,
|
||||||
cx: &App,
|
cx: &App,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
) -> (String, Vec<usize>, String, Vec<usize>) {
|
) -> (HighlightedLabel, HighlightedLabel) {
|
||||||
let (file_name, file_name_positions, full_path, full_path_positions) = match &path_match {
|
let (file_name, file_name_positions, mut full_path, mut full_path_positions) =
|
||||||
|
match &path_match {
|
||||||
Match::History {
|
Match::History {
|
||||||
path: entry_path,
|
path: entry_path,
|
||||||
panel_match,
|
panel_match,
|
||||||
|
@ -821,9 +825,10 @@ impl FileFinderDelegate {
|
||||||
.worktree_for_id(worktree_id, cx)
|
.worktree_for_id(worktree_id, cx)
|
||||||
.is_some();
|
.is_some();
|
||||||
|
|
||||||
if !has_worktree {
|
if let Some(absolute_path) =
|
||||||
if let Some(absolute_path) = &entry_path.absolute {
|
entry_path.absolute.as_ref().filter(|_| !has_worktree)
|
||||||
return (
|
{
|
||||||
|
(
|
||||||
absolute_path
|
absolute_path
|
||||||
.file_name()
|
.file_name()
|
||||||
.map_or_else(
|
.map_or_else(
|
||||||
|
@ -834,10 +839,8 @@ impl FileFinderDelegate {
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
absolute_path.to_string_lossy().to_string(),
|
absolute_path.to_string_lossy().to_string(),
|
||||||
Vec::new(),
|
Vec::new(),
|
||||||
);
|
)
|
||||||
}
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
let mut path = Arc::clone(project_relative_path);
|
let mut path = Arc::clone(project_relative_path);
|
||||||
if project_relative_path.as_ref() == Path::new("") {
|
if project_relative_path.as_ref() == Path::new("") {
|
||||||
if let Some(absolute_path) = &entry_path.absolute {
|
if let Some(absolute_path) = &entry_path.absolute {
|
||||||
|
@ -862,6 +865,7 @@ impl FileFinderDelegate {
|
||||||
|
|
||||||
self.labels_for_path_match(&path_match)
|
self.labels_for_path_match(&path_match)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
Match::Search(path_match) => self.labels_for_path_match(&path_match.0),
|
Match::Search(path_match) => self.labels_for_path_match(&path_match.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -870,22 +874,67 @@ impl FileFinderDelegate {
|
||||||
let user_home_path = user_home_path.trim();
|
let user_home_path = user_home_path.trim();
|
||||||
if !user_home_path.is_empty() {
|
if !user_home_path.is_empty() {
|
||||||
if (&full_path).starts_with(user_home_path) {
|
if (&full_path).starts_with(user_home_path) {
|
||||||
return (
|
full_path.replace_range(0..user_home_path.len(), "~");
|
||||||
file_name,
|
full_path_positions.retain_mut(|pos| {
|
||||||
file_name_positions,
|
if *pos >= user_home_path.len() {
|
||||||
full_path.replace(user_home_path, "~"),
|
*pos -= user_home_path.len();
|
||||||
full_path_positions,
|
*pos += 1;
|
||||||
);
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if full_path.is_ascii() {
|
||||||
|
let file_finder_settings = FileFinderSettings::get_global(cx);
|
||||||
|
let max_width =
|
||||||
|
FileFinder::modal_max_width(file_finder_settings.modal_max_width, window);
|
||||||
|
let (normal_em, small_em) = {
|
||||||
|
let style = window.text_style();
|
||||||
|
let font_id = window.text_system().resolve_font(&style.font());
|
||||||
|
let font_size = TextSize::Default.rems(cx).to_pixels(window.rem_size());
|
||||||
|
let normal = cx
|
||||||
|
.text_system()
|
||||||
|
.em_width(font_id, font_size)
|
||||||
|
.unwrap_or(px(16.));
|
||||||
|
let font_size = TextSize::Small.rems(cx).to_pixels(window.rem_size());
|
||||||
|
let small = cx
|
||||||
|
.text_system()
|
||||||
|
.em_width(font_id, font_size)
|
||||||
|
.unwrap_or(px(10.));
|
||||||
|
(normal, small)
|
||||||
|
};
|
||||||
|
let budget = full_path_budget(&file_name, normal_em, small_em, max_width);
|
||||||
|
if full_path.len() > budget {
|
||||||
|
let components = PathComponentSlice::new(&full_path);
|
||||||
|
if let Some(elided_range) =
|
||||||
|
components.elision_range(budget - 1, &full_path_positions)
|
||||||
|
{
|
||||||
|
let elided_len = elided_range.end - elided_range.start;
|
||||||
|
let placeholder = "…";
|
||||||
|
full_path_positions.retain_mut(|mat| {
|
||||||
|
if *mat >= elided_range.end {
|
||||||
|
*mat -= elided_len;
|
||||||
|
*mat += placeholder.len();
|
||||||
|
} else if *mat >= elided_range.start {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
true
|
||||||
|
});
|
||||||
|
full_path.replace_range(elided_range, placeholder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
(
|
(
|
||||||
file_name,
|
HighlightedLabel::new(file_name, file_name_positions),
|
||||||
file_name_positions,
|
HighlightedLabel::new(full_path, full_path_positions)
|
||||||
full_path,
|
.size(LabelSize::Small)
|
||||||
full_path_positions,
|
.color(Color::Muted),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1004,6 +1053,15 @@ impl FileFinderDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn full_path_budget(
|
||||||
|
file_name: &str,
|
||||||
|
normal_em: Pixels,
|
||||||
|
small_em: Pixels,
|
||||||
|
max_width: Pixels,
|
||||||
|
) -> usize {
|
||||||
|
((px(max_width / px(0.8)) - px(file_name.len() as f32) * normal_em) / small_em) as usize
|
||||||
|
}
|
||||||
|
|
||||||
impl PickerDelegate for FileFinderDelegate {
|
impl PickerDelegate for FileFinderDelegate {
|
||||||
type ListItem = ListItem;
|
type ListItem = ListItem;
|
||||||
|
|
||||||
|
@ -1249,7 +1307,7 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
&self,
|
&self,
|
||||||
ix: usize,
|
ix: usize,
|
||||||
selected: bool,
|
selected: bool,
|
||||||
_: &mut Window,
|
window: &mut Window,
|
||||||
cx: &mut Context<Picker<Self>>,
|
cx: &mut Context<Picker<Self>>,
|
||||||
) -> Option<Self::ListItem> {
|
) -> Option<Self::ListItem> {
|
||||||
let settings = FileFinderSettings::get_global(cx);
|
let settings = FileFinderSettings::get_global(cx);
|
||||||
|
@ -1269,16 +1327,16 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
.size(IconSize::Small.rems())
|
.size(IconSize::Small.rems())
|
||||||
.into_any_element(),
|
.into_any_element(),
|
||||||
};
|
};
|
||||||
let (file_name, file_name_positions, full_path, full_path_positions) =
|
let (file_name_label, full_path_label) = self.labels_for_match(path_match, window, cx, ix);
|
||||||
self.labels_for_match(path_match, cx, ix);
|
|
||||||
|
|
||||||
let file_icon = if settings.file_icons {
|
let file_icon = maybe!({
|
||||||
FileIcons::get_icon(Path::new(&file_name), cx)
|
if !settings.file_icons {
|
||||||
.map(Icon::from_path)
|
return None;
|
||||||
.map(|icon| icon.color(Color::Muted))
|
}
|
||||||
} else {
|
let file_name = path_match.path().file_name()?;
|
||||||
None
|
let icon = FileIcons::get_icon(file_name.as_ref(), cx)?;
|
||||||
};
|
Some(Icon::from_path(icon).color(Color::Muted))
|
||||||
|
});
|
||||||
|
|
||||||
Some(
|
Some(
|
||||||
ListItem::new(ix)
|
ListItem::new(ix)
|
||||||
|
@ -1291,12 +1349,8 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
h_flex()
|
h_flex()
|
||||||
.gap_2()
|
.gap_2()
|
||||||
.py_px()
|
.py_px()
|
||||||
.child(HighlightedLabel::new(file_name, file_name_positions))
|
.child(file_name_label)
|
||||||
.child(
|
.child(full_path_label),
|
||||||
HighlightedLabel::new(full_path, full_path_positions)
|
|
||||||
.size(LabelSize::Small)
|
|
||||||
.color(Color::Muted),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1345,110 +1399,120 @@ impl PickerDelegate for FileFinderDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
mod tests {
|
struct PathComponentSlice<'a> {
|
||||||
use super::*;
|
path: Cow<'a, Path>,
|
||||||
|
path_str: Cow<'a, str>,
|
||||||
|
component_ranges: Vec<(Component<'a>, Range<usize>)>,
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
impl<'a> PathComponentSlice<'a> {
|
||||||
fn test_custom_project_search_ordering_in_file_finder() {
|
fn new(path: &'a str) -> Self {
|
||||||
let mut file_finder_sorted_output = vec![
|
let trimmed_path = Path::new(path).components().as_path().as_os_str();
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
let mut component_ranges = Vec::new();
|
||||||
score: 0.5,
|
let mut components = Path::new(trimmed_path).components();
|
||||||
positions: Vec::new(),
|
let len = trimmed_path.as_encoded_bytes().len();
|
||||||
worktree_id: 0,
|
let mut pos = 0;
|
||||||
path: Arc::from(Path::new("b0.5")),
|
while let Some(component) = components.next() {
|
||||||
path_prefix: Arc::default(),
|
component_ranges.push((component, pos..0));
|
||||||
distance_to_relative_ancestor: 0,
|
pos = len - components.as_path().as_os_str().as_encoded_bytes().len();
|
||||||
is_dir: false,
|
}
|
||||||
}),
|
for ((_, range), ancestor) in component_ranges
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
.iter_mut()
|
||||||
score: 1.0,
|
.rev()
|
||||||
positions: Vec::new(),
|
.zip(Path::new(trimmed_path).ancestors())
|
||||||
worktree_id: 0,
|
{
|
||||||
path: Arc::from(Path::new("c1.0")),
|
range.end = ancestor.as_os_str().as_encoded_bytes().len();
|
||||||
path_prefix: Arc::default(),
|
}
|
||||||
distance_to_relative_ancestor: 0,
|
Self {
|
||||||
is_dir: false,
|
path: Cow::Borrowed(Path::new(path)),
|
||||||
}),
|
path_str: Cow::Borrowed(path),
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
component_ranges,
|
||||||
score: 1.0,
|
}
|
||||||
positions: Vec::new(),
|
}
|
||||||
worktree_id: 0,
|
|
||||||
path: Arc::from(Path::new("a1.0")),
|
fn elision_range(&self, budget: usize, matches: &[usize]) -> Option<Range<usize>> {
|
||||||
path_prefix: Arc::default(),
|
let eligible_range = {
|
||||||
distance_to_relative_ancestor: 0,
|
assert!(matches.windows(2).all(|w| w[0] <= w[1]));
|
||||||
is_dir: false,
|
let mut matches = matches.iter().copied().peekable();
|
||||||
}),
|
let mut longest: Option<Range<usize>> = None;
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
let mut cur = 0..0;
|
||||||
score: 0.5,
|
let mut seen_normal = false;
|
||||||
positions: Vec::new(),
|
for (i, (component, range)) in self.component_ranges.iter().enumerate() {
|
||||||
worktree_id: 0,
|
let is_normal = matches!(component, Component::Normal(_));
|
||||||
path: Arc::from(Path::new("a0.5")),
|
let is_first_normal = is_normal && !seen_normal;
|
||||||
path_prefix: Arc::default(),
|
seen_normal |= is_normal;
|
||||||
distance_to_relative_ancestor: 0,
|
let is_last = i == self.component_ranges.len() - 1;
|
||||||
is_dir: false,
|
let contains_match = matches.peek().is_some_and(|mat| range.contains(mat));
|
||||||
}),
|
if contains_match {
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
matches.next();
|
||||||
score: 1.0,
|
}
|
||||||
positions: Vec::new(),
|
if is_first_normal || is_last || !is_normal || contains_match {
|
||||||
worktree_id: 0,
|
if !longest
|
||||||
path: Arc::from(Path::new("b1.0")),
|
.as_ref()
|
||||||
path_prefix: Arc::default(),
|
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||||
distance_to_relative_ancestor: 0,
|
{
|
||||||
is_dir: false,
|
longest = Some(cur);
|
||||||
}),
|
}
|
||||||
];
|
cur = i + 1..i + 1;
|
||||||
file_finder_sorted_output.sort_by(|a, b| b.cmp(a));
|
} else {
|
||||||
|
cur.end = i + 1;
|
||||||
assert_eq!(
|
}
|
||||||
file_finder_sorted_output,
|
}
|
||||||
vec![
|
if !longest
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
.as_ref()
|
||||||
score: 1.0,
|
.is_some_and(|old| old.end - old.start > cur.end - cur.start)
|
||||||
positions: Vec::new(),
|
{
|
||||||
worktree_id: 0,
|
longest = Some(cur);
|
||||||
path: Arc::from(Path::new("a1.0")),
|
}
|
||||||
path_prefix: Arc::default(),
|
longest
|
||||||
distance_to_relative_ancestor: 0,
|
};
|
||||||
is_dir: false,
|
|
||||||
}),
|
let eligible_range = eligible_range?;
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
assert!(eligible_range.start <= eligible_range.end);
|
||||||
score: 1.0,
|
if eligible_range.is_empty() {
|
||||||
positions: Vec::new(),
|
return None;
|
||||||
worktree_id: 0,
|
}
|
||||||
path: Arc::from(Path::new("b1.0")),
|
|
||||||
path_prefix: Arc::default(),
|
let elided_range: Range<usize> = {
|
||||||
distance_to_relative_ancestor: 0,
|
let byte_range = self.component_ranges[eligible_range.start].1.start
|
||||||
is_dir: false,
|
..self.component_ranges[eligible_range.end - 1].1.end;
|
||||||
}),
|
let midpoint = self.path_str.len() / 2;
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
let distance_from_start = byte_range.start.abs_diff(midpoint);
|
||||||
score: 1.0,
|
let distance_from_end = byte_range.end.abs_diff(midpoint);
|
||||||
positions: Vec::new(),
|
let pick_from_end = distance_from_start > distance_from_end;
|
||||||
worktree_id: 0,
|
let mut len_with_elision = self.path_str.len();
|
||||||
path: Arc::from(Path::new("c1.0")),
|
let mut i = eligible_range.start;
|
||||||
path_prefix: Arc::default(),
|
while i < eligible_range.end {
|
||||||
distance_to_relative_ancestor: 0,
|
let x = if pick_from_end {
|
||||||
is_dir: false,
|
eligible_range.end - i + eligible_range.start - 1
|
||||||
}),
|
} else {
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
i
|
||||||
score: 0.5,
|
};
|
||||||
positions: Vec::new(),
|
len_with_elision -= self.component_ranges[x]
|
||||||
worktree_id: 0,
|
.0
|
||||||
path: Arc::from(Path::new("a0.5")),
|
.as_os_str()
|
||||||
path_prefix: Arc::default(),
|
.as_encoded_bytes()
|
||||||
distance_to_relative_ancestor: 0,
|
.len()
|
||||||
is_dir: false,
|
+ 1;
|
||||||
}),
|
if len_with_elision <= budget {
|
||||||
ProjectPanelOrdMatch(PathMatch {
|
break;
|
||||||
score: 0.5,
|
}
|
||||||
positions: Vec::new(),
|
i += 1;
|
||||||
worktree_id: 0,
|
}
|
||||||
path: Arc::from(Path::new("b0.5")),
|
if len_with_elision > budget {
|
||||||
path_prefix: Arc::default(),
|
return None;
|
||||||
distance_to_relative_ancestor: 0,
|
} else if pick_from_end {
|
||||||
is_dir: false,
|
let x = eligible_range.end - i + eligible_range.start - 1;
|
||||||
}),
|
x..eligible_range.end
|
||||||
]
|
} else {
|
||||||
);
|
let x = i;
|
||||||
|
eligible_range.start..x + 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let byte_range = self.component_ranges[elided_range.start].1.start
|
||||||
|
..self.component_ranges[elided_range.end - 1].1.end;
|
||||||
|
Some(byte_range)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,164 @@ fn init_logger() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_path_elision() {
|
||||||
|
#[track_caller]
|
||||||
|
fn check(path: &str, budget: usize, matches: impl IntoIterator<Item = usize>, expected: &str) {
|
||||||
|
let mut path = path.to_owned();
|
||||||
|
let slice = PathComponentSlice::new(&path);
|
||||||
|
let matches = Vec::from_iter(matches);
|
||||||
|
if let Some(range) = slice.elision_range(budget - 1, &matches) {
|
||||||
|
path.replace_range(range, "…");
|
||||||
|
}
|
||||||
|
assert_eq!(path, expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simple cases, mostly to check that different path shapes are handled gracefully.
|
||||||
|
check("p/a/b/c/d/", 6, [], "p/…/d/");
|
||||||
|
check("p/a/b/c/d/", 1, [2, 4, 6], "p/a/b/c/d/");
|
||||||
|
check("p/a/b/c/d/", 10, [2, 6], "p/a/…/c/d/");
|
||||||
|
check("p/a/b/c/d/", 8, [6], "p/…/c/d/");
|
||||||
|
|
||||||
|
check("p/a/b/c/d", 5, [], "p/…/d");
|
||||||
|
check("p/a/b/c/d", 9, [2, 4, 6], "p/a/b/c/d");
|
||||||
|
check("p/a/b/c/d", 9, [2, 6], "p/a/…/c/d");
|
||||||
|
check("p/a/b/c/d", 7, [6], "p/…/c/d");
|
||||||
|
|
||||||
|
check("/p/a/b/c/d/", 7, [], "/p/…/d/");
|
||||||
|
check("/p/a/b/c/d/", 11, [3, 5, 7], "/p/a/b/c/d/");
|
||||||
|
check("/p/a/b/c/d/", 11, [3, 7], "/p/a/…/c/d/");
|
||||||
|
check("/p/a/b/c/d/", 9, [7], "/p/…/c/d/");
|
||||||
|
|
||||||
|
// If the budget can't be met, no elision is done.
|
||||||
|
check(
|
||||||
|
"project/dir/child/grandchild",
|
||||||
|
5,
|
||||||
|
[],
|
||||||
|
"project/dir/child/grandchild",
|
||||||
|
);
|
||||||
|
|
||||||
|
// The longest unmatched segment is picked for elision.
|
||||||
|
check(
|
||||||
|
"project/one/two/X/three/sub",
|
||||||
|
21,
|
||||||
|
[16],
|
||||||
|
"project/…/X/three/sub",
|
||||||
|
);
|
||||||
|
|
||||||
|
// Elision stops when the budget is met, even though there are more components in the chosen segment.
|
||||||
|
// It proceeds from the end of the unmatched segment that is closer to the midpoint of the path.
|
||||||
|
check(
|
||||||
|
"project/one/two/three/X/sub",
|
||||||
|
21,
|
||||||
|
[22],
|
||||||
|
"project/…/three/X/sub",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_custom_project_search_ordering_in_file_finder() {
|
||||||
|
let mut file_finder_sorted_output = vec![
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 0.5,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("b0.5")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("c1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("a1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 0.5,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("a0.5")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("b1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
];
|
||||||
|
file_finder_sorted_output.sort_by(|a, b| b.cmp(a));
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
file_finder_sorted_output,
|
||||||
|
vec![
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("a1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("b1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 1.0,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("c1.0")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 0.5,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("a0.5")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
ProjectPanelOrdMatch(PathMatch {
|
||||||
|
score: 0.5,
|
||||||
|
positions: Vec::new(),
|
||||||
|
worktree_id: 0,
|
||||||
|
path: Arc::from(Path::new("b0.5")),
|
||||||
|
path_prefix: Arc::default(),
|
||||||
|
distance_to_relative_ancestor: 0,
|
||||||
|
is_dir: false,
|
||||||
|
}),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_matching_paths(cx: &mut TestAppContext) {
|
async fn test_matching_paths(cx: &mut TestAppContext) {
|
||||||
let app_state = init_test(cx);
|
let app_state = init_test(cx);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue