Reduce amount of workspace serialization happening (#22730)

Kirill Bulatov created

Part of https://github.com/zed-industries/zed/issues/16472

Reduces amount of workspace serialization happening by:
* fixing the deserialization logic: now it does not set panels that are
hidden to active
* cleaning up `active_panel_index` for docks that are closed, to avoid
emitting extra events for such closed docks
* adjusting outline panel to drop active editor subscriptions on
deactivation β€”Β this way, `cx.observe` on the dock with outline panel is
not triggered (used to be triggered on every selection change before)
* adjusting workspace dock drag listener to remember previous
coordinates and only resize the dock if those had changed
* adjusting workspace pane event listener to ignore
`pane::Event::UserSavedItem` and `pane::Event::ChangeItemTitle` that
seem to happen relatively frequently but not influence values that are
serialized for the workspace
* not using `cx.observe` on docks, instead explicitly serializing on
panel zoom and size changes

Release Notes:

- Reduced amount of workspace serialization happening

Change summary

crates/outline_panel/src/outline_panel.rs |  43 ++++++---
crates/workspace/src/dock.rs              |  22 ++++
crates/workspace/src/workspace.rs         | 102 +++++++++++++-----------
3 files changed, 101 insertions(+), 66 deletions(-)

Detailed changes

crates/outline_panel/src/outline_panel.rs πŸ”—

@@ -4656,18 +4656,27 @@ impl Panel for OutlinePanel {
                 .update(&mut cx, |outline_panel, cx| {
                     let old_active = outline_panel.active;
                     outline_panel.active = active;
-                    if active && old_active != active {
-                        if let Some((active_item, active_editor)) = outline_panel
-                            .workspace
-                            .upgrade()
-                            .and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
-                        {
-                            if outline_panel.should_replace_active_item(active_item.as_ref()) {
-                                outline_panel.replace_active_editor(active_item, active_editor, cx);
-                            } else {
-                                outline_panel.update_fs_entries(active_editor, None, cx)
+                    if old_active != active {
+                        if active {
+                            if let Some((active_item, active_editor)) =
+                                outline_panel.workspace.upgrade().and_then(|workspace| {
+                                    workspace_active_editor(workspace.read(cx), cx)
+                                })
+                            {
+                                if outline_panel.should_replace_active_item(active_item.as_ref()) {
+                                    outline_panel.replace_active_editor(
+                                        active_item,
+                                        active_editor,
+                                        cx,
+                                    );
+                                } else {
+                                    outline_panel.update_fs_entries(active_editor, None, cx)
+                                }
+                                return;
                             }
-                        } else if !outline_panel.pinned {
+                        }
+
+                        if !outline_panel.pinned {
                             outline_panel.clear_previous(cx);
                         }
                     }
@@ -4814,9 +4823,11 @@ fn subscribe_for_editor_events(
     cx: &mut ViewContext<OutlinePanel>,
 ) -> Subscription {
     let debounce = Some(UPDATE_DEBOUNCE);
-    cx.subscribe(
-        editor,
-        move |outline_panel, editor, e: &EditorEvent, cx| match e {
+    cx.subscribe(editor, move |outline_panel, editor, e: &EditorEvent, cx| {
+        if !outline_panel.active {
+            return;
+        }
+        match e {
             EditorEvent::SelectionsChanged { local: true } => {
                 outline_panel.reveal_entry_for_selection(editor, cx);
                 cx.notify();
@@ -4921,8 +4932,8 @@ fn subscribe_for_editor_events(
                 outline_panel.update_non_fs_items(cx);
             }
             _ => {}
-        },
-    )
+        }
+    })
 }
 
 fn empty_icon() -> AnyElement {

crates/workspace/src/dock.rs πŸ”—

@@ -170,6 +170,7 @@ impl From<&dyn PanelHandle> for AnyView {
 pub struct Dock {
     position: DockPosition,
     panel_entries: Vec<PanelEntry>,
+    workspace: WeakView<Workspace>,
     is_open: bool,
     active_panel_index: Option<usize>,
     focus_handle: FocusHandle,
@@ -236,6 +237,7 @@ impl Dock {
             });
             Self {
                 position,
+                workspace: workspace.downgrade(),
                 panel_entries: Default::default(),
                 active_panel_index: None,
                 is_open: false,
@@ -337,6 +339,9 @@ impl Dock {
             self.is_open = open;
             if let Some(active_panel) = self.active_panel_entry() {
                 active_panel.panel.set_active(open, cx);
+                if !open {
+                    self.active_panel_index = None;
+                }
             }
 
             cx.notify();
@@ -354,6 +359,11 @@ impl Dock {
             }
         }
 
+        self.workspace
+            .update(cx, |workspace, cx| {
+                workspace.serialize_workspace(cx);
+            })
+            .ok();
         cx.notify();
     }
 
@@ -484,7 +494,8 @@ impl Dock {
             },
         );
 
-        if !self.restore_state(cx) && panel.read(cx).starts_open(cx) {
+        self.restore_state(cx);
+        if panel.read(cx).starts_open(cx) {
             self.activate_panel(index, cx);
             self.set_open(true, cx);
         }
@@ -652,9 +663,14 @@ impl Render for Dock {
                     )
                     .on_mouse_up(
                         MouseButton::Left,
-                        cx.listener(|v, e: &MouseUpEvent, cx| {
+                        cx.listener(|dock, e: &MouseUpEvent, cx| {
                             if e.click_count == 2 {
-                                v.resize_active_panel(None, cx);
+                                dock.resize_active_panel(None, cx);
+                                dock.workspace
+                                    .update(cx, |workspace, cx| {
+                                        workspace.serialize_workspace(cx);
+                                    })
+                                    .ok();
                                 cx.stop_propagation();
                             }
                         }),

crates/workspace/src/workspace.rs πŸ”—

@@ -743,6 +743,7 @@ pub struct Workspace {
     weak_self: WeakView<Self>,
     workspace_actions: Vec<Box<dyn Fn(Div, &mut ViewContext<Self>) -> Div>>,
     zoomed: Option<AnyWeakView>,
+    previous_dock_drag_coordinates: Option<Point<Pixels>>,
     zoomed_position: Option<DockPosition>,
     center: PaneGroup,
     left_dock: View<Dock>,
@@ -1020,18 +1021,6 @@ impl Workspace {
 
                 ThemeSettings::reload_current_theme(cx);
             }),
-            cx.observe(&left_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.observe(&bottom_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
-            cx.observe(&right_dock, |this, _, cx| {
-                this.serialize_workspace(cx);
-                cx.notify();
-            }),
             cx.on_release(|this, window, cx| {
                 this.app_state.workspace_store.update(cx, |store, _| {
                     let window = window.downcast::<Self>().unwrap();
@@ -1047,6 +1036,7 @@ impl Workspace {
             weak_self: weak_handle.clone(),
             zoomed: None,
             zoomed_position: None,
+            previous_dock_drag_coordinates: None,
             center: PaneGroup::new(center_pane.clone()),
             panes: vec![center_pane.clone()],
             panes_by_item: Default::default(),
@@ -3060,6 +3050,7 @@ impl Workspace {
         event: &pane::Event,
         cx: &mut ViewContext<Self>,
     ) {
+        let mut serialize_workspace = true;
         match event {
             pane::Event::AddItem { item } => {
                 item.added_to_pane(self, pane, cx);
@@ -3070,10 +3061,14 @@ impl Workspace {
             pane::Event::Split(direction) => {
                 self.split_and_clone(pane, *direction, cx);
             }
-            pane::Event::JoinIntoNext => self.join_pane_into_next(pane, cx),
-            pane::Event::JoinAll => self.join_all_panes(cx),
+            pane::Event::JoinIntoNext => {
+                self.join_pane_into_next(pane, cx);
+            }
+            pane::Event::JoinAll => {
+                self.join_all_panes(cx);
+            }
             pane::Event::Remove { focus_on_pane } => {
-                self.remove_pane(pane, focus_on_pane.clone(), cx)
+                self.remove_pane(pane, focus_on_pane.clone(), cx);
             }
             pane::Event::ActivateItem { local } => {
                 cx.on_next_frame(|_, cx| {
@@ -3091,16 +3086,20 @@ impl Workspace {
                     self.update_active_view_for_followers(cx);
                 }
             }
-            pane::Event::UserSavedItem { item, save_intent } => cx.emit(Event::UserSavedItem {
-                pane: pane.downgrade(),
-                item: item.boxed_clone(),
-                save_intent: *save_intent,
-            }),
+            pane::Event::UserSavedItem { item, save_intent } => {
+                cx.emit(Event::UserSavedItem {
+                    pane: pane.downgrade(),
+                    item: item.boxed_clone(),
+                    save_intent: *save_intent,
+                });
+                serialize_workspace = false;
+            }
             pane::Event::ChangeItemTitle => {
                 if pane == self.active_pane {
                     self.active_item_path_changed(cx);
                 }
                 self.update_window_edited(cx);
+                serialize_workspace = false;
             }
             pane::Event::RemoveItem { .. } => {}
             pane::Event::RemovedItem { item_id } => {
@@ -3139,7 +3138,9 @@ impl Workspace {
             }
         }
 
-        self.serialize_workspace(cx);
+        if serialize_workspace {
+            self.serialize_workspace(cx);
+        }
     }
 
     pub fn unfollow_in_pane(
@@ -4900,32 +4901,39 @@ impl Render for Workspace {
                                 })
                                 .when(self.zoomed.is_none(), |this| {
                                     this.on_drag_move(cx.listener(
-                                        |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
-                                            match e.drag(cx).0 {
-                                                DockPosition::Left => {
-                                                    resize_left_dock(
-                                                        e.event.position.x
-                                                            - workspace.bounds.left(),
-                                                        workspace,
-                                                        cx,
-                                                    );
-                                                }
-                                                DockPosition::Right => {
-                                                    resize_right_dock(
-                                                        workspace.bounds.right()
-                                                            - e.event.position.x,
-                                                        workspace,
-                                                        cx,
-                                                    );
-                                                }
-                                                DockPosition::Bottom => {
-                                                    resize_bottom_dock(
-                                                        workspace.bounds.bottom()
-                                                            - e.event.position.y,
-                                                        workspace,
-                                                        cx,
-                                                    );
-                                                }
+                                        move |workspace, e: &DragMoveEvent<DraggedDock>, cx| {
+                                            if workspace.previous_dock_drag_coordinates
+                                                != Some(e.event.position)
+                                            {
+                                                workspace.previous_dock_drag_coordinates =
+                                                    Some(e.event.position);
+                                                match e.drag(cx).0 {
+                                                    DockPosition::Left => {
+                                                        resize_left_dock(
+                                                            e.event.position.x
+                                                                - workspace.bounds.left(),
+                                                            workspace,
+                                                            cx,
+                                                        );
+                                                    }
+                                                    DockPosition::Right => {
+                                                        resize_right_dock(
+                                                            workspace.bounds.right()
+                                                                - e.event.position.x,
+                                                            workspace,
+                                                            cx,
+                                                        );
+                                                    }
+                                                    DockPosition::Bottom => {
+                                                        resize_bottom_dock(
+                                                            workspace.bounds.bottom()
+                                                                - e.event.position.y,
+                                                            workspace,
+                                                            cx,
+                                                        );
+                                                    }
+                                                };
+                                                workspace.serialize_workspace(cx);
                                             }
                                         },
                                     ))