From 4884a05a731a2b0ed22c226555c8438566785607 Mon Sep 17 00:00:00 2001 From: Antonio Scandurra Date: Thu, 13 May 2021 11:26:11 +0200 Subject: [PATCH] Change buffer atomically when reloading from disk --- zed/src/editor/buffer/mod.rs | 117 ++++++++++++++++++----------------- 1 file changed, 61 insertions(+), 56 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 0bb5fc8ea3..f26ccd50b8 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -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, + 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, ctx: &AppContext) -> Task { + // 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::>(); + Diff { + base_version, + new_text, + changes, + } + }) + } + fn set_text_via_diff( &mut self, - new_text: Arc, + diff: Diff, ctx: &mut ModelContext, - ) -> Task>> { - 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::>() + ) -> Option> { + 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)); }