From 218629cdd426f3e4bf16667f3cdab1e940464d7d Mon Sep 17 00:00:00 2001 From: Piotr Osiewicz <24362066+osiewicz@users.noreply.github.com> Date: Fri, 28 Jun 2024 20:23:59 +0200 Subject: [PATCH] language: Memoize value of has_edits_since for a given buffer version (#13656) As a drive-by of https://github.com/zed-industries/zed/pull/13654, I've noticed that the editor felt sluggish after I've undone the changes made by the replacement. It turns out that we are repeatedly checking whether there are any edits to estabilish dirty/conflict state of a buffer, even though this operation is pure; this PR stores away the result of a computation and refers to it before rerunning it. Release Notes: - Improve editor's performance with large undo histories --- crates/language/src/buffer.rs | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/crates/language/src/buffer.rs b/crates/language/src/buffer.rs index 40b8ef6b5e..adba227948 100644 --- a/crates/language/src/buffer.rs +++ b/crates/language/src/buffer.rs @@ -31,6 +31,7 @@ use smallvec::SmallVec; use smol::future::yield_now; use std::{ any::Any, + cell::Cell, cmp::{self, Ordering}, collections::BTreeMap, ffi::OsStr, @@ -113,6 +114,9 @@ pub struct Buffer { capability: Capability, has_conflict: bool, diff_base_version: usize, + /// Memoize calls to has_changes_since(saved_version). + /// The contents of a cell are (self.version, has_changes) at the time of a last call. + has_unsaved_edits: Cell<(clock::Global, bool)>, } /// An immutable, cheaply cloneable representation of a fixed @@ -690,6 +694,7 @@ impl Buffer { reload_task: None, transaction_depth: 0, was_dirty_before_starting_transaction: None, + has_unsaved_edits: Cell::new((buffer.version(), false)), text: buffer, diff_base: diff_base .map(|mut raw_diff_base| { @@ -799,6 +804,8 @@ impl Buffer { cx: &mut ModelContext, ) { self.saved_version = version; + self.has_unsaved_edits + .set((self.saved_version().clone(), false)); self.has_conflict = false; self.saved_mtime = mtime; cx.emit(Event::Saved); @@ -860,6 +867,8 @@ impl Buffer { cx: &mut ModelContext, ) { self.saved_version = version; + self.has_unsaved_edits + .set((self.saved_version.clone(), false)); self.text.set_line_ending(line_ending); self.saved_mtime = mtime; cx.emit(Event::Reloaded); @@ -1516,10 +1525,25 @@ impl Buffer { self.end_transaction(cx) } + fn has_unsaved_edits(&self) -> bool { + let (last_version, has_unsaved_edits) = self.has_unsaved_edits.take(); + + if last_version == self.version { + self.has_unsaved_edits + .set((last_version, has_unsaved_edits)); + return has_unsaved_edits; + } + + let has_edits = self.has_edits_since(&self.saved_version); + self.has_unsaved_edits + .set((self.version.clone(), has_edits)); + has_edits + } + /// Checks if the buffer has unsaved changes. pub fn is_dirty(&self) -> bool { self.has_conflict - || self.has_edits_since(&self.saved_version) + || self.has_unsaved_edits() || self .file .as_ref() @@ -1531,7 +1555,7 @@ impl Buffer { pub fn has_conflict(&self) -> bool { self.has_conflict || self.file.as_ref().map_or(false, |file| { - file.mtime() > self.saved_mtime && self.has_edits_since(&self.saved_version) + file.mtime() > self.saved_mtime && self.has_unsaved_edits() }) }