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:
Max Brunsfeld 2021-10-04 16:45:05 -07:00
parent 748598e419
commit 2f0212ee98
18 changed files with 316 additions and 270 deletions

66
Cargo.lock generated
View file

@ -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]]

View file

@ -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" }

View file

@ -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,
) { ) {

View file

@ -1,5 +1,5 @@
[package] [package]
name = "worktree" name = "project"
version = "0.1.0" version = "0.1.0"
edition = "2018" edition = "2018"

View file

@ -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))
}
} }

View file

@ -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::{

View file

@ -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]

View file

@ -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"

View file

@ -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) {

View file

@ -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
}

View file

@ -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);

View file

@ -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)

View file

@ -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]

View file

@ -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>>>,

View file

@ -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()));

View file

@ -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;