Change buffer atomically when reloading from disk

This commit is contained in:
Antonio Scandurra 2021-05-13 11:26:11 +02:00
parent 19f51bc480
commit 4884a05a73

View file

@ -18,7 +18,7 @@ use crate::{
worktree::FileHandle,
};
use anyhow::{anyhow, Result};
use gpui::{Entity, ModelContext, Task};
use gpui::{AppContext, Entity, ModelContext, Task};
use lazy_static::lazy_static;
use rand::prelude::*;
use std::{
@ -274,6 +274,12 @@ impl Edit {
}
}
struct Diff {
base_version: time::Global,
new_text: Arc<str>,
changes: Vec<(ChangeTag, usize)>,
}
#[derive(Clone, Eq, PartialEq, Debug)]
pub struct Insertion {
id: time::Local,
@ -391,18 +397,16 @@ impl Buffer {
});
if let (Ok(history), true) = (history.await, current_version == version)
{
let operations = handle
.update(&mut ctx, |this, ctx| {
this.set_text_via_diff(history.base_text, ctx)
})
let diff = handle
.read_with(&ctx, |this, ctx| this.diff(history.base_text, ctx))
.await;
if operations.is_some() {
handle.update(&mut ctx, |this, ctx| {
handle.update(&mut ctx, |this, ctx| {
if let Some(_ops) = this.set_text_via_diff(diff, ctx) {
this.saved_version = this.version.clone();
this.saved_mtime = file.mtime();
ctx.emit(Event::Reloaded);
});
}
}
});
}
})
.detach();
@ -539,54 +543,53 @@ impl Buffer {
ctx.emit(Event::Saved);
}
fn diff(&self, new_text: Arc<str>, ctx: &AppContext) -> Task<Diff> {
// TODO: it would be nice to not allocate here.
let old_text = self.text();
let base_version = self.version();
ctx.background_executor().spawn(async move {
let changes = TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
.iter_all_changes()
.map(|c| (c.tag(), c.value().len()))
.collect::<Vec<_>>();
Diff {
base_version,
new_text,
changes,
}
})
}
fn set_text_via_diff(
&mut self,
new_text: Arc<str>,
diff: Diff,
ctx: &mut ModelContext<Self>,
) -> Task<Option<Vec<Operation>>> {
let version = self.version.clone();
let old_text = self.text();
ctx.spawn(|handle, mut ctx| async move {
let diff = ctx
.background_executor()
.spawn({
let new_text = new_text.clone();
async move {
TextDiff::from_lines(old_text.as_str(), new_text.as_ref())
.iter_all_changes()
.map(|c| (c.tag(), c.value().len()))
.collect::<Vec<_>>()
) -> Option<Vec<Operation>> {
if self.version == diff.base_version {
self.start_transaction(None).unwrap();
let mut operations = Vec::new();
let mut offset = 0;
for (tag, len) in diff.changes {
let range = offset..(offset + len);
match tag {
ChangeTag::Equal => offset += len,
ChangeTag::Delete => operations
.extend_from_slice(&self.edit(Some(range), "", Some(ctx)).unwrap()),
ChangeTag::Insert => {
operations.extend_from_slice(
&self
.edit(Some(offset..offset), &diff.new_text[range], Some(ctx))
.unwrap(),
);
offset += len;
}
})
.await;
handle.update(&mut ctx, |this, ctx| {
if this.version == version {
this.start_transaction(None).unwrap();
let mut operations = Vec::new();
let mut offset = 0;
for (tag, len) in diff {
let range = offset..(offset + len);
match tag {
ChangeTag::Equal => offset += len,
ChangeTag::Delete => operations
.extend_from_slice(&this.edit(Some(range), "", Some(ctx)).unwrap()),
ChangeTag::Insert => {
operations.extend_from_slice(
&this
.edit(Some(offset..offset), &new_text[range], Some(ctx))
.unwrap(),
);
offset += len;
}
}
}
this.end_transaction(None, Some(ctx)).unwrap();
Some(operations)
} else {
None
}
})
})
}
self.end_transaction(None, Some(ctx)).unwrap();
Some(operations)
} else {
None
}
}
pub fn is_dirty(&self) -> bool {
@ -3278,15 +3281,17 @@ mod tests {
let buffer = app.add_model(|ctx| Buffer::new(0, text, ctx));
let text = "a\nccc\ndddd\nffffff\n";
buffer
.update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
let diff = buffer
.read_with(&app, |b, ctx| b.diff(text.into(), ctx))
.await;
buffer.update(&mut app, |b, ctx| b.set_text_via_diff(diff, ctx));
app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
let text = "a\n1\n\nccc\ndd2dd\nffffff\n";
buffer
.update(&mut app, |b, ctx| b.set_text_via_diff(text.into(), ctx))
let diff = buffer
.read_with(&app, |b, ctx| b.diff(text.into(), ctx))
.await;
buffer.update(&mut app, |b, ctx| b.set_text_via_diff(diff, ctx));
app.read(|ctx| assert_eq!(buffer.read(ctx).text(), text));
}