From 2619bc4602b4122c82d90db5204a5c9e925c64f5 Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Tue, 6 Apr 2021 20:46:35 -0600 Subject: [PATCH] Update modified status by emitting event whenever buffer is dirtied or saved I used the word "dirty" because it felt more expressive than "modified" to me, but not married to it. Tagging Max because we did a lot of this thinking together. Co-Authored-By: Max Brunsfeld --- zed/src/editor/buffer/mod.rs | 34 +++++++++++++++++++---------- zed/src/editor/buffer_view.rs | 14 +++++++----- zed/src/workspace/pane.rs | 2 +- zed/src/workspace/workspace_view.rs | 18 +++++++-------- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/zed/src/editor/buffer/mod.rs b/zed/src/editor/buffer/mod.rs index 2e25a70bae..0f4b985955 100644 --- a/zed/src/editor/buffer/mod.rs +++ b/zed/src/editor/buffer/mod.rs @@ -266,8 +266,12 @@ impl Buffer { ctx.emit(Event::Saved); } - pub fn is_modified(&self) -> bool { - self.fragments.summary().max_version > self.persisted_version + pub fn is_dirty(&self) -> bool { + self.version > self.persisted_version + } + + pub fn version(&self) -> time::Global { + self.version.clone() } pub fn text_summary(&self) -> TextSummary { @@ -414,6 +418,7 @@ impl Buffer { None }; + let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let old_ranges = old_ranges .into_iter() @@ -432,7 +437,7 @@ impl Buffer { ctx.notify(); let changes = self.edits_since(old_version).collect::>(); if !changes.is_empty() { - self.did_edit(changes, ctx); + self.did_edit(changes, was_dirty, ctx); } } @@ -450,8 +455,11 @@ impl Buffer { Ok(ops) } - fn did_edit(&self, changes: Vec, ctx: &mut ModelContext) { - ctx.emit(Event::Edited(changes)) + fn did_edit(&self, changes: Vec, was_dirty: bool, ctx: &mut ModelContext) { + ctx.emit(Event::Edited(changes)); + if !was_dirty { + ctx.emit(Event::Dirtied); + } } pub fn simulate_typing(&mut self, rng: &mut T) { @@ -639,6 +647,7 @@ impl Buffer { ops: I, ctx: Option<&mut ModelContext>, ) -> Result<()> { + let was_dirty = self.is_dirty(); let old_version = self.version.clone(); let mut deferred_ops = Vec::new(); @@ -657,7 +666,7 @@ impl Buffer { ctx.notify(); let changes = self.edits_since(old_version).collect::>(); if !changes.is_empty() { - ctx.emit(Event::Edited(changes)); + self.did_edit(changes, was_dirty, ctx); } } @@ -1416,6 +1425,7 @@ impl Snapshot { #[derive(Clone, Debug, Eq, PartialEq)] pub enum Event { Edited(Vec), + Dirtied, Saved, } @@ -2510,28 +2520,28 @@ mod tests { let model = app.add_model(|_| Buffer::new(0, "abc")); model.update(&mut app, |buffer, ctx| { // initially, buffer isn't modified. - assert!(!buffer.is_modified()); + assert!(!buffer.is_dirty()); // after editing, buffer is modified. buffer.edit(vec![1..2], "", None).unwrap(); assert!(buffer.text() == "ac"); - assert!(buffer.is_modified()); + assert!(buffer.is_dirty()); // after saving, buffer is not modified. - buffer.did_save(ctx); - assert!(!buffer.is_modified()); + buffer.did_save(buffer.version(), ctx); + assert!(!buffer.is_dirty()); // after editing again, buffer is modified. buffer.edit(vec![1..1], "B", None).unwrap(); buffer.edit(vec![2..2], "D", None).unwrap(); assert!(buffer.text() == "aBDc"); - assert!(buffer.is_modified()); + assert!(buffer.is_dirty()); // TODO - currently, after restoring the buffer to its // saved state, it is still considered modified. buffer.edit(vec![1..3], "", None).unwrap(); assert!(buffer.text() == "ac"); - assert!(buffer.is_modified()); + assert!(buffer.is_dirty()); }); }); Ok(()) diff --git a/zed/src/editor/buffer_view.rs b/zed/src/editor/buffer_view.rs index 3057bd4ffc..1c5a07dca7 100644 --- a/zed/src/editor/buffer_view.rs +++ b/zed/src/editor/buffer_view.rs @@ -5,7 +5,7 @@ use super::{ use crate::{ settings::Settings, watch, - workspace::{self, WorkspaceEvent}, + workspace::{self, ItemViewEvent}, }; use anyhow::Result; use futures_core::future::LocalBoxFuture; @@ -1095,6 +1095,7 @@ impl BufferView { ) { match event { buffer::Event::Edited(_) => ctx.emit(Event::Edited), + buffer::Event::Dirtied => ctx.emit(Event::Dirtied), buffer::Event::Saved => ctx.emit(Event::Saved), } } @@ -1111,6 +1112,7 @@ pub enum Event { Activate, Edited, Blurred, + Dirtied, Saved, } @@ -1153,10 +1155,10 @@ impl workspace::Item for Buffer { } impl workspace::ItemView for BufferView { - fn to_workspace_event(event: &Self::Event) -> Option { + fn to_workspace_event(event: &Self::Event) -> Option { match event { - Event::Activate => Some(WorkspaceEvent::Activate), - Event::Saved => Some(WorkspaceEvent::TabStateChanged), + Event::Activate => Some(ItemViewEvent::Activated), + Event::Dirtied | Event::Saved => Some(ItemViewEvent::TabStateChanged), _ => None, } } @@ -1189,8 +1191,8 @@ impl workspace::ItemView for BufferView { self.buffer.update(ctx, |buffer, ctx| buffer.save(ctx)) } - fn is_modified(&self, ctx: &AppContext) -> bool { - self.buffer.as_ref(ctx).is_modified() + fn is_dirty(&self, ctx: &AppContext) -> bool { + self.buffer.as_ref(ctx).is_dirty() } } diff --git a/zed/src/workspace/pane.rs b/zed/src/workspace/pane.rs index e2662eab96..15c8195d31 100644 --- a/zed/src/workspace/pane.rs +++ b/zed/src/workspace/pane.rs @@ -210,7 +210,7 @@ impl Pane { settings.ui_font_family, settings.ui_font_size, ConstrainedBox::new(Self::render_modified_icon( - item.is_modified(app), + item.is_dirty(app), )) .with_max_width(12.) .boxed(), diff --git a/zed/src/workspace/workspace_view.rs b/zed/src/workspace/workspace_view.rs index 5c62139a45..5f7222f292 100644 --- a/zed/src/workspace/workspace_view.rs +++ b/zed/src/workspace/workspace_view.rs @@ -13,13 +13,13 @@ pub fn init(app: &mut App) { app.add_bindings(vec![Binding::new("cmd-s", "workspace:save", None)]); } -pub enum WorkspaceEvent { +pub enum ItemViewEvent { TabStateChanged, - Activate, + Activated, } pub trait ItemView: View { - fn to_workspace_event(event: &Self::Event) -> Option; + fn to_workspace_event(event: &Self::Event) -> Option; fn title(&self, app: &AppContext) -> String; fn entry_id(&self, app: &AppContext) -> Option<(usize, usize)>; fn clone_on_split(&self, _: &mut ViewContext) -> Option @@ -28,7 +28,7 @@ pub trait ItemView: View { { None } - fn is_modified(&self, _: &AppContext) -> bool { + fn is_dirty(&self, _: &AppContext) -> bool { false } fn save(&self, _: &mut ViewContext) -> LocalBoxFuture<'static, anyhow::Result<()>> { @@ -44,7 +44,7 @@ pub trait ItemViewHandle: Send + Sync { fn set_parent_pane(&self, pane: &ViewHandle, app: &mut MutableAppContext); fn id(&self) -> usize; fn to_any(&self) -> AnyViewHandle; - fn is_modified(&self, ctx: &AppContext) -> bool; + fn is_dirty(&self, ctx: &AppContext) -> bool; fn save(&self, ctx: &mut MutableAppContext) -> LocalBoxFuture<'static, anyhow::Result<()>>; } @@ -72,13 +72,13 @@ impl ItemViewHandle for ViewHandle { pane.update(app, |_, ctx| { ctx.subscribe_to_view(self, |pane, item, event, ctx| { match T::to_workspace_event(event) { - Some(WorkspaceEvent::Activate) => { + Some(ItemViewEvent::Activated) => { if let Some(ix) = pane.item_index(&item) { pane.activate_item(ix, ctx); pane.activate(ctx); } } - Some(WorkspaceEvent::TabStateChanged) => ctx.notify(), + Some(ItemViewEvent::TabStateChanged) => ctx.notify(), _ => {} } }) @@ -89,8 +89,8 @@ impl ItemViewHandle for ViewHandle { self.update(ctx, |item, ctx| item.save(ctx)) } - fn is_modified(&self, ctx: &AppContext) -> bool { - self.as_ref(ctx).is_modified(ctx) + fn is_dirty(&self, ctx: &AppContext) -> bool { + self.as_ref(ctx).is_dirty(ctx) } fn id(&self) -> usize {