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]]
name = "fuzzy"
version = "0.1.0"
dependencies = [
"gpui",
"util",
]
[[package]]
name = "generator"
@ -3674,6 +3678,36 @@ dependencies = [
"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]]
name = "prost"
version = "0.8.0"
@ -5975,36 +6009,6 @@ dependencies = [
"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]]
name = "wyz"
version = "0.2.0"
@ -6070,6 +6074,7 @@ dependencies = [
"num_cpus",
"parking_lot",
"postage",
"project",
"rand 0.8.3",
"rpc",
"rpc_client",
@ -6093,7 +6098,6 @@ dependencies = [
"unindent",
"url",
"util",
"worktree",
]
[[package]]

View File

@ -2,3 +2,7 @@
name = "fuzzy"
version = "0.1.0"
edition = "2018"
[dependencies]
gpui = { path = "../gpui" }
util = { path = "../util" }

View File

@ -1,8 +1,9 @@
mod char_bag;
use gpui::executor;
use std::{
borrow::Cow,
cmp::Ordering,
cmp::{self, Ordering},
path::Path,
sync::atomic::{self, AtomicBool},
sync::Arc,
@ -58,6 +59,14 @@ pub struct StringMatchCandidate {
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 {
fn score(&self) -> f64 {
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> {
pub fn new(
query: &'a [char],
@ -194,11 +337,11 @@ impl<'a> Matcher<'a> {
)
}
pub fn match_paths(
pub fn match_paths<'c: 'a>(
&mut self,
tree_id: usize,
path_prefix: Arc<str>,
path_entries: impl Iterator<Item = PathMatchCandidate<'a>>,
path_entries: impl Iterator<Item = PathMatchCandidate<'c>>,
results: &mut Vec<PathMatch>,
cancel_flag: &AtomicBool,
) {

View File

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

View File

@ -1,10 +1,11 @@
use crate::{
fuzzy::{self, PathMatch},
AppState,
};
pub mod fs;
mod ignore;
mod worktree;
use anyhow::Result;
use buffer::LanguageRegistry;
use futures::Future;
use fuzzy::{self, PathMatch, PathMatchCandidate, PathMatchCandidateSet};
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
use rpc_client as rpc;
use std::{
@ -12,7 +13,9 @@ use std::{
sync::{atomic::AtomicBool, Arc},
};
use util::TryFutureExt as _;
use worktree::{fs::Fs, Worktree};
pub use fs::*;
pub use worktree::*;
pub struct Project {
worktrees: Vec<ModelHandle<Worktree>>,
@ -40,13 +43,13 @@ pub struct ProjectEntry {
}
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 {
worktrees: Default::default(),
active_entry: None,
languages: app_state.languages.clone(),
rpc: app_state.rpc.clone(),
fs: app_state.fs.clone(),
languages,
rpc,
fs,
}
}
@ -207,18 +210,22 @@ impl Project {
cancel_flag: &'a AtomicBool,
cx: &AppContext,
) -> impl 'a + Future<Output = Vec<PathMatch>> {
let snapshots = self
let include_root_name = self.worktrees.len() > 1;
let candidate_sets = self
.worktrees
.iter()
.map(|worktree| worktree.read(cx).snapshot())
.map(|worktree| CandidateSet {
snapshot: worktree.read(cx).snapshot(),
include_ignored,
include_root_name,
})
.collect::<Vec<_>>();
let background = cx.background().clone();
let background = cx.background().clone();
async move {
fuzzy::match_paths(
snapshots.as_slice(),
candidate_sets.as_slice(),
query,
include_ignored,
smart_case,
max_results,
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 {
type Event = Event;
}
@ -236,16 +302,15 @@ impl Entity for Project {
#[cfg(test)]
mod tests {
use super::*;
use crate::test::test_app_state;
use buffer::LanguageRegistry;
use fs::RealFs;
use gpui::TestAppContext;
use serde_json::json;
use std::{os::unix, path::PathBuf};
use util::test::temp_tree;
use worktree::fs::RealFs;
#[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": "",
@ -269,7 +334,8 @@ mod tests {
)
.unwrap();
let project = cx.add_model(|_| Project::new(app_state.as_ref()));
let project = build_project(&mut cx);
let tree = project
.update(&mut cx, |project, cx| {
project.add_local_worktree(&root_link_path, cx)
@ -308,8 +374,6 @@ mod tests {
#[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": {},
@ -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
.update(&mut cx, |project, cx| {
project.add_local_worktree(&dir.path(), cx)
@ -339,4 +403,11 @@ mod tests {
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;
mod ignore;
use self::ignore::IgnoreStack;
use super::{
fs::{self, Fs},
ignore::IgnoreStack,
};
use ::ignore::gitignore::{Gitignore, GitignoreBuilder};
use anyhow::{anyhow, Result};
use buffer::{self, Buffer, History, LanguageRegistry, Operation, Rope};
use clock::ReplicaId;
pub use fs::*;
use futures::{Stream, StreamExt};
use fuzzy::CharBag;
use gpui::{

View File

@ -981,12 +981,11 @@ mod tests {
editor::{Editor, EditorSettings, Insert},
fs::{FakeFs, Fs as _},
people_panel::JoinWorktree,
project::ProjectPath,
project::{ProjectPath, Worktree},
rpc::{self, Client, Credentials, EstablishConnectionError},
test::FakeHttpClient,
user::UserStore,
workspace::Workspace,
worktree::Worktree,
};
#[gpui::test]

View File

@ -17,10 +17,10 @@ path = "src/main.rs"
test-support = [
"buffer/test-support",
"gpui/test-support",
"project/test-support",
"rpc/test-support",
"rpc_client/test-support",
"tempdir",
"worktree/test-support",
"rpc/test-support",
]
[dependencies]
@ -30,11 +30,11 @@ fsevent = { path = "../fsevent" }
fuzzy = { path = "../fuzzy" }
editor = { path = "../editor" }
gpui = { path = "../gpui" }
project = { path = "../project" }
rpc = { path = "../rpc" }
rpc_client = { path = "../rpc_client" }
sum_tree = { path = "../sum_tree" }
util = { path = "../util" }
worktree = { path = "../worktree" }
anyhow = "1.0.38"
async-recursion = "0.3"
@ -79,10 +79,10 @@ url = "2.2"
buffer = { path = "../buffer", features = ["test-support"] }
editor = { path = "../editor", features = ["test-support"] }
gpui = { path = "../gpui", features = ["test-support"] }
project = { path = "../project", features = ["test-support"] }
rpc = { path = "../rpc", features = ["test-support"] }
rpc_client = { path = "../rpc_client", features = ["test-support"] }
util = { path = "../util", features = ["test-support"] }
worktree = { path = "../worktree", features = ["test-support"] }
cargo-bundle = "0.5.0"
env_logger = "0.8"

View File

@ -1,10 +1,6 @@
use crate::{
fuzzy::PathMatch,
project::{Project, ProjectPath},
settings::Settings,
workspace::Workspace,
};
use crate::{settings::Settings, workspace::Workspace};
use editor::{self, Editor, EditorSettings};
use fuzzy::PathMatch;
use gpui::{
action,
elements::*,
@ -17,6 +13,7 @@ use gpui::{
ViewContext, ViewHandle, WeakViewHandle,
};
use postage::watch;
use project::{Project, ProjectPath};
use std::{
cmp,
path::Path,
@ -427,9 +424,9 @@ mod tests {
use super::*;
use crate::{test::test_app_state, workspace::Workspace};
use editor::{self, Insert};
use project::fs::FakeFs;
use serde_json::json;
use std::path::PathBuf;
use worktree::fs::FakeFs;
#[gpui::test]
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 chat_panel;
pub mod file_finder;
mod fuzzy;
pub mod http;
pub mod language;
pub mod menus;
pub mod people_panel;
pub mod project;
pub mod project_panel;
pub mod settings;
#[cfg(any(test, feature = "test-support"))]
@ -24,11 +22,11 @@ pub use editor;
use gpui::{action, keymap::Binding, ModelHandle};
use parking_lot::Mutex;
use postage::watch;
pub use project::{self, fs};
pub use rpc_client as rpc;
pub use settings::Settings;
use std::sync::Arc;
use util::TryFutureExt;
pub use worktree::{self, fs};
action!(About);
action!(Quit);

View File

@ -20,12 +20,12 @@ use gpui::{
ViewContext, ViewHandle, WeakViewHandle,
};
use postage::watch;
use project::Worktree;
use std::{
collections::{hash_map, HashMap},
ffi::OsStr,
ops::Range,
};
use worktree::Worktree;
pub struct ProjectPanel {
project: ModelHandle<Project>,
@ -288,7 +288,7 @@ impl ProjectPanel {
&self,
target_ix: usize,
cx: &'a AppContext,
) -> Option<(&'a Worktree, &'a worktree::Entry)> {
) -> Option<(&'a Worktree, &'a project::Entry)> {
let project = self.project.read(cx);
let mut offset = None;
let mut ix = 0;
@ -309,10 +309,7 @@ impl ProjectPanel {
})
}
fn selected_entry<'a>(
&self,
cx: &'a AppContext,
) -> Option<(&'a Worktree, &'a worktree::Entry)> {
fn selected_entry<'a>(&self, cx: &'a AppContext) -> Option<(&'a Worktree, &'a project::Entry)> {
let selection = self.selection?;
let project = self.project.read(cx);
let worktree = project.worktree_for_id(selection.worktree_id)?.read(cx);
@ -626,7 +623,13 @@ mod tests {
)
.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
.update(&mut cx, |project, cx| {
project.add_local_worktree("/root1".as_ref(), cx)

View File

@ -12,9 +12,9 @@ use buffer::LanguageRegistry;
use futures::{future::BoxFuture, Future};
use gpui::MutableAppContext;
use parking_lot::Mutex;
use project::fs::FakeFs;
use rpc_client as rpc;
use std::{fmt, sync::Arc};
use worktree::fs::FakeFs;
#[cfg(test)]
#[ctor::ctor]

View File

@ -1,12 +1,6 @@
use std::{cmp, sync::Arc};
use crate::{
fuzzy::{match_strings, StringMatch, StringMatchCandidate},
settings::ThemeRegistry,
workspace::Workspace,
AppState, Settings,
};
use crate::{settings::ThemeRegistry, workspace::Workspace, AppState, Settings};
use editor::{self, Editor, EditorSettings};
use fuzzy::{match_strings, StringMatch, StringMatchCandidate};
use gpui::{
action,
elements::*,
@ -16,6 +10,7 @@ use gpui::{
};
use parking_lot::Mutex;
use postage::watch;
use std::{cmp, sync::Arc};
pub struct ThemeSelector {
settings_tx: Arc<Mutex<watch::Sender<Settings>>>,

View File

@ -32,13 +32,13 @@ use log::error;
pub use pane::*;
pub use pane_group::*;
use postage::{prelude::Stream, watch};
use project::Worktree;
use std::{
collections::{hash_map::Entry, HashMap},
future::Future,
path::{Path, PathBuf},
sync::Arc,
};
use worktree::Worktree;
action!(Open, Arc<AppState>);
action!(OpenPaths, OpenParams);
@ -376,7 +376,13 @@ pub struct Workspace {
impl Workspace {
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();
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 gpui::{fonts::TextStyle, AppContext, ModelHandle, Task, ViewContext};
use postage::watch;
use project::Worktree;
use std::path::Path;
use worktree::Worktree;
impl Item for Buffer {
type View = Editor;