Fix `Workspace` references being leaked (#17497)

Thorsten Ball and Bennet created

We noticed that the `Workspace` was never released (along with the
`Project` and everything that comes along with that) when closing a
window.

After playing around with the LeakDetector and debugging with
`cx.on_release()` callbacks, we found two culprits: the inline assistant
and the outline panel.

Both held strong references to `View<Workspace>` after PR #16589 and PR
#16845.

This PR changes both references to `WeakView<Workspace>` which fixes the
leak but keeps the behaviour the same.

Release Notes:

- N/A

Co-authored-by: Bennet <bennet@zed.dev>

Change summary

crates/assistant/src/inline_assistant.rs  |  5 +
crates/outline_panel/src/outline_panel.rs | 57 ++++++++++++++----------
2 files changed, 38 insertions(+), 24 deletions(-)

Detailed changes

crates/assistant/src/inline_assistant.rs 🔗

@@ -134,8 +134,11 @@ impl InlineAssistant {
         })
         .detach();
 
-        let workspace = workspace.clone();
+        let workspace = workspace.downgrade();
         cx.observe_global::<SettingsStore>(move |cx| {
+            let Some(workspace) = workspace.upgrade() else {
+                return;
+            };
             let Some(terminal_panel) = workspace.read(cx).panel::<TerminalPanel>(cx) else {
                 return;
             };

crates/outline_panel/src/outline_panel.rs 🔗

@@ -91,7 +91,7 @@ pub struct OutlinePanel {
     fs: Arc<dyn Fs>,
     width: Option<Pixels>,
     project: Model<Project>,
-    workspace: View<Workspace>,
+    workspace: WeakView<Workspace>,
     active: bool,
     pinned: bool,
     scroll_handle: UniformListScrollHandle,
@@ -607,7 +607,7 @@ impl OutlinePanel {
 
     fn new(workspace: &mut Workspace, cx: &mut ViewContext<Workspace>) -> View<Self> {
         let project = workspace.project().clone();
-        let workspace_handle = cx.view().clone();
+        let workspace_handle = cx.view().downgrade();
         let outline_panel = cx.new_view(|cx| {
             let filter_editor = cx.new_view(|cx| {
                 let mut editor = Editor::single_line(cx);
@@ -865,7 +865,8 @@ impl OutlinePanel {
         };
 
         if let Some((offset, anchor)) = scroll_target {
-            self.workspace
+            let activate = self
+                .workspace
                 .update(cx, |workspace, cx| match self.active_item() {
                     Some(active_item) => {
                         workspace.activate_item(active_item.as_ref(), true, change_selection, cx)
@@ -873,21 +874,23 @@ impl OutlinePanel {
                     None => workspace.activate_item(&active_editor, true, change_selection, cx),
                 });
 
-            self.select_entry(entry.clone(), true, cx);
-            if change_selection {
-                active_editor.update(cx, |editor, cx| {
-                    editor.change_selections(
-                        Some(Autoscroll::Strategy(AutoscrollStrategy::Top)),
-                        cx,
-                        |s| s.select_ranges(Some(anchor..anchor)),
-                    );
-                });
-                active_editor.focus_handle(cx).focus(cx);
-            } else {
-                active_editor.update(cx, |editor, cx| {
-                    editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, cx);
-                });
-                self.focus_handle.focus(cx);
+            if activate.is_ok() {
+                self.select_entry(entry.clone(), true, cx);
+                if change_selection {
+                    active_editor.update(cx, |editor, cx| {
+                        editor.change_selections(
+                            Some(Autoscroll::Strategy(AutoscrollStrategy::Top)),
+                            cx,
+                            |s| s.select_ranges(Some(anchor..anchor)),
+                        );
+                    });
+                    active_editor.focus_handle(cx).focus(cx);
+                } else {
+                    active_editor.update(cx, |editor, cx| {
+                        editor.set_scroll_anchor(ScrollAnchor { offset, anchor }, cx);
+                    });
+                    self.focus_handle.focus(cx);
+                }
             }
         }
     }
@@ -3320,7 +3323,11 @@ impl OutlinePanel {
         let buffer_search = self
             .active_item()
             .as_deref()
-            .and_then(|active_item| self.workspace.read(cx).pane_for(active_item))
+            .and_then(|active_item| {
+                self.workspace
+                    .upgrade()
+                    .and_then(|workspace| workspace.read(cx).pane_for(active_item))
+            })
             .and_then(|pane| {
                 pane.read(cx)
                     .toolbar()
@@ -3572,8 +3579,10 @@ impl OutlinePanel {
     ) {
         self.pinned = !self.pinned;
         if !self.pinned {
-            if let Some((active_item, active_editor)) =
-                workspace_active_editor(self.workspace.read(cx), cx)
+            if let Some((active_item, active_editor)) = self
+                .workspace
+                .upgrade()
+                .and_then(|workspace| workspace_active_editor(workspace.read(cx), cx))
             {
                 if self.should_replace_active_item(active_item.as_ref()) {
                     self.replace_active_editor(active_item, active_editor, cx);
@@ -3839,8 +3848,10 @@ impl Panel for OutlinePanel {
                     let old_active = outline_panel.active;
                     outline_panel.active = active;
                     if active && old_active != active {
-                        if let Some((active_item, active_editor)) =
-                            workspace_active_editor(outline_panel.workspace.read(cx), cx)
+                        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);