Move fuzzy::match_paths to Project::match_paths
This prepares the way to extract the rest of fuzzy to its own crate. Co-Authored-By: Max Brunsfeld <maxbrunsfeld@gmail.com>
This commit is contained in:
co-authored by
Max Brunsfeld
parent
cf270b4dff
commit
706925b781
+38
-35
@@ -1,9 +1,10 @@
|
||||
use crate::{
|
||||
editor::{self, Editor},
|
||||
project::Project,
|
||||
settings::Settings,
|
||||
util,
|
||||
workspace::Workspace,
|
||||
worktree::{match_paths, PathMatch},
|
||||
worktree::PathMatch,
|
||||
};
|
||||
use gpui::{
|
||||
action,
|
||||
@@ -13,8 +14,8 @@ use gpui::{
|
||||
menu::{SelectNext, SelectPrev},
|
||||
Binding,
|
||||
},
|
||||
AppContext, Axis, Entity, MutableAppContext, RenderContext, Task, View, ViewContext,
|
||||
ViewHandle, WeakViewHandle,
|
||||
AppContext, Axis, Entity, ModelHandle, MutableAppContext, RenderContext, Task, View,
|
||||
ViewContext, ViewHandle, WeakViewHandle,
|
||||
};
|
||||
use postage::watch;
|
||||
use std::{
|
||||
@@ -29,7 +30,7 @@ use std::{
|
||||
pub struct FileFinder {
|
||||
handle: WeakViewHandle<Self>,
|
||||
settings: watch::Receiver<Settings>,
|
||||
workspace: WeakViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
query_editor: ViewHandle<Editor>,
|
||||
search_count: usize,
|
||||
latest_search_id: usize,
|
||||
@@ -241,8 +242,8 @@ impl FileFinder {
|
||||
|
||||
fn toggle(workspace: &mut Workspace, _: &Toggle, cx: &mut ViewContext<Workspace>) {
|
||||
workspace.toggle_modal(cx, |cx, workspace| {
|
||||
let handle = cx.handle();
|
||||
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), handle, cx));
|
||||
let project = workspace.project().clone();
|
||||
let finder = cx.add_view(|cx| Self::new(workspace.settings.clone(), project, cx));
|
||||
cx.subscribe(&finder, Self::on_event).detach();
|
||||
finder
|
||||
});
|
||||
@@ -269,10 +270,10 @@ impl FileFinder {
|
||||
|
||||
pub fn new(
|
||||
settings: watch::Receiver<Settings>,
|
||||
workspace: ViewHandle<Workspace>,
|
||||
project: ModelHandle<Project>,
|
||||
cx: &mut ViewContext<Self>,
|
||||
) -> Self {
|
||||
cx.observe(&workspace, Self::workspace_updated).detach();
|
||||
cx.observe(&project, Self::project_updated).detach();
|
||||
|
||||
let query_editor = cx.add_view(|cx| {
|
||||
Editor::single_line(
|
||||
@@ -290,7 +291,7 @@ impl FileFinder {
|
||||
Self {
|
||||
handle: cx.handle().downgrade(),
|
||||
settings,
|
||||
workspace: workspace.downgrade(),
|
||||
project,
|
||||
query_editor,
|
||||
search_count: 0,
|
||||
latest_search_id: 0,
|
||||
@@ -303,7 +304,7 @@ impl FileFinder {
|
||||
}
|
||||
}
|
||||
|
||||
fn workspace_updated(&mut self, _: ViewHandle<Workspace>, cx: &mut ViewContext<Self>) {
|
||||
fn project_updated(&mut self, _: ModelHandle<Project>, cx: &mut ViewContext<Self>) {
|
||||
let query = self.query_editor.update(cx, |buffer, cx| buffer.text(cx));
|
||||
if let Some(task) = self.spawn_search(query, cx) {
|
||||
task.detach();
|
||||
@@ -381,30 +382,17 @@ impl FileFinder {
|
||||
|
||||
#[must_use]
|
||||
fn spawn_search(&mut self, query: String, cx: &mut ViewContext<Self>) -> Option<Task<()>> {
|
||||
let snapshots = self
|
||||
.workspace
|
||||
.upgrade(&cx)?
|
||||
.read(cx)
|
||||
.worktrees(cx)
|
||||
.iter()
|
||||
.map(|tree| tree.read(cx).snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
let search_id = util::post_inc(&mut self.search_count);
|
||||
let background = cx.as_ref().background().clone();
|
||||
self.cancel_flag.store(true, atomic::Ordering::Relaxed);
|
||||
self.cancel_flag = Arc::new(AtomicBool::new(false));
|
||||
let cancel_flag = self.cancel_flag.clone();
|
||||
let project = self.project.clone();
|
||||
Some(cx.spawn(|this, mut cx| async move {
|
||||
let matches = match_paths(
|
||||
&snapshots,
|
||||
&query,
|
||||
false,
|
||||
false,
|
||||
100,
|
||||
cancel_flag.as_ref(),
|
||||
background,
|
||||
)
|
||||
.await;
|
||||
let matches = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project.match_paths(&query, false, false, 100, cancel_flag.as_ref(), cx)
|
||||
})
|
||||
.await;
|
||||
let did_cancel = cancel_flag.load(atomic::Ordering::Relaxed);
|
||||
this.update(&mut cx, |this, cx| {
|
||||
this.update_matches((search_id, did_cancel, query, matches), cx)
|
||||
@@ -536,8 +524,13 @@ mod tests {
|
||||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
app_state.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
let query = "hi".to_string();
|
||||
finder
|
||||
@@ -596,8 +589,13 @@ mod tests {
|
||||
.unwrap();
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
app_state.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Even though there is only one worktree, that worktree's filename
|
||||
// is included in the matching, because the worktree is a single file.
|
||||
@@ -654,8 +652,13 @@ mod tests {
|
||||
cx.read(|cx| workspace.read(cx).worktree_scans_complete(cx))
|
||||
.await;
|
||||
|
||||
let (_, finder) =
|
||||
cx.add_window(|cx| FileFinder::new(app_state.settings.clone(), workspace.clone(), cx));
|
||||
let (_, finder) = cx.add_window(|cx| {
|
||||
FileFinder::new(
|
||||
app_state.settings.clone(),
|
||||
workspace.read(cx).project().clone(),
|
||||
cx,
|
||||
)
|
||||
});
|
||||
|
||||
// Run a search that matches two files with the same relative path.
|
||||
finder
|
||||
|
||||
+4
-112
@@ -19,7 +19,7 @@ const BASE_DISTANCE_PENALTY: f64 = 0.6;
|
||||
const ADDITIONAL_DISTANCE_PENALTY: f64 = 0.05;
|
||||
const MIN_DISTANCE_PENALTY: f64 = 0.2;
|
||||
|
||||
struct Matcher<'a> {
|
||||
pub struct Matcher<'a> {
|
||||
query: &'a [char],
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
@@ -213,116 +213,8 @@ pub async fn match_strings(
|
||||
results
|
||||
}
|
||||
|
||||
pub async fn match_paths(
|
||||
snapshots: &[Snapshot],
|
||||
query: &str,
|
||||
include_ignored: bool,
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
cancel_flag: &AtomicBool,
|
||||
background: Arc<executor::Background>,
|
||||
) -> Vec<PathMatch> {
|
||||
let path_count: usize = if include_ignored {
|
||||
snapshots.iter().map(Snapshot::file_count).sum()
|
||||
} else {
|
||||
snapshots.iter().map(Snapshot::visible_file_count).sum()
|
||||
};
|
||||
if path_count == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
|
||||
let query = query.chars().collect::<Vec<_>>();
|
||||
|
||||
let lowercase_query = &lowercase_query;
|
||||
let query = &query;
|
||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||
|
||||
let num_cpus = background.num_cpus().min(path_count);
|
||||
let segment_size = (path_count + num_cpus - 1) / num_cpus;
|
||||
let mut segment_results = (0..num_cpus)
|
||||
.map(|_| Vec::with_capacity(max_results))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
background
|
||||
.scoped(|scope| {
|
||||
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
|
||||
scope.spawn(async move {
|
||||
let segment_start = segment_idx * segment_size;
|
||||
let segment_end = segment_start + segment_size;
|
||||
let mut matcher = Matcher::new(
|
||||
query,
|
||||
lowercase_query,
|
||||
query_char_bag,
|
||||
smart_case,
|
||||
max_results,
|
||||
);
|
||||
|
||||
let mut tree_start = 0;
|
||||
for snapshot in snapshots {
|
||||
let tree_end = if include_ignored {
|
||||
tree_start + snapshot.file_count()
|
||||
} else {
|
||||
tree_start + snapshot.visible_file_count()
|
||||
};
|
||||
|
||||
if tree_start < segment_end && segment_start < tree_end {
|
||||
let path_prefix: Arc<str> =
|
||||
if snapshot.root_entry().map_or(false, |e| e.is_file()) {
|
||||
snapshot.root_name().into()
|
||||
} else if snapshots.len() > 1 {
|
||||
format!("{}/", snapshot.root_name()).into()
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
|
||||
let start = max(tree_start, segment_start) - tree_start;
|
||||
let end = min(tree_end, segment_end) - tree_start;
|
||||
let paths = snapshot
|
||||
.files(include_ignored, start)
|
||||
.take(end - start)
|
||||
.map(|entry| {
|
||||
if let EntryKind::File(char_bag) = entry.kind {
|
||||
PathMatchCandidate {
|
||||
path: &entry.path,
|
||||
char_bag,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
||||
matcher.match_paths(
|
||||
snapshot.id(),
|
||||
path_prefix,
|
||||
paths,
|
||||
results,
|
||||
&cancel_flag,
|
||||
);
|
||||
}
|
||||
if tree_end >= segment_end {
|
||||
break;
|
||||
}
|
||||
tree_start = tree_end;
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for segment_result in segment_results {
|
||||
if results.is_empty() {
|
||||
results = segment_result;
|
||||
} else {
|
||||
util::extend_sorted(&mut results, segment_result, max_results, |a, b| b.cmp(&a));
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
|
||||
impl<'a> Matcher<'a> {
|
||||
fn new(
|
||||
pub fn new(
|
||||
query: &'a [char],
|
||||
lowercase_query: &'a [char],
|
||||
query_char_bag: CharBag,
|
||||
@@ -343,7 +235,7 @@ impl<'a> Matcher<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn match_strings(
|
||||
pub fn match_strings(
|
||||
&mut self,
|
||||
candidates: &[StringMatchCandidate],
|
||||
results: &mut Vec<StringMatch>,
|
||||
@@ -363,7 +255,7 @@ impl<'a> Matcher<'a> {
|
||||
)
|
||||
}
|
||||
|
||||
fn match_paths(
|
||||
pub fn match_paths(
|
||||
&mut self,
|
||||
tree_id: usize,
|
||||
path_prefix: Arc<str>,
|
||||
|
||||
+243
-6
@@ -1,14 +1,20 @@
|
||||
use crate::{
|
||||
fs::Fs,
|
||||
fuzzy::{CharBag, Matcher, PathMatchCandidate},
|
||||
language::LanguageRegistry,
|
||||
rpc::Client,
|
||||
util::TryFutureExt as _,
|
||||
worktree::{self, Worktree},
|
||||
util::{self, TryFutureExt as _},
|
||||
worktree::{self, EntryKind, PathMatch, Snapshot, Worktree},
|
||||
AppState,
|
||||
};
|
||||
use anyhow::Result;
|
||||
use gpui::{Entity, ModelContext, ModelHandle, Task};
|
||||
use std::{path::Path, sync::Arc};
|
||||
use futures::Future;
|
||||
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||
use std::{
|
||||
cmp,
|
||||
path::Path,
|
||||
sync::{atomic::AtomicBool, Arc},
|
||||
};
|
||||
|
||||
pub struct Project {
|
||||
worktrees: Vec<ModelHandle<Worktree>>,
|
||||
@@ -47,13 +53,13 @@ impl Project {
|
||||
|
||||
pub fn add_local_worktree(
|
||||
&mut self,
|
||||
path: &Path,
|
||||
abs_path: &Path,
|
||||
cx: &mut ModelContext<Self>,
|
||||
) -> Task<Result<ModelHandle<Worktree>>> {
|
||||
let fs = self.fs.clone();
|
||||
let rpc = self.rpc.clone();
|
||||
let languages = self.languages.clone();
|
||||
let path = Arc::from(path);
|
||||
let path = Arc::from(abs_path);
|
||||
cx.spawn(|this, mut cx| async move {
|
||||
let worktree = Worktree::open_local(rpc, path, fs, languages, &mut cx).await?;
|
||||
this.update(&mut cx, |this, cx| {
|
||||
@@ -182,8 +188,239 @@ impl Project {
|
||||
keep
|
||||
});
|
||||
}
|
||||
|
||||
pub fn match_paths<'a>(
|
||||
&self,
|
||||
query: &str,
|
||||
include_ignored: bool,
|
||||
smart_case: bool,
|
||||
max_results: usize,
|
||||
cancel_flag: &'a AtomicBool,
|
||||
cx: &AppContext,
|
||||
) -> impl 'a + Future<Output = Vec<PathMatch>> {
|
||||
let snapshots = self
|
||||
.worktrees
|
||||
.iter()
|
||||
.map(|worktree| worktree.read(cx).snapshot())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let path_count: usize = if include_ignored {
|
||||
snapshots.iter().map(Snapshot::file_count).sum()
|
||||
} else {
|
||||
snapshots.iter().map(Snapshot::visible_file_count).sum()
|
||||
};
|
||||
|
||||
let lowercase_query = query.to_lowercase().chars().collect::<Vec<_>>();
|
||||
let query = query.chars().collect::<Vec<_>>();
|
||||
let query_char_bag = CharBag::from(&lowercase_query[..]);
|
||||
|
||||
let background = cx.background().clone();
|
||||
|
||||
async move {
|
||||
if path_count == 0 {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let num_cpus = background.num_cpus().min(path_count);
|
||||
let segment_size = (path_count + num_cpus - 1) / num_cpus;
|
||||
let mut segment_results = (0..num_cpus)
|
||||
.map(|_| Vec::with_capacity(max_results))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let lowercase_query = &lowercase_query;
|
||||
let query = &query;
|
||||
let snapshots = snapshots.as_slice();
|
||||
|
||||
background
|
||||
.scoped(|scope| {
|
||||
for (segment_idx, results) in segment_results.iter_mut().enumerate() {
|
||||
scope.spawn(async move {
|
||||
let segment_start = segment_idx * segment_size;
|
||||
let segment_end = segment_start + segment_size;
|
||||
let mut matcher = Matcher::new(
|
||||
query,
|
||||
lowercase_query,
|
||||
query_char_bag,
|
||||
smart_case,
|
||||
max_results,
|
||||
);
|
||||
|
||||
let mut tree_start = 0;
|
||||
for snapshot in snapshots {
|
||||
let tree_end = if include_ignored {
|
||||
tree_start + snapshot.file_count()
|
||||
} else {
|
||||
tree_start + snapshot.visible_file_count()
|
||||
};
|
||||
|
||||
if tree_start < segment_end && segment_start < tree_end {
|
||||
let path_prefix: Arc<str> =
|
||||
if snapshot.root_entry().map_or(false, |e| e.is_file()) {
|
||||
snapshot.root_name().into()
|
||||
} else if snapshots.len() > 1 {
|
||||
format!("{}/", snapshot.root_name()).into()
|
||||
} else {
|
||||
"".into()
|
||||
};
|
||||
|
||||
let start = cmp::max(tree_start, segment_start) - tree_start;
|
||||
let end = cmp::min(tree_end, segment_end) - tree_start;
|
||||
let paths = snapshot
|
||||
.files(include_ignored, start)
|
||||
.take(end - start)
|
||||
.map(|entry| {
|
||||
if let EntryKind::File(char_bag) = entry.kind {
|
||||
PathMatchCandidate {
|
||||
path: &entry.path,
|
||||
char_bag,
|
||||
}
|
||||
} else {
|
||||
unreachable!()
|
||||
}
|
||||
});
|
||||
|
||||
matcher.match_paths(
|
||||
snapshot.id(),
|
||||
path_prefix,
|
||||
paths,
|
||||
results,
|
||||
&cancel_flag,
|
||||
);
|
||||
}
|
||||
if tree_end >= segment_end {
|
||||
break;
|
||||
}
|
||||
tree_start = tree_end;
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
.await;
|
||||
|
||||
let mut results = Vec::new();
|
||||
for segment_result in segment_results {
|
||||
if results.is_empty() {
|
||||
results = segment_result;
|
||||
} else {
|
||||
util::extend_sorted(&mut results, segment_result, max_results, |a, b| {
|
||||
b.cmp(&a)
|
||||
});
|
||||
}
|
||||
}
|
||||
results
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Entity for Project {
|
||||
type Event = Event;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
fs::RealFs,
|
||||
test::{temp_tree, test_app_state},
|
||||
};
|
||||
use serde_json::json;
|
||||
use std::{os::unix, path::PathBuf};
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
|
||||
let mut app_state = cx.update(test_app_state);
|
||||
Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(RealFs);
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"apple": "",
|
||||
"banana": {
|
||||
"carrot": {
|
||||
"date": "",
|
||||
"endive": "",
|
||||
}
|
||||
},
|
||||
"fennel": {
|
||||
"grape": "",
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let root_link_path = dir.path().join("root_link");
|
||||
unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
|
||||
unix::fs::symlink(
|
||||
&dir.path().join("root/fennel"),
|
||||
&dir.path().join("root/finnochio"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let project = cx.add_model(|_| Project::new(app_state.as_ref()));
|
||||
let tree = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.add_local_worktree(&root_link_path, cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert_eq!(tree.file_count(), 5);
|
||||
assert_eq!(
|
||||
tree.inode_for_path("fennel/grape"),
|
||||
tree.inode_for_path("finnochio/grape")
|
||||
);
|
||||
});
|
||||
|
||||
let cancel_flag = Default::default();
|
||||
let results = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project.match_paths("bna", false, false, 10, &cancel_flag, cx)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
results
|
||||
.into_iter()
|
||||
.map(|result| result.path)
|
||||
.collect::<Vec<Arc<Path>>>(),
|
||||
vec![
|
||||
PathBuf::from("banana/carrot/date").into(),
|
||||
PathBuf::from("banana/carrot/endive").into(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
|
||||
let mut app_state = cx.update(test_app_state);
|
||||
Arc::get_mut(&mut app_state).unwrap().fs = Arc::new(RealFs);
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"dir1": {},
|
||||
"dir2": {
|
||||
"dir3": {}
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let project = cx.add_model(|_| Project::new(app_state.as_ref()));
|
||||
let tree = project
|
||||
.update(&mut cx, |project, cx| {
|
||||
project.add_local_worktree(&dir.path(), cx)
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
|
||||
let cancel_flag = Default::default();
|
||||
let results = project
|
||||
.read_with(&cx, |project, cx| {
|
||||
project.match_paths("dir", false, false, 10, &cancel_flag, cx)
|
||||
})
|
||||
.await;
|
||||
|
||||
assert!(results.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -454,6 +454,10 @@ impl Workspace {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn project(&self) -> &ModelHandle<Project> {
|
||||
&self.project
|
||||
}
|
||||
|
||||
pub fn worktrees<'a>(&self, cx: &'a AppContext) -> &'a [ModelHandle<Worktree>] {
|
||||
&self.project.read(cx).worktrees()
|
||||
}
|
||||
|
||||
+4
-127
@@ -14,12 +14,10 @@ use crate::{
|
||||
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||
use anyhow::{anyhow, Result};
|
||||
use futures::{Stream, StreamExt};
|
||||
pub use fuzzy::{match_paths, PathMatch};
|
||||
use sum_tree::{self, Edit, SeekTarget, SumTree};
|
||||
pub use fuzzy::PathMatch;
|
||||
use gpui::{
|
||||
executor,
|
||||
AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task,
|
||||
UpgradeModelHandle, WeakModelHandle,
|
||||
executor, AppContext, AsyncAppContext, Entity, ModelContext, ModelHandle, MutableAppContext,
|
||||
Task, UpgradeModelHandle, WeakModelHandle,
|
||||
};
|
||||
use lazy_static::lazy_static;
|
||||
use parking_lot::Mutex;
|
||||
@@ -44,6 +42,7 @@ use std::{
|
||||
},
|
||||
time::{Duration, SystemTime},
|
||||
};
|
||||
use sum_tree::{self, Edit, SeekTarget, SumTree};
|
||||
use zrpc::{PeerId, TypedEnvelope};
|
||||
|
||||
lazy_static! {
|
||||
@@ -2841,128 +2840,6 @@ mod tests {
|
||||
})
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_populate_and_search(cx: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"apple": "",
|
||||
"banana": {
|
||||
"carrot": {
|
||||
"date": "",
|
||||
"endive": "",
|
||||
}
|
||||
},
|
||||
"fennel": {
|
||||
"grape": "",
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
||||
let root_link_path = dir.path().join("root_link");
|
||||
unix::fs::symlink(&dir.path().join("root"), &root_link_path).unwrap();
|
||||
unix::fs::symlink(
|
||||
&dir.path().join("root/fennel"),
|
||||
&dir.path().join("root/finnochio"),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let tree = Worktree::open_local(
|
||||
rpc::Client::new(),
|
||||
root_link_path,
|
||||
Arc::new(RealFs),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
let snapshots = [cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert_eq!(tree.file_count(), 5);
|
||||
assert_eq!(
|
||||
tree.inode_for_path("fennel/grape"),
|
||||
tree.inode_for_path("finnochio/grape")
|
||||
);
|
||||
tree.snapshot()
|
||||
})];
|
||||
let cancel_flag = Default::default();
|
||||
let results = cx
|
||||
.read(|cx| {
|
||||
match_paths(
|
||||
&snapshots,
|
||||
"bna",
|
||||
false,
|
||||
false,
|
||||
10,
|
||||
&cancel_flag,
|
||||
cx.background().clone(),
|
||||
)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
results
|
||||
.into_iter()
|
||||
.map(|result| result.path)
|
||||
.collect::<Vec<Arc<Path>>>(),
|
||||
vec![
|
||||
PathBuf::from("banana/carrot/date").into(),
|
||||
PathBuf::from("banana/carrot/endive").into(),
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_search_worktree_without_files(cx: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
"root": {
|
||||
"dir1": {},
|
||||
"dir2": {
|
||||
"dir3": {}
|
||||
}
|
||||
}
|
||||
}));
|
||||
let tree = Worktree::open_local(
|
||||
rpc::Client::new(),
|
||||
dir.path(),
|
||||
Arc::new(RealFs),
|
||||
Default::default(),
|
||||
&mut cx.to_async(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
cx.read(|cx| tree.read(cx).as_local().unwrap().scan_complete())
|
||||
.await;
|
||||
let snapshots = [cx.read(|cx| {
|
||||
let tree = tree.read(cx);
|
||||
assert_eq!(tree.file_count(), 0);
|
||||
tree.snapshot()
|
||||
})];
|
||||
let cancel_flag = Default::default();
|
||||
let results = cx
|
||||
.read(|cx| {
|
||||
match_paths(
|
||||
&snapshots,
|
||||
"dir",
|
||||
false,
|
||||
false,
|
||||
10,
|
||||
&cancel_flag,
|
||||
cx.background().clone(),
|
||||
)
|
||||
})
|
||||
.await;
|
||||
assert_eq!(
|
||||
results
|
||||
.into_iter()
|
||||
.map(|result| result.path)
|
||||
.collect::<Vec<Arc<Path>>>(),
|
||||
vec![]
|
||||
);
|
||||
}
|
||||
|
||||
#[gpui::test]
|
||||
async fn test_save_file(mut cx: gpui::TestAppContext) {
|
||||
let dir = temp_tree(json!({
|
||||
|
||||
Reference in New Issue
Block a user