Redesign Worktree save API and make test_rescan_simple pass

This commit does too much. The first goal was to change our approach to saving new buffers so that we don't need to construct a File for an entry that doesn't exist. Rather than doing that, we call `Worktree::save_buffer_as` with the buffer handle, the path, and the contents. This then saves the buffer and returns a handle to a `File` that references an entry that actually exists. I needed to do this so that we can store an entry id on `File`.

In the process, I noticed intermittent test failures on `test_rescan_simple`, so I made some changes required to fix those related to our reuse of existing ids. Our previous approach of removing a path when inserting a new entry was broken, because of the recursive nature of `remove_path`. Instead, I simply recycle the id of an existing worktree entry with the same path if one is present, then allow it to be replaced.
This commit is contained in:
Nathan Sobo
2021-06-28 19:05:38 -06:00
parent 1793eda470
commit b7a4393f29
4 changed files with 190 additions and 108 deletions
+49 -34
View File
@@ -20,10 +20,10 @@ use crate::{
sum_tree::{self, FilterCursor, SumTree},
time::{self, ReplicaId},
util::Bias,
worktree::File,
worktree::{File, Worktree},
};
use anyhow::{anyhow, Result};
use gpui::{AppContext, Entity, ModelContext, Task};
use gpui::{AppContext, Entity, ModelContext, ModelHandle, Task};
use lazy_static::lazy_static;
use std::{
cell::RefCell,
@@ -561,44 +561,60 @@ impl Buffer {
self.file.as_ref()
}
pub fn save(
&mut self,
new_file: Option<File>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
pub fn save(&mut self, cx: &mut ModelContext<Self>) -> Result<Task<Result<()>>> {
let file = self
.file
.as_ref()
.ok_or_else(|| anyhow!("buffer has no file"))?;
let text = self.visible_text.clone();
let version = self.version.clone();
let file = self.file.clone();
let save = file.save(text, cx.as_mut());
cx.spawn(|handle, mut cx| async move {
if let Some(file) = new_file.as_ref().or(file.as_ref()) {
let result = cx.update(|cx| file.save(text, cx)).await;
if result.is_ok() {
handle.update(&mut cx, |me, cx| me.did_save(version, new_file, cx));
}
result
} else {
Ok(())
}
Ok(cx.spawn(|this, mut cx| async move {
save.await?;
this.update(&mut cx, |this, cx| {
this.did_save(version, cx).unwrap();
});
Ok(())
}))
}
pub fn save_as(
&mut self,
worktree: &ModelHandle<Worktree>,
path: impl Into<Arc<Path>>,
cx: &mut ModelContext<Self>,
) -> Task<Result<()>> {
let handle = cx.handle();
let text = self.visible_text.clone();
let version = self.version.clone();
let save_as = worktree.update(cx, |worktree, cx| {
worktree
.as_local_mut()
.unwrap()
.save_buffer_as(handle, path, text, cx)
});
cx.spawn(|this, mut cx| async move {
save_as.await.map(|new_file| {
this.update(&mut cx, |this, cx| {
this.file = Some(new_file);
this.did_save(version, cx).unwrap();
});
})
})
}
fn did_save(
&mut self,
version: time::Global,
new_file: Option<File>,
cx: &mut ModelContext<Self>,
) {
if let Some(new_file) = new_file {
let buffer = cx.handle();
new_file.buffer_added(buffer, cx.as_mut());
self.file = Some(new_file);
}
if let Some(file) = &self.file {
fn did_save(&mut self, version: time::Global, cx: &mut ModelContext<Self>) -> Result<()> {
if let Some(file) = self.file.as_ref() {
self.saved_mtime = file.mtime(cx.as_ref());
self.saved_version = version;
cx.emit(Event::Saved);
Ok(())
} else {
Err(anyhow!("buffer has no file"))
}
self.saved_version = version;
cx.emit(Event::Saved);
}
pub fn file_was_moved(&mut self, new_path: Arc<Path>, cx: &mut ModelContext<Self>) {
@@ -3051,8 +3067,7 @@ mod tests {
assert!(buffer.is_dirty(cx.as_ref()));
assert_eq!(*events.borrow(), &[Event::Edited, Event::Dirtied]);
events.borrow_mut().clear();
buffer.did_save(buffer.version(), None, cx);
buffer.did_save(buffer.version(), cx).unwrap();
});
// after saving, the buffer is not dirty, and emits a saved event.