This commit is contained in:
Nathan Sobo 2021-04-14 12:38:06 -06:00
parent cbc1d83067
commit 36e6ed3aef
6 changed files with 329 additions and 7 deletions

View file

@ -413,7 +413,7 @@ impl MutableAppContext {
windows: HashMap::new(),
ref_counts: Arc::new(Mutex::new(RefCounts::default())),
background: Arc::new(executor::Background::new()),
scoped_pool: scoped_pool::Pool::new(num_cpus::get()),
thread_pool: scoped_pool::Pool::new(num_cpus::get()),
},
actions: HashMap::new(),
global_actions: HashMap::new(),
@ -1316,7 +1316,7 @@ pub struct AppContext {
windows: HashMap<usize, Window>,
background: Arc<executor::Background>,
ref_counts: Arc<Mutex<RefCounts>>,
scoped_pool: scoped_pool::Pool,
thread_pool: scoped_pool::Pool,
}
impl AppContext {
@ -1356,8 +1356,8 @@ impl AppContext {
&self.background
}
pub fn scoped_pool(&self) -> &scoped_pool::Pool {
&self.scoped_pool
pub fn thread_pool(&self) -> &scoped_pool::Pool {
&self.thread_pool
}
}
@ -1505,6 +1505,10 @@ impl<'a, T: Entity> ModelContext<'a, T> {
&self.app.ctx.background
}
pub fn thread_pool(&self) -> &scoped_pool::Pool {
&self.app.ctx.thread_pool
}
pub fn halt_stream(&mut self) {
self.halt_stream = true;
}

View file

@ -347,7 +347,7 @@ impl FileFinder {
fn spawn_search(&mut self, query: String, ctx: &mut ViewContext<Self>) {
let worktrees = self.worktrees(ctx.as_ref());
let search_id = util::post_inc(&mut self.search_count);
let pool = ctx.as_ref().scoped_pool().clone();
let pool = ctx.as_ref().thread_pool().clone();
let task = ctx.background_executor().spawn(async move {
let matches = match_paths(worktrees.as_slice(), &query, false, false, 100, pool);
(search_id, matches)

View file

@ -13,4 +13,5 @@ mod timer;
mod util;
pub mod watch;
pub mod workspace;
mod worktree;
mod worktree_old;

View file

@ -375,6 +375,12 @@ impl<T: KeyedItem> SumTree<T> {
}
}
impl<T: Item> Default for SumTree<T> {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone, Debug)]
pub enum Node<T: Item> {
Internal {

311
zed/src/worktree.rs Normal file
View file

@ -0,0 +1,311 @@
use crate::sum_tree::{self, Edit, SumTree};
use gpui::{Entity, ModelContext};
use ignore::dir::{Ignore, IgnoreBuilder};
use parking_lot::Mutex;
use smol::channel::Sender;
use std::{
ffi::{OsStr, OsString},
fs, io,
ops::AddAssign,
os::unix::fs::MetadataExt,
path::{Path, PathBuf},
sync::Arc,
};
enum ScanState {
Idle,
Scanning,
}
pub struct Worktree {
path: Arc<Path>,
entries: SumTree<Entry>,
scanner: BackgroundScanner,
scan_state: ScanState,
}
impl Worktree {
fn new(path: impl Into<Arc<Path>>, ctx: &mut ModelContext<Self>) -> Self {
let path = path.into();
let scan_state = smol::channel::unbounded();
let scanner = BackgroundScanner::new(path.clone(), scan_state.0);
let tree = Self {
path,
entries: Default::default(),
scanner,
scan_state: ScanState::Idle,
};
{
let scanner = tree.scanner.clone();
std::thread::spawn(move || scanner.run());
}
tree
}
}
impl Entity for Worktree {
type Event = ();
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum Entry {
Dir {
parent: Option<u64>,
name: Arc<OsStr>,
ino: u64,
is_symlink: bool,
is_ignored: bool,
children: Arc<[u64]>,
pending: bool,
},
File {
parent: Option<u64>,
name: Arc<OsStr>,
ino: u64,
is_symlink: bool,
is_ignored: bool,
},
}
impl Entry {
fn ino(&self) -> u64 {
match self {
Entry::Dir { ino, .. } => *ino,
Entry::File { ino, .. } => *ino,
}
}
}
impl sum_tree::Item for Entry {
type Summary = EntrySummary;
fn summary(&self) -> Self::Summary {
EntrySummary {
max_ino: self.ino(),
file_count: if matches!(self, Self::File { .. }) {
1
} else {
0
},
}
}
}
impl sum_tree::KeyedItem for Entry {
type Key = u64;
fn key(&self) -> Self::Key {
self.ino()
}
}
#[derive(Clone, Debug, Default)]
pub struct EntrySummary {
max_ino: u64,
file_count: usize,
}
impl<'a> AddAssign<&'a EntrySummary> for EntrySummary {
fn add_assign(&mut self, rhs: &'a EntrySummary) {
self.max_ino = rhs.max_ino;
self.file_count += rhs.file_count;
}
}
impl<'a> sum_tree::Dimension<'a, EntrySummary> for u64 {
fn add_summary(&mut self, summary: &'a EntrySummary) {
*self = summary.max_ino;
}
}
#[derive(Clone)]
struct BackgroundScanner {
path: Arc<Path>,
entries: Arc<Mutex<SumTree<Entry>>>,
notify: Sender<ScanState>,
}
impl BackgroundScanner {
fn new(path: Arc<Path>, notify: Sender<ScanState>) -> Self {
Self {
path,
entries: Default::default(),
notify,
}
}
fn run(&self) {
if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() {
return;
}
self.scan_dirs();
if smol::block_on(self.notify.send(ScanState::Idle)).is_err() {
return;
}
// TODO: Update when dir changes
}
fn scan_dirs(&self) -> io::Result<()> {
let metadata = fs::metadata(&self.path)?;
let ino = metadata.ino();
let is_symlink = fs::symlink_metadata(&self.path)?.file_type().is_symlink();
let name = self.path.file_name().unwrap_or(OsStr::new("/")).into();
let relative_path = PathBuf::from(&name);
let mut ignore = IgnoreBuilder::new()
.build()
.add_parents(&self.path)
.unwrap();
if metadata.is_dir() {
ignore = ignore.add_child(&self.path).unwrap();
}
let is_ignored = ignore.matched(&self.path, metadata.is_dir()).is_ignore();
if metadata.file_type().is_dir() {
let is_ignored = is_ignored || name == ".git";
self.insert_entries(Some(Entry::Dir {
parent: None,
name,
ino,
is_symlink,
is_ignored,
children: Arc::from([]),
pending: true,
}));
let (tx, rx) = crossbeam_channel::unbounded();
tx.send(Ok(ScanJob {
ino,
path: path.into(),
relative_path,
ignore: Some(ignore),
scan_queue: tx.clone(),
}))
.unwrap();
drop(tx);
Parallel::<io::Result<()>>::new()
.each(0..16, |_| {
while let Ok(result) = rx.recv() {
self.scan_dir(result?)?;
}
Ok(())
})
.run()
.into_iter()
.collect::<io::Result<()>>()?;
} else {
self.insert_file(None, name, ino, is_symlink, is_ignored, relative_path);
}
self.0.write().root_ino = Some(ino);
Ok(())
}
fn scan_dir(&self, to_scan: ScanJob) -> io::Result<()> {
let mut new_children = Vec::new();
for child_entry in fs::read_dir(&to_scan.path)? {
let child_entry = child_entry?;
let name = child_entry.file_name();
let relative_path = to_scan.relative_path.join(&name);
let metadata = child_entry.metadata()?;
let ino = metadata.ino();
let is_symlink = metadata.file_type().is_symlink();
if metadata.is_dir() {
let path = to_scan.path.join(&name);
let mut is_ignored = true;
let mut ignore = None;
if let Some(parent_ignore) = to_scan.ignore.as_ref() {
let child_ignore = parent_ignore.add_child(&path).unwrap();
is_ignored = child_ignore.matched(&path, true).is_ignore() || name == ".git";
if !is_ignored {
ignore = Some(child_ignore);
}
}
self.insert_entries(
Some(Entry::Dir {
parent: (),
name: (),
ino: (),
is_symlink: (),
is_ignored: (),
children: (),
pending: (),
})
.into_iter(),
);
self.insert_dir(Some(to_scan.ino), name, ino, is_symlink, is_ignored);
new_children.push(ino);
let dirs_to_scan = to_scan.scan_queue.clone();
let _ = to_scan.scan_queue.send(Ok(ScanJob {
ino,
path,
relative_path,
ignore,
scan_queue: dirs_to_scan,
}));
} else {
let is_ignored = to_scan.ignore.as_ref().map_or(true, |i| {
i.matched(to_scan.path.join(&name), false).is_ignore()
});
self.insert_file(
Some(to_scan.ino),
name,
ino,
is_symlink,
is_ignored,
relative_path,
);
new_children.push(ino);
};
}
if let Some(Entry::Dir { children, .. }) = &mut self.0.write().entries.get_mut(&to_scan.ino)
{
*children = new_children.clone();
}
Ok(())
}
fn insert_entries(&self, entries: impl IntoIterator<Item = Entry>) {
self.entries
.lock()
.edit(&mut entries.into_iter().map(Edit::Insert).collect::<Vec<_>>());
}
}
struct ScanJob {
ino: u64,
path: Arc<Path>,
relative_path: PathBuf,
ignore: Option<Ignore>,
scan_queue: crossbeam_channel::Sender<io::Result<ScanJob>>,
}
trait UnwrapIgnoreTuple {
fn unwrap(self) -> Ignore;
}
impl UnwrapIgnoreTuple for (Ignore, Option<ignore::Error>) {
fn unwrap(self) -> Ignore {
if let Some(error) = self.1 {
log::error!("error loading gitignore data: {}", error);
}
self.0
}
}

View file

@ -74,7 +74,7 @@ impl Worktree {
{
let tree = tree.clone();
ctx.as_ref().scoped_pool().spawn(move || {
ctx.as_ref().thread_pool().spawn(move || {
if let Err(error) = tree.scan_dirs() {
log::error!("error scanning worktree: {}", error);
}
@ -725,7 +725,7 @@ mod test {
false,
false,
10,
ctx.scoped_pool().clone(),
ctx.thread_pool().clone(),
)
.iter()
.map(|result| tree.entry_path(result.entry_id))