From 520cbfb9556c1a7a3cd7c99cf425893df91e2db0 Mon Sep 17 00:00:00 2001 From: Max Brunsfeld Date: Wed, 12 May 2021 20:13:27 -0700 Subject: [PATCH] Read file's mtime in background when getting a FileHandle Co-Authored-By: Antonio Scandurra --- zed/src/editor/buffer/mod.rs | 14 ++++-- zed/src/workspace.rs | 23 +++++---- zed/src/worktree.rs | 93 ++++++++++++++++++++---------------- 3 files changed, 73 insertions(+), 57 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index bc672a1ffcccaa5a20c7287f817b8e2f3e5dc7b8..b1f04f6786bc2dc746f54e8e2335690db3c995ef 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -376,7 +376,9 @@ impl Buffer { file: Option, ctx: &mut ModelContext, ) -> Self { + let saved_mtime; if let Some(file) = file.as_ref() { + saved_mtime = file.mtime(); file.observe_from_model(ctx, |this, file, ctx| { let version = this.version.clone(); if this.version == this.saved_version { @@ -408,6 +410,8 @@ impl Buffer { } ctx.emit(Event::FileHandleChanged); }); + } else { + saved_mtime = UNIX_EPOCH; } let mut insertion_splits = HashMap::default(); @@ -472,11 +476,11 @@ impl Buffer { insertion_splits, version: time::Global::new(), saved_version: time::Global::new(), - saved_mtime: UNIX_EPOCH, last_edit: time::Local::default(), undo_map: Default::default(), history, file, + saved_mtime, selections: HashMap::default(), selections_last_update: 0, deferred_ops: OperationQueue::new(), @@ -3073,7 +3077,7 @@ mod tests { tree.flush_fs_events(&app).await; app.read(|ctx| tree.read(ctx).scan_complete()).await; - let file1 = app.read(|ctx| tree.file("file1", ctx)); + let file1 = app.update(|ctx| tree.file("file1", ctx)).await; let buffer1 = app.add_model(|ctx| { Buffer::from_history(0, History::new("abc".into()), Some(file1), ctx) }); @@ -3133,7 +3137,7 @@ mod tests { // When a file is deleted, the buffer is considered dirty. let events = Rc::new(RefCell::new(Vec::new())); - let file2 = app.read(|ctx| tree.file("file2", ctx)); + let file2 = app.update(|ctx| tree.file("file2", ctx)).await; let buffer2 = app.add_model(|ctx: &mut ModelContext| { ctx.subscribe(&ctx.handle(), { let events = events.clone(); @@ -3154,7 +3158,7 @@ mod tests { // When a file is already dirty when deleted, we don't emit a Dirtied event. let events = Rc::new(RefCell::new(Vec::new())); - let file3 = app.read(|ctx| tree.file("file3", ctx)); + let file3 = app.update(|ctx| tree.file("file3", ctx)).await; let buffer3 = app.add_model(|ctx: &mut ModelContext| { ctx.subscribe(&ctx.handle(), { let events = events.clone(); @@ -3185,7 +3189,7 @@ mod tests { app.read(|ctx| tree.read(ctx).scan_complete()).await; let abs_path = dir.path().join("the-file"); - let file = app.read(|ctx| tree.file("the-file", ctx)); + let file = app.update(|ctx| tree.file("the-file", ctx)).await; let buffer = app.add_model(|ctx| { Buffer::from_history(0, History::new(initial_contents.into()), Some(file), ctx) }); diff --git a/zed/src/workspace.rs b/zed/src/workspace.rs index e82d7351e48187613a163d442375186c87a1e314..5c9dc16c92707d8875ad59cc4eb8f981b0f5162e 100644 --- a/zed/src/workspace.rs +++ b/zed/src/workspace.rs @@ -369,6 +369,7 @@ impl Workspace { .map(|(abs_path, file)| { let is_file = bg.spawn(async move { abs_path.is_file() }); ctx.spawn(|this, mut ctx| async move { + let file = file.await; let is_file = is_file.await; this.update(&mut ctx, |this, ctx| { if is_file { @@ -389,14 +390,14 @@ impl Workspace { } } - fn file_for_path(&mut self, abs_path: &Path, ctx: &mut ViewContext) -> FileHandle { + fn file_for_path(&mut self, abs_path: &Path, ctx: &mut ViewContext) -> Task { for tree in self.worktrees.iter() { if let Ok(relative_path) = abs_path.strip_prefix(tree.read(ctx).abs_path()) { - return tree.file(relative_path, ctx.as_ref()); + return tree.file(relative_path, ctx.as_mut()); } } let worktree = self.add_worktree(&abs_path, ctx); - worktree.file(Path::new(""), ctx.as_ref()) + worktree.file(Path::new(""), ctx.as_mut()) } pub fn add_worktree( @@ -497,18 +498,19 @@ impl Workspace { } }; - let file = worktree.file(path.clone(), ctx.as_ref()); + let file = worktree.file(path.clone(), ctx.as_mut()); if let Entry::Vacant(entry) = self.loading_items.entry(entry.clone()) { let (mut tx, rx) = postage::watch::channel(); entry.insert(rx); let replica_id = self.replica_id; - let history = ctx - .background_executor() - .spawn(file.load_history(ctx.as_ref())); ctx.as_mut() .spawn(|mut ctx| async move { - *tx.borrow_mut() = Some(match history.await { + let file = file.await; + let history = ctx.read(|ctx| file.load_history(ctx)); + let history = ctx.background_executor().spawn(history).await; + + *tx.borrow_mut() = Some(match history { Ok(history) => Ok(Box::new(ctx.add_model(|ctx| { Buffer::from_history(replica_id, history, Some(file), ctx) }))), @@ -564,8 +566,9 @@ impl Workspace { ctx.prompt_for_new_path(&start_path, move |path, ctx| { if let Some(path) = path { ctx.spawn(|mut ctx| async move { - let file = - handle.update(&mut ctx, |me, ctx| me.file_for_path(&path, ctx)); + let file = handle + .update(&mut ctx, |me, ctx| me.file_for_path(&path, ctx)) + .await; if let Err(error) = ctx.update(|ctx| item.save(Some(file), ctx)).await { error!("failed to save item: {:?}, ", error); } diff --git a/zed/src/worktree.rs b/zed/src/worktree.rs index 62bb4ae0080e439dce51fdabd0cd48c5dd7fb7a0..59171e115df7dd01587d4ca6b969f1313aeae889 100644 --- a/zed/src/worktree.rs +++ b/zed/src/worktree.rs @@ -9,7 +9,7 @@ use crate::{ use ::ignore::gitignore::Gitignore; use anyhow::{Context, Result}; pub use fuzzy::{match_paths, PathMatch}; -use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, Task}; +use gpui::{scoped_pool, AppContext, Entity, ModelContext, ModelHandle, MutableAppContext, Task}; use lazy_static::lazy_static; use parking_lot::Mutex; use postage::{ @@ -202,14 +202,12 @@ impl Worktree { path: &Path, ctx: &AppContext, ) -> impl Future> { - let handles = self.handles.clone(); let path = path.to_path_buf(); let abs_path = self.absolutize(&path); ctx.background_executor().spawn(async move { let mut file = fs::File::open(&abs_path)?; let mut base_text = String::new(); file.read_to_string(&mut base_text)?; - Self::update_file_handle(&file, &path, &handles)?; Ok(History::new(Arc::from(base_text))) }) } @@ -1228,7 +1226,7 @@ struct UpdateIgnoreStatusJob { } pub trait WorktreeHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> FileHandle; + fn file(&self, path: impl AsRef, app: &mut MutableAppContext) -> Task; #[cfg(test)] fn flush_fs_events<'a>( @@ -1238,36 +1236,51 @@ pub trait WorktreeHandle { } impl WorktreeHandle for ModelHandle { - fn file(&self, path: impl AsRef, app: &AppContext) -> FileHandle { - let path = path.as_ref(); + fn file(&self, path: impl AsRef, app: &mut MutableAppContext) -> Task { + let path = Arc::from(path.as_ref()); + let handle = self.clone(); let tree = self.read(app); - let mut handles = tree.handles.lock(); - let state = if let Some(state) = handles.get(path).and_then(Weak::upgrade) { - state - } else { - let handle_state = if let Some(entry) = tree.entry_for_path(path) { - FileHandleState { - path: entry.path().clone(), - is_deleted: false, - mtime: UNIX_EPOCH, - } - } else { - FileHandleState { - path: path.into(), - is_deleted: !tree.path_is_pending(path), - mtime: UNIX_EPOCH, - } - }; - - let state = Arc::new(Mutex::new(handle_state.clone())); - handles.insert(handle_state.path, Arc::downgrade(&state)); - state - }; + let abs_path = tree.absolutize(&path); + app.spawn(|ctx| async move { + let mtime = ctx + .background_executor() + .spawn(async move { + if let Ok(metadata) = fs::metadata(&abs_path) { + metadata.modified().unwrap() + } else { + UNIX_EPOCH + } + }) + .await; + let state = handle.read_with(&ctx, |tree, _| { + let mut handles = tree.handles.lock(); + if let Some(state) = handles.get(&path).and_then(Weak::upgrade) { + state + } else { + let handle_state = if let Some(entry) = tree.entry_for_path(&path) { + FileHandleState { + path: entry.path().clone(), + is_deleted: false, + mtime, + } + } else { + FileHandleState { + path: path.clone(), + is_deleted: !tree.path_is_pending(path), + mtime, + } + }; - FileHandle { - worktree: self.clone(), - state, - } + let state = Arc::new(Mutex::new(handle_state.clone())); + handles.insert(handle_state.path, Arc::downgrade(&state)); + state + } + }); + FileHandle { + worktree: handle.clone(), + state, + } + }) } // When the worktree's FS event stream sometimes delivers "redundant" events for FS changes that @@ -1525,7 +1538,7 @@ mod tests { let buffer = app.add_model(|ctx| Buffer::new(1, "a line of text.\n".repeat(10 * 1024), ctx)); - let file = app.read(|ctx| tree.file("", ctx)); + let file = app.update(|ctx| tree.file("", ctx)).await; app.update(|ctx| { assert_eq!(file.path().file_name(), None); smol::block_on(file.save(buffer.read(ctx).snapshot(), ctx.as_ref())).unwrap(); @@ -1552,15 +1565,11 @@ mod tests { })); let tree = app.add_model(|ctx| Worktree::new(dir.path(), ctx)); - let (file2, file3, file4, file5, non_existent_file) = app.read(|ctx| { - ( - tree.file("a/file2", ctx), - tree.file("a/file3", ctx), - tree.file("b/c/file4", ctx), - tree.file("b/c/file5", ctx), - tree.file("a/filex", ctx), - ) - }); + let file2 = app.update(|ctx| tree.file("a/file2", ctx)).await; + let file3 = app.update(|ctx| tree.file("a/file3", ctx)).await; + let file4 = app.update(|ctx| tree.file("b/c/file4", ctx)).await; + let file5 = app.update(|ctx| tree.file("b/c/file5", ctx)).await; + let non_existent_file = app.update(|ctx| tree.file("a/file_x", ctx)).await; // The worktree hasn't scanned the directories containing these paths, // so it can't determine that the paths are deleted.