From 3fa4e5acee94e2e87bc263b8fea2532014bfd963 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Thu, 15 Apr 2021 17:39:32 -0700 Subject: [PATCH] Fill out some missing parts of the new worktree module Co-Authored-By: Nathan Sobo --- zed/src/worktree.rs | 128 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 115 insertions(+), 13 deletions(-) diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index d9a8d1e028..db45fb79c3 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -8,7 +8,7 @@ use crate::{ use anyhow::{anyhow, Result}; pub use fuzzy::match_paths; use fuzzy::PathEntry; -use gpui::{scoped_pool, AppContext, Entity, ModelContext, Task}; +use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task}; use ignore::dir::{Ignore, IgnoreBuilder}; use parking_lot::Mutex; use smol::{channel::Sender, Timer}; @@ -27,6 +27,8 @@ use std::{ time::Duration, }; +pub use fuzzy::PathMatch; + #[derive(Debug)] enum ScanState { Idle, @@ -48,6 +50,12 @@ pub struct Worktree { poll_scheduled: bool, } +#[derive(Clone)] +pub struct FileHandle { + worktree: ModelHandle, + inode: u64, +} + impl Worktree { fn new(path: impl Into>, ctx: &mut ModelContext) -> Self { let path = path.into(); @@ -98,7 +106,7 @@ impl Worktree { } } - fn root_ino(&self) -> Option { + fn root_inode(&self) -> Option { let ino = self.scanner.root_ino.load(atomic::Ordering::SeqCst); if ino == 0 { None @@ -121,21 +129,20 @@ impl Worktree { } fn root_entry(&self) -> Option<&Entry> { - self.root_ino().and_then(|ino| self.entries.get(&ino)) + self.root_inode().and_then(|ino| self.entries.get(&ino)) } fn file_count(&self) -> usize { self.entries.summary().file_count } - fn abs_entry_path(&self, ino: u64) -> Result { + fn abs_path_for_inode(&self, ino: u64) -> Result { let mut result = self.path.to_path_buf(); - result.pop(); - result.push(self.entry_path(ino)?); + result.push(self.path_for_inode(ino, false)?); Ok(result) } - fn entry_path(&self, ino: u64) -> Result { + pub fn path_for_inode(&self, ino: u64, include_root: bool) -> Result { let mut components = Vec::new(); let mut entry = self .entries @@ -147,15 +154,39 @@ impl Worktree { components.push(entry.name()); } + let mut components = components.into_iter().rev(); + if !include_root { + components.next(); + } + let mut path = PathBuf::new(); - for component in components.into_iter().rev() { + for component in components { path.push(component); } Ok(path) } + fn inode_for_path(&self, path: impl AsRef) -> Option { + let path = path.as_ref(); + self.root_inode().and_then(|mut inode| { + 'components: for path_component in path { + if let Some(Entry::Dir { children, .. }) = &self.entries.get(&inode) { + for child in children.as_ref() { + if self.entries.get(child).map(|entry| entry.name()) == Some(path_component) + { + inode = *child; + continue 'components; + } + } + } + return None; + } + Some(inode) + }) + } + pub fn load_file(&self, ino: u64, ctx: &AppContext) -> impl Future> { - let path = self.abs_entry_path(ino); + let path = self.abs_path_for_inode(ino); ctx.background_executor().spawn(async move { let mut file = std::fs::File::open(&path?)?; let mut base_text = String::new(); @@ -165,7 +196,7 @@ impl Worktree { } pub fn save<'a>(&self, ino: u64, content: Snapshot, ctx: &AppContext) -> Task> { - let path = self.abs_entry_path(ino); + let path = self.abs_path_for_inode(ino); eprintln!("save to path: {:?}", path); ctx.background_executor().spawn(async move { let buffer_size = content.text_summary().bytes.min(10 * 1024); @@ -211,7 +242,7 @@ impl Entity for Worktree { impl fmt::Debug for Worktree { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if let Some(root_ino) = self.root_ino() { + if let Some(root_ino) = self.root_inode() { self.fmt_entry(f, root_ino, 0) } else { write!(f, "Empty tree\n") @@ -219,6 +250,28 @@ impl fmt::Debug for Worktree { } } +impl FileHandle { + pub fn path(&self, ctx: &AppContext) -> PathBuf { + self.worktree + .read(ctx) + .path_for_inode(self.inode, false) + .unwrap() + } + + pub fn load(&self, ctx: &AppContext) -> impl Future> { + self.worktree.read(ctx).load_file(self.inode, ctx) + } + + pub fn save<'a>(&self, content: Snapshot, ctx: &AppContext) -> Task> { + let worktree = self.worktree.read(ctx); + worktree.save(self.inode, content, ctx) + } + + pub fn entry_id(&self) -> (usize, u64) { + (self.worktree.id(), self.inode) + } +} + #[derive(Clone, Debug)] pub enum Entry { Dir { @@ -339,6 +392,14 @@ impl BackgroundScanner { } fn run(&self) { + let event_stream = fsevent::EventStream::new( + &[self.path.as_ref()], + Duration::from_millis(100), + |events| { + eprintln!("events: {:?}", events); + }, + ); + if smol::block_on(self.notify.send(ScanState::Scanning)).is_err() { return; } @@ -353,7 +414,7 @@ impl BackgroundScanner { return; } - // TODO: Update when dir changes + event_stream.run(); } fn scan_dirs(&self) -> io::Result<()> { @@ -588,7 +649,7 @@ mod tests { ctx.thread_pool().clone(), ) .iter() - .map(|result| tree.entry_path(result.entry_id)) + .map(|result| tree.path_for_inode(result.entry_id, true)) .collect::, _>>() .unwrap(); assert_eq!( @@ -632,4 +693,45 @@ mod tests { assert_eq!(loaded_text, buffer.text()); }); } + + #[test] + fn test_rescan() { + App::test_async((), |mut app| async move { + let dir = temp_tree(json!({ + "dir1": { + "file": "contents" + }, + "dir2": { + } + })); + + let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx)); + assert_condition(1, 300, || app.read(|ctx| tree.read(ctx).file_count() == 1)).await; + + let file_entry = app.read(|ctx| tree.read(ctx).inode_for_path("dir1/file").unwrap()); + app.read(|ctx| { + let tree = tree.read(ctx); + assert_eq!( + tree.path_for_inode(file_entry, false) + .unwrap() + .to_str() + .unwrap(), + "dir1/file" + ); + }); + + std::fs::rename(dir.path().join("dir1/file"), dir.path().join("dir2/file")).unwrap(); + assert_condition(1, 300, || { + app.read(|ctx| { + let tree = tree.read(ctx); + tree.path_for_inode(file_entry, false) + .unwrap() + .to_str() + .unwrap() + == "dir2/file" + }) + }) + .await + }); + } }