Rename worktree crate to project, pull in Project
Also, move the high-level fuzzy mathcing functions in zed::fuzzy into the fuzzy crate so that project can use them. This required defining a 'PathMatchCandidateSet' trait to avoid a circular dependency from fuzzy to worktree. Co-Authored-By: Nathan Sobo <nathan@zed.dev>
This commit is contained in:
parent
748598e419
commit
2f0212ee98
18 changed files with 316 additions and 270 deletions
66
Cargo.lock
generated
66
Cargo.lock
generated
|
@ -2097,6 +2097,10 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "fuzzy"
|
name = "fuzzy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"gpui",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "generator"
|
name = "generator"
|
||||||
|
@ -3674,6 +3678,36 @@ dependencies = [
|
||||||
"unicode-xid",
|
"unicode-xid",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "project"
|
||||||
|
version = "0.1.0"
|
||||||
|
dependencies = [
|
||||||
|
"anyhow",
|
||||||
|
"async-trait",
|
||||||
|
"buffer",
|
||||||
|
"clock",
|
||||||
|
"fsevent",
|
||||||
|
"futures",
|
||||||
|
"fuzzy",
|
||||||
|
"gpui",
|
||||||
|
"ignore",
|
||||||
|
"lazy_static",
|
||||||
|
"libc",
|
||||||
|
"log",
|
||||||
|
"parking_lot",
|
||||||
|
"postage",
|
||||||
|
"rand 0.8.3",
|
||||||
|
"rpc",
|
||||||
|
"rpc_client",
|
||||||
|
"serde 1.0.125",
|
||||||
|
"serde_json 1.0.64",
|
||||||
|
"smol",
|
||||||
|
"sum_tree",
|
||||||
|
"tempdir",
|
||||||
|
"toml 0.5.8",
|
||||||
|
"util",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "prost"
|
name = "prost"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -5975,36 +6009,6 @@ dependencies = [
|
||||||
"winapi 0.3.9",
|
"winapi 0.3.9",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "worktree"
|
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"async-trait",
|
|
||||||
"buffer",
|
|
||||||
"clock",
|
|
||||||
"fsevent",
|
|
||||||
"futures",
|
|
||||||
"fuzzy",
|
|
||||||
"gpui",
|
|
||||||
"ignore",
|
|
||||||
"lazy_static",
|
|
||||||
"libc",
|
|
||||||
"log",
|
|
||||||
"parking_lot",
|
|
||||||
"postage",
|
|
||||||
"rand 0.8.3",
|
|
||||||
"rpc",
|
|
||||||
"rpc_client",
|
|
||||||
"serde 1.0.125",
|
|
||||||
"serde_json 1.0.64",
|
|
||||||
"smol",
|
|
||||||
"sum_tree",
|
|
||||||
"tempdir",
|
|
||||||
"toml 0.5.8",
|
|
||||||
"util",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wyz"
|
name = "wyz"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
|
@ -6070,6 +6074,7 @@ dependencies = [
|
||||||
"num_cpus",
|
"num_cpus",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"postage",
|
"postage",
|
||||||
|
"project",
|
||||||
"rand 0.8.3",
|
"rand 0.8.3",
|
||||||
"rpc",
|
"rpc",
|
||||||
"rpc_client",
|
"rpc_client",
|
||||||
|
@ -6093,7 +6098,6 @@ dependencies = [
|
||||||
"unindent",
|
"unindent",
|
||||||
"url",
|
"url",
|
||||||
"util",
|
"util",
|
||||||
"worktree",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -2,3 +2,7 @@
|
||||||
name = "fuzzy"
|
name = "fuzzy"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
gpui = { path = "../gpui" }
|
||||||
|
util = { path = "../util" }
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
mod char_bag;
|
mod char_bag;
|
||||||
|
|
||||||
|
use gpui::executor;
|
||||||
use std::{
|
use std::{
|
||||||
borrow::Cow,
|
borrow::Cow,
|
||||||
cmp::Ordering,
|
cmp::{self, Ordering},
|
||||||
path::Path,
|
path::Path,
|
||||||
sync::atomic::{self, AtomicBool},
|
sync::atomic::{self, AtomicBool},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
|
@ -58,6 +59,14 @@ pub struct StringMatchCandidate {
|
||||||
pub char_bag: CharBag,
|
pub char_bag: CharBag,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub trait PathMatchCandidateSet<'a>: Send + Sync {
|
||||||
|
type Candidates: Iterator<Item = PathMatchCandidate<'a>>;
|
||||||
|
fn id(&self) -> usize;
|
||||||
|
fn len(&self) -> usize;
|
||||||
|
fn prefix(&self) -> Arc<str>;
|
||||||
|
fn candidates(&'a self, start: usize) -> Self::Candidates;
|
||||||
|
}
|
||||||
|
|
||||||
impl Match for PathMatch {
|
impl Match for PathMatch {
|
||||||
fn score(&self) -> f64 {
|
fn score(&self) -> f64 {
|
||||||
self.score
|
self.score
|
||||||
|
@ -152,6 +161,140 @@ impl Ord for PathMatch {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn match_strings(
|
||||||
|
candidates: &[StringMatchCandidate],
|
||||||
|
query: &str,
|
||||||
|
smart_case: bool,
|
||||||
|
max_results: usize,
|
||||||
|
cancel_flag: &AtomicBool,
|
||||||
|
background: Arc<executor::Background>,
|
||||||
|
) -> Vec<StringMatch> {
|
||||||
|
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(candidates.len());
|
||||||
|
let segment_size = (candidates.len() + 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() {
|
||||||
|
let cancel_flag = &cancel_flag;
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
matcher.match_strings(
|
||||||
|
&candidates[segment_start..segment_end],
|
||||||
|
results,
|
||||||
|
cancel_flag,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.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
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn match_paths<'a, Set: PathMatchCandidateSet<'a>>(
|
||||||
|
candidate_sets: &'a [Set],
|
||||||
|
query: &str,
|
||||||
|
smart_case: bool,
|
||||||
|
max_results: usize,
|
||||||
|
cancel_flag: &AtomicBool,
|
||||||
|
background: Arc<executor::Background>,
|
||||||
|
) -> Vec<PathMatch> {
|
||||||
|
let path_count: usize = candidate_sets.iter().map(|s| s.len()).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 candidate_set in candidate_sets {
|
||||||
|
let tree_end = tree_start + candidate_set.len();
|
||||||
|
|
||||||
|
if tree_start < segment_end && segment_start < tree_end {
|
||||||
|
let start = cmp::max(tree_start, segment_start) - tree_start;
|
||||||
|
let end = cmp::min(tree_end, segment_end) - tree_start;
|
||||||
|
let candidates = candidate_set.candidates(start).take(end - start);
|
||||||
|
|
||||||
|
matcher.match_paths(
|
||||||
|
candidate_set.id(),
|
||||||
|
candidate_set.prefix(),
|
||||||
|
candidates,
|
||||||
|
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> {
|
impl<'a> Matcher<'a> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
query: &'a [char],
|
query: &'a [char],
|
||||||
|
@ -194,11 +337,11 @@ impl<'a> Matcher<'a> {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn match_paths(
|
pub fn match_paths<'c: 'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
tree_id: usize,
|
tree_id: usize,
|
||||||
path_prefix: Arc<str>,
|
path_prefix: Arc<str>,
|
||||||
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
|
path_entries: impl Iterator<Item = PathMatchCandidate<'c>>,
|
||||||
results: &mut Vec<PathMatch>,
|
results: &mut Vec<PathMatch>,
|
||||||
cancel_flag: &AtomicBool,
|
cancel_flag: &AtomicBool,
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
[package]
|
[package]
|
||||||
name = "worktree"
|
name = "project"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::{
|
pub mod fs;
|
||||||
fuzzy::{self, PathMatch},
|
mod ignore;
|
||||||
AppState,
|
mod worktree;
|
||||||
};
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use buffer::LanguageRegistry;
|
use buffer::LanguageRegistry;
|
||||||
use futures::Future;
|
use futures::Future;
|
||||||
|
use fuzzy::{self, PathMatch, PathMatchCandidate, PathMatchCandidateSet};
|
||||||
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
|
||||||
use rpc_client as rpc;
|
use rpc_client as rpc;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -12,7 +13,9 @@ use std::{
|
||||||
sync::{atomic::AtomicBool, Arc},
|
sync::{atomic::AtomicBool, Arc},
|
||||||
};
|
};
|
||||||
use util::TryFutureExt as _;
|
use util::TryFutureExt as _;
|
||||||
use worktree::{fs::Fs, Worktree};
|
|
||||||
|
pub use fs::*;
|
||||||
|
pub use worktree::*;
|
||||||
|
|
||||||
pub struct Project {
|
pub struct Project {
|
||||||
worktrees: Vec<ModelHandle<Worktree>>,
|
worktrees: Vec<ModelHandle<Worktree>>,
|
||||||
|
@ -40,13 +43,13 @@ pub struct ProjectEntry {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Project {
|
impl Project {
|
||||||
pub fn new(app_state: &AppState) -> Self {
|
pub fn new(languages: Arc<LanguageRegistry>, rpc: Arc<rpc::Client>, fs: Arc<dyn Fs>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
worktrees: Default::default(),
|
worktrees: Default::default(),
|
||||||
active_entry: None,
|
active_entry: None,
|
||||||
languages: app_state.languages.clone(),
|
languages,
|
||||||
rpc: app_state.rpc.clone(),
|
rpc,
|
||||||
fs: app_state.fs.clone(),
|
fs,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,18 +210,22 @@ impl Project {
|
||||||
cancel_flag: &'a AtomicBool,
|
cancel_flag: &'a AtomicBool,
|
||||||
cx: &AppContext,
|
cx: &AppContext,
|
||||||
) -> impl 'a + Future<Output = Vec<PathMatch>> {
|
) -> impl 'a + Future<Output = Vec<PathMatch>> {
|
||||||
let snapshots = self
|
let include_root_name = self.worktrees.len() > 1;
|
||||||
|
let candidate_sets = self
|
||||||
.worktrees
|
.worktrees
|
||||||
.iter()
|
.iter()
|
||||||
.map(|worktree| worktree.read(cx).snapshot())
|
.map(|worktree| CandidateSet {
|
||||||
|
snapshot: worktree.read(cx).snapshot(),
|
||||||
|
include_ignored,
|
||||||
|
include_root_name,
|
||||||
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let background = cx.background().clone();
|
|
||||||
|
|
||||||
|
let background = cx.background().clone();
|
||||||
async move {
|
async move {
|
||||||
fuzzy::match_paths(
|
fuzzy::match_paths(
|
||||||
snapshots.as_slice(),
|
candidate_sets.as_slice(),
|
||||||
query,
|
query,
|
||||||
include_ignored,
|
|
||||||
smart_case,
|
smart_case,
|
||||||
max_results,
|
max_results,
|
||||||
cancel_flag,
|
cancel_flag,
|
||||||
|
@ -229,6 +236,65 @@ impl Project {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct CandidateSet {
|
||||||
|
snapshot: Snapshot,
|
||||||
|
include_ignored: bool,
|
||||||
|
include_root_name: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> PathMatchCandidateSet<'a> for CandidateSet {
|
||||||
|
type Candidates = CandidateSetIter<'a>;
|
||||||
|
|
||||||
|
fn id(&self) -> usize {
|
||||||
|
self.snapshot.id()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self) -> usize {
|
||||||
|
if self.include_ignored {
|
||||||
|
self.snapshot.file_count()
|
||||||
|
} else {
|
||||||
|
self.snapshot.visible_file_count()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn prefix(&self) -> Arc<str> {
|
||||||
|
if self.snapshot.root_entry().map_or(false, |e| e.is_file()) {
|
||||||
|
self.snapshot.root_name().into()
|
||||||
|
} else if self.include_root_name {
|
||||||
|
format!("{}/", self.snapshot.root_name()).into()
|
||||||
|
} else {
|
||||||
|
"".into()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn candidates(&'a self, start: usize) -> Self::Candidates {
|
||||||
|
CandidateSetIter {
|
||||||
|
traversal: self.snapshot.files(self.include_ignored, start),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CandidateSetIter<'a> {
|
||||||
|
traversal: Traversal<'a>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Iterator for CandidateSetIter<'a> {
|
||||||
|
type Item = PathMatchCandidate<'a>;
|
||||||
|
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.traversal.next().map(|entry| {
|
||||||
|
if let EntryKind::File(char_bag) = entry.kind {
|
||||||
|
PathMatchCandidate {
|
||||||
|
path: &entry.path,
|
||||||
|
char_bag,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Entity for Project {
|
impl Entity for Project {
|
||||||
type Event = Event;
|
type Event = Event;
|
||||||
}
|
}
|
||||||
|
@ -236,16 +302,15 @@ impl Entity for Project {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::test::test_app_state;
|
use buffer::LanguageRegistry;
|
||||||
|
use fs::RealFs;
|
||||||
|
use gpui::TestAppContext;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::{os::unix, path::PathBuf};
|
use std::{os::unix, path::PathBuf};
|
||||||
use util::test::temp_tree;
|
use util::test::temp_tree;
|
||||||
use worktree::fs::RealFs;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_populate_and_search(mut cx: gpui::TestAppContext) {
|
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!({
|
let dir = temp_tree(json!({
|
||||||
"root": {
|
"root": {
|
||||||
"apple": "",
|
"apple": "",
|
||||||
|
@ -269,7 +334,8 @@ mod tests {
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let project = cx.add_model(|_| Project::new(app_state.as_ref()));
|
let project = build_project(&mut cx);
|
||||||
|
|
||||||
let tree = project
|
let tree = project
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.add_local_worktree(&root_link_path, cx)
|
project.add_local_worktree(&root_link_path, cx)
|
||||||
|
@ -308,8 +374,6 @@ mod tests {
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_search_worktree_without_files(mut cx: gpui::TestAppContext) {
|
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!({
|
let dir = temp_tree(json!({
|
||||||
"root": {
|
"root": {
|
||||||
"dir1": {},
|
"dir1": {},
|
||||||
|
@ -319,7 +383,7 @@ mod tests {
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let project = cx.add_model(|_| Project::new(app_state.as_ref()));
|
let project = build_project(&mut cx);
|
||||||
let tree = project
|
let tree = project
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.add_local_worktree(&dir.path(), cx)
|
project.add_local_worktree(&dir.path(), cx)
|
||||||
|
@ -339,4 +403,11 @@ mod tests {
|
||||||
|
|
||||||
assert!(results.is_empty());
|
assert!(results.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn build_project(cx: &mut TestAppContext) -> ModelHandle<Project> {
|
||||||
|
let languages = Arc::new(LanguageRegistry::new());
|
||||||
|
let fs = Arc::new(RealFs);
|
||||||
|
let rpc = rpc::Client::new();
|
||||||
|
cx.add_model(|_| Project::new(languages, rpc, fs))
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
pub mod fs;
|
use super::{
|
||||||
mod ignore;
|
fs::{self, Fs},
|
||||||
|
ignore::IgnoreStack,
|
||||||
use self::ignore::IgnoreStack;
|
};
|
||||||
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope};
|
use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope};
|
||||||
use clock::ReplicaId;
|
use clock::ReplicaId;
|
||||||
pub use fs::*;
|
|
||||||
use futures::{Stream, StreamExt};
|
use futures::{Stream, StreamExt};
|
||||||
use fuzzy::CharBag;
|
use fuzzy::CharBag;
|
||||||
use gpui::{
|
use gpui::{
|
|
@ -981,12 +981,11 @@ mod tests {
|
||||||
editor::{Editor, EditorSettings, Insert},
|
editor::{Editor, EditorSettings, Insert},
|
||||||
fs::{FakeFs, Fs as _},
|
fs::{FakeFs, Fs as _},
|
||||||
people_panel::JoinWorktree,
|
people_panel::JoinWorktree,
|
||||||
project::ProjectPath,
|
project::{ProjectPath, Worktree},
|
||||||
rpc::{self, Client, Credentials, EstablishConnectionError},
|
rpc::{self, Client, Credentials, EstablishConnectionError},
|
||||||
test::FakeHttpClient,
|
test::FakeHttpClient,
|
||||||
user::UserStore,
|
user::UserStore,
|
||||||
workspace::Workspace,
|
workspace::Workspace,
|
||||||
worktree::Worktree,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
|
|
|
@ -17,10 +17,10 @@ path = "src/main.rs"
|
||||||
test-support = [
|
test-support = [
|
||||||
"buffer/test-support",
|
"buffer/test-support",
|
||||||
"gpui/test-support",
|
"gpui/test-support",
|
||||||
|
"project/test-support",
|
||||||
|
"rpc/test-support",
|
||||||
"rpc_client/test-support",
|
"rpc_client/test-support",
|
||||||
"tempdir",
|
"tempdir",
|
||||||
"worktree/test-support",
|
|
||||||
"rpc/test-support",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -30,11 +30,11 @@ fsevent = { path = "../fsevent" }
|
||||||
fuzzy = { path = "../fuzzy" }
|
fuzzy = { path = "../fuzzy" }
|
||||||
editor = { path = "../editor" }
|
editor = { path = "../editor" }
|
||||||
gpui = { path = "../gpui" }
|
gpui = { path = "../gpui" }
|
||||||
|
project = { path = "../project" }
|
||||||
rpc = { path = "../rpc" }
|
rpc = { path = "../rpc" }
|
||||||
rpc_client = { path = "../rpc_client" }
|
rpc_client = { path = "../rpc_client" }
|
||||||
sum_tree = { path = "../sum_tree" }
|
sum_tree = { path = "../sum_tree" }
|
||||||
util = { path = "../util" }
|
util = { path = "../util" }
|
||||||
worktree = { path = "../worktree" }
|
|
||||||
|
|
||||||
anyhow = "1.0.38"
|
anyhow = "1.0.38"
|
||||||
async-recursion = "0.3"
|
async-recursion = "0.3"
|
||||||
|
@ -79,10 +79,10 @@ url = "2.2"
|
||||||
buffer = { path = "../buffer", features = ["test-support"] }
|
buffer = { path = "../buffer", features = ["test-support"] }
|
||||||
editor = { path = "../editor", features = ["test-support"] }
|
editor = { path = "../editor", features = ["test-support"] }
|
||||||
gpui = { path = "../gpui", features = ["test-support"] }
|
gpui = { path = "../gpui", features = ["test-support"] }
|
||||||
|
project = { path = "../project", features = ["test-support"] }
|
||||||
rpc = { path = "../rpc", features = ["test-support"] }
|
rpc = { path = "../rpc", features = ["test-support"] }
|
||||||
rpc_client = { path = "../rpc_client", features = ["test-support"] }
|
rpc_client = { path = "../rpc_client", features = ["test-support"] }
|
||||||
util = { path = "../util", features = ["test-support"] }
|
util = { path = "../util", features = ["test-support"] }
|
||||||
worktree = { path = "../worktree", features = ["test-support"] }
|
|
||||||
|
|
||||||
cargo-bundle = "0.5.0"
|
cargo-bundle = "0.5.0"
|
||||||
env_logger = "0.8"
|
env_logger = "0.8"
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
use crate::{
|
use crate::{settings::Settings, workspace::Workspace};
|
||||||
fuzzy::PathMatch,
|
|
||||||
project::{Project, ProjectPath},
|
|
||||||
settings::Settings,
|
|
||||||
workspace::Workspace,
|
|
||||||
};
|
|
||||||
use editor::{self, Editor, EditorSettings};
|
use editor::{self, Editor, EditorSettings};
|
||||||
|
use fuzzy::PathMatch;
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
|
@ -17,6 +13,7 @@ use gpui::{
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use project::{Project, ProjectPath};
|
||||||
use std::{
|
use std::{
|
||||||
cmp,
|
cmp,
|
||||||
path::Path,
|
path::Path,
|
||||||
|
@ -427,9 +424,9 @@ mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::{test::test_app_state, workspace::Workspace};
|
use crate::{test::test_app_state, workspace::Workspace};
|
||||||
use editor::{self, Insert};
|
use editor::{self, Insert};
|
||||||
|
use project::fs::FakeFs;
|
||||||
use serde_json::json;
|
use serde_json::json;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use worktree::fs::FakeFs;
|
|
||||||
|
|
||||||
#[gpui::test]
|
#[gpui::test]
|
||||||
async fn test_matching_paths(mut cx: gpui::TestAppContext) {
|
async fn test_matching_paths(mut cx: gpui::TestAppContext) {
|
||||||
|
|
|
@ -1,173 +0,0 @@
|
||||||
use gpui::executor;
|
|
||||||
use std::{
|
|
||||||
cmp,
|
|
||||||
sync::{atomic::AtomicBool, Arc},
|
|
||||||
};
|
|
||||||
use util;
|
|
||||||
use worktree::{EntryKind, Snapshot};
|
|
||||||
|
|
||||||
pub use fuzzy::*;
|
|
||||||
|
|
||||||
pub async fn match_strings(
|
|
||||||
candidates: &[StringMatchCandidate],
|
|
||||||
query: &str,
|
|
||||||
smart_case: bool,
|
|
||||||
max_results: usize,
|
|
||||||
cancel_flag: &AtomicBool,
|
|
||||||
background: Arc<executor::Background>,
|
|
||||||
) -> Vec<StringMatch> {
|
|
||||||
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(candidates.len());
|
|
||||||
let segment_size = (candidates.len() + 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() {
|
|
||||||
let cancel_flag = &cancel_flag;
|
|
||||||
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,
|
|
||||||
);
|
|
||||||
matcher.match_strings(
|
|
||||||
&candidates[segment_start..segment_end],
|
|
||||||
results,
|
|
||||||
cancel_flag,
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.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
|
|
||||||
}
|
|
||||||
|
|
||||||
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 = 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
|
|
||||||
}
|
|
|
@ -2,12 +2,10 @@ pub mod assets;
|
||||||
pub mod channel;
|
pub mod channel;
|
||||||
pub mod chat_panel;
|
pub mod chat_panel;
|
||||||
pub mod file_finder;
|
pub mod file_finder;
|
||||||
mod fuzzy;
|
|
||||||
pub mod http;
|
pub mod http;
|
||||||
pub mod language;
|
pub mod language;
|
||||||
pub mod menus;
|
pub mod menus;
|
||||||
pub mod people_panel;
|
pub mod people_panel;
|
||||||
pub mod project;
|
|
||||||
pub mod project_panel;
|
pub mod project_panel;
|
||||||
pub mod settings;
|
pub mod settings;
|
||||||
#[cfg(any(test, feature = "test-support"))]
|
#[cfg(any(test, feature = "test-support"))]
|
||||||
|
@ -24,11 +22,11 @@ pub use editor;
|
||||||
use gpui::{action, keymap::Binding, ModelHandle};
|
use gpui::{action, keymap::Binding, ModelHandle};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
pub use project::{self, fs};
|
||||||
pub use rpc_client as rpc;
|
pub use rpc_client as rpc;
|
||||||
pub use settings::Settings;
|
pub use settings::Settings;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use util::TryFutureExt;
|
use util::TryFutureExt;
|
||||||
pub use worktree::{self, fs};
|
|
||||||
|
|
||||||
action!(About);
|
action!(About);
|
||||||
action!(Quit);
|
action!(Quit);
|
||||||
|
|
|
@ -20,12 +20,12 @@ use gpui::{
|
||||||
ViewContext, ViewHandle, WeakViewHandle,
|
ViewContext, ViewHandle, WeakViewHandle,
|
||||||
};
|
};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use project::Worktree;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map, HashMap},
|
collections::{hash_map, HashMap},
|
||||||
ffi::OsStr,
|
ffi::OsStr,
|
||||||
ops::Range,
|
ops::Range,
|
||||||
};
|
};
|
||||||
use worktree::Worktree;
|
|
||||||
|
|
||||||
pub struct ProjectPanel {
|
pub struct ProjectPanel {
|
||||||
project: ModelHandle<Project>,
|
project: ModelHandle<Project>,
|
||||||
|
@ -288,7 +288,7 @@ impl ProjectPanel {
|
||||||
&self,
|
&self,
|
||||||
target_ix: usize,
|
target_ix: usize,
|
||||||
cx: &'a AppContext,
|
cx: &'a AppContext,
|
||||||
) -> Option<(&'a Worktree, &'a worktree::Entry)> {
|
) -> Option<(&'a Worktree, &'a project::Entry)> {
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let mut offset = None;
|
let mut offset = None;
|
||||||
let mut ix = 0;
|
let mut ix = 0;
|
||||||
|
@ -309,10 +309,7 @@ impl ProjectPanel {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn selected_entry<'a>(
|
fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> {
|
||||||
&self,
|
|
||||||
cx: &'a AppContext,
|
|
||||||
) -> Option<(&'a Worktree, &'a worktree::Entry)> {
|
|
||||||
let selection = self.selection?;
|
let selection = self.selection?;
|
||||||
let project = self.project.read(cx);
|
let project = self.project.read(cx);
|
||||||
let worktree = project.worktree_for_id(selection.worktree_id)?.read(cx);
|
let worktree = project.worktree_for_id(selection.worktree_id)?.read(cx);
|
||||||
|
@ -626,7 +623,13 @@ mod tests {
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
let project = cx.add_model(|_| Project::new(&app_state));
|
let project = cx.add_model(|_| {
|
||||||
|
Project::new(
|
||||||
|
app_state.languages.clone(),
|
||||||
|
app_state.rpc.clone(),
|
||||||
|
app_state.fs.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
let root1 = project
|
let root1 = project
|
||||||
.update(&mut cx, |project, cx| {
|
.update(&mut cx, |project, cx| {
|
||||||
project.add_local_worktree("/root1".as_ref(), cx)
|
project.add_local_worktree("/root1".as_ref(), cx)
|
||||||
|
|
|
@ -12,9 +12,9 @@ use buffer::LanguageRegistry;
|
||||||
use futures::{future::BoxFuture, Future};
|
use futures::{future::BoxFuture, Future};
|
||||||
use gpui::MutableAppContext;
|
use gpui::MutableAppContext;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
|
use project::fs::FakeFs;
|
||||||
use rpc_client as rpc;
|
use rpc_client as rpc;
|
||||||
use std::{fmt, sync::Arc};
|
use std::{fmt, sync::Arc};
|
||||||
use worktree::fs::FakeFs;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[ctor::ctor]
|
#[ctor::ctor]
|
||||||
|
|
|
@ -1,12 +1,6 @@
|
||||||
use std::{cmp, sync::Arc};
|
use crate::{settings::ThemeRegistry, workspace::Workspace, AppState, Settings};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
fuzzy::{match_strings, StringMatch, StringMatchCandidate},
|
|
||||||
settings::ThemeRegistry,
|
|
||||||
workspace::Workspace,
|
|
||||||
AppState, Settings,
|
|
||||||
};
|
|
||||||
use editor::{self, Editor, EditorSettings};
|
use editor::{self, Editor, EditorSettings};
|
||||||
|
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
|
||||||
use gpui::{
|
use gpui::{
|
||||||
action,
|
action,
|
||||||
elements::*,
|
elements::*,
|
||||||
|
@ -16,6 +10,7 @@ use gpui::{
|
||||||
};
|
};
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use std::{cmp, sync::Arc};
|
||||||
|
|
||||||
pub struct ThemeSelector {
|
pub struct ThemeSelector {
|
||||||
settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
settings_tx: Arc<Mutex<watch::Sender<Settings>>>,
|
||||||
|
|
|
@ -32,13 +32,13 @@ use log::error;
|
||||||
pub use pane::*;
|
pub use pane::*;
|
||||||
pub use pane_group::*;
|
pub use pane_group::*;
|
||||||
use postage::{prelude::Stream, watch};
|
use postage::{prelude::Stream, watch};
|
||||||
|
use project::Worktree;
|
||||||
use std::{
|
use std::{
|
||||||
collections::{hash_map::Entry, HashMap},
|
collections::{hash_map::Entry, HashMap},
|
||||||
future::Future,
|
future::Future,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
use worktree::Worktree;
|
|
||||||
|
|
||||||
action!(Open, Arc<AppState>);
|
action!(Open, Arc<AppState>);
|
||||||
action!(OpenPaths, OpenParams);
|
action!(OpenPaths, OpenParams);
|
||||||
|
@ -376,7 +376,13 @@ pub struct Workspace {
|
||||||
|
|
||||||
impl Workspace {
|
impl Workspace {
|
||||||
pub fn new(app_state: &AppState, cx: &mut ViewContext<Self>) -> Self {
|
pub fn new(app_state: &AppState, cx: &mut ViewContext<Self>) -> Self {
|
||||||
let project = cx.add_model(|_| Project::new(app_state));
|
let project = cx.add_model(|_| {
|
||||||
|
Project::new(
|
||||||
|
app_state.languages.clone(),
|
||||||
|
app_state.rpc.clone(),
|
||||||
|
app_state.fs.clone(),
|
||||||
|
)
|
||||||
|
});
|
||||||
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
cx.observe(&project, |_, _, cx| cx.notify()).detach();
|
||||||
|
|
||||||
let pane = cx.add_view(|_| Pane::new(app_state.settings.clone()));
|
let pane = cx.add_view(|_| Pane::new(app_state.settings.clone()));
|
||||||
|
|
|
@ -5,8 +5,8 @@ use buffer::{Buffer, File as _};
|
||||||
use editor::{Editor, EditorSettings, Event};
|
use editor::{Editor, EditorSettings, Event};
|
||||||
use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
|
use gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
|
||||||
use postage::watch;
|
use postage::watch;
|
||||||
|
use project::Worktree;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use worktree::Worktree;
|
|
||||||
|
|
||||||
impl Item for Buffer {
|
impl Item for Buffer {
|
||||||
type View = Editor;
|
type View = Editor;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue