Track dirtyness per item (#26237)
This reduces the number of multibuffer syncs when starting the editor with 80 files open in the Zed repo from 10,000,000 to 100,000 by avoiding O(n**2) dirtyness checks. Release Notes: - Fixed a beachball when restarting in a large repo with a large number open files
This commit is contained in:
parent
263d9ff755
commit
f373383fc1
3 changed files with 43 additions and 7 deletions
|
@ -802,6 +802,7 @@ impl<T: Item> ItemHandle for Entity<T> {
|
|||
}
|
||||
|
||||
ItemEvent::UpdateTab => {
|
||||
workspace.update_item_dirty_state(item, window, cx);
|
||||
pane.update(cx, |_, cx| {
|
||||
cx.emit(pane::Event::ChangeItemTitle);
|
||||
cx.notify();
|
||||
|
|
|
@ -826,6 +826,7 @@ pub struct Workspace {
|
|||
follower_states: HashMap<PeerId, FollowerState>,
|
||||
last_leaders_by_pane: HashMap<WeakEntity<Pane>, PeerId>,
|
||||
window_edited: bool,
|
||||
dirty_items: HashMap<EntityId, Subscription>,
|
||||
active_call: Option<(Entity<ActiveCall>, Vec<Subscription>)>,
|
||||
leader_updates_tx: mpsc::UnboundedSender<(PeerId, proto::UpdateFollowers)>,
|
||||
database_id: Option<WorkspaceId>,
|
||||
|
@ -1128,6 +1129,7 @@ impl Workspace {
|
|||
last_leaders_by_pane: Default::default(),
|
||||
dispatching_keystrokes: Default::default(),
|
||||
window_edited: false,
|
||||
dirty_items: Default::default(),
|
||||
active_call,
|
||||
database_id: workspace_id,
|
||||
app_state,
|
||||
|
@ -3395,13 +3397,11 @@ impl Workspace {
|
|||
if *pane == self.active_pane {
|
||||
self.active_item_path_changed(window, cx);
|
||||
}
|
||||
self.update_window_edited(window, cx);
|
||||
serialize_workspace = false;
|
||||
}
|
||||
pane::Event::RemoveItem { .. } => {}
|
||||
pane::Event::RemovedItem { item_id } => {
|
||||
cx.emit(Event::ActiveItemChanged);
|
||||
self.update_window_edited(window, cx);
|
||||
if let hash_map::Entry::Occupied(entry) = self.panes_by_item.entry(*item_id) {
|
||||
if entry.get().entity_id() == pane.entity_id() {
|
||||
entry.remove();
|
||||
|
@ -3858,16 +3858,47 @@ impl Workspace {
|
|||
}
|
||||
|
||||
fn update_window_edited(&mut self, window: &mut Window, cx: &mut App) {
|
||||
let is_edited = !self.project.read(cx).is_disconnected(cx)
|
||||
&& self
|
||||
.items(cx)
|
||||
.any(|item| item.has_conflict(cx) || item.is_dirty(cx));
|
||||
let is_edited = !self.project.read(cx).is_disconnected(cx) && !self.dirty_items.is_empty();
|
||||
if is_edited != self.window_edited {
|
||||
self.window_edited = is_edited;
|
||||
window.set_window_edited(self.window_edited)
|
||||
}
|
||||
}
|
||||
|
||||
fn update_item_dirty_state(
|
||||
&mut self,
|
||||
item: &dyn ItemHandle,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) {
|
||||
let is_dirty = item.is_dirty(cx);
|
||||
let item_id = item.item_id();
|
||||
let was_dirty = self.dirty_items.contains_key(&item_id);
|
||||
if is_dirty == was_dirty {
|
||||
return;
|
||||
}
|
||||
if was_dirty {
|
||||
self.dirty_items.remove(&item_id);
|
||||
self.update_window_edited(window, cx);
|
||||
return;
|
||||
}
|
||||
if let Some(window_handle) = window.window_handle().downcast::<Self>() {
|
||||
let s = item.on_release(
|
||||
cx,
|
||||
Box::new(move |cx| {
|
||||
window_handle
|
||||
.update(cx, |this, window, cx| {
|
||||
this.dirty_items.remove(&item_id);
|
||||
this.update_window_edited(window, cx)
|
||||
})
|
||||
.ok();
|
||||
}),
|
||||
);
|
||||
self.dirty_items.insert(item_id, s);
|
||||
self.update_window_edited(window, cx);
|
||||
}
|
||||
}
|
||||
|
||||
fn render_notifications(&self, _window: &mut Window, _cx: &mut Context<Self>) -> Option<Div> {
|
||||
if self.notifications.is_empty() {
|
||||
None
|
||||
|
|
|
@ -2112,6 +2112,7 @@ mod tests {
|
|||
})
|
||||
.unwrap();
|
||||
assert!(window_is_edited(window, cx));
|
||||
let weak = editor.downgrade();
|
||||
|
||||
// Closing the item restores the window's edited state.
|
||||
let close = window
|
||||
|
@ -2127,11 +2128,12 @@ mod tests {
|
|||
|
||||
cx.simulate_prompt_answer("Don't Save");
|
||||
close.await.unwrap();
|
||||
assert!(!window_is_edited(window, cx));
|
||||
|
||||
// Advance the clock to ensure that the item has been serialized and dropped from the queue
|
||||
cx.executor().advance_clock(Duration::from_secs(1));
|
||||
|
||||
weak.assert_released();
|
||||
assert!(!window_is_edited(window, cx));
|
||||
// Opening the buffer again doesn't impact the window's edited state.
|
||||
cx.update(|cx| {
|
||||
open_paths(
|
||||
|
@ -2266,6 +2268,8 @@ mod tests {
|
|||
assert_eq!(cx.update(|cx| cx.windows().len()), 1);
|
||||
assert!(cx.update(|cx| cx.active_window().is_some()));
|
||||
|
||||
cx.run_until_parked();
|
||||
|
||||
// When opening the workspace, the window is not in a edited state.
|
||||
let window = cx.update(|cx| cx.active_window().unwrap().downcast::<Workspace>().unwrap());
|
||||
assert!(window_is_edited(window, cx));
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue