Merge pull request #2414 from zed-industries/spawn-weak-for-views

Nathan Sobo created

Pass a `WeakViewHandle` to `ViewContext::spawn`

Change summary

crates/activity_indicator/src/activity_indicator.rs |  22 
crates/auto_update/src/auto_update.rs               |  20 
crates/client/src/client.rs                         |  30 -
crates/collab_ui/src/collab_ui.rs                   |   4 
crates/collab_ui/src/incoming_call_notification.rs  |   2 
crates/context_menu/src/context_menu.rs             |   4 
crates/editor/src/editor.rs                         | 141 ++++----
crates/editor/src/hover_popover.rs                  |  49 +-
crates/editor/src/items.rs                          |  11 
crates/editor/src/link_go_to_definition.rs          | 120 +++---
crates/editor/src/scroll.rs                         |  16 
crates/feedback/src/feedback_editor.rs              |   3 
crates/gpui/src/app.rs                              |  97 ++---
crates/gpui/src/app/test_app_context.rs             |   2 
crates/gpui/src/app/window.rs                       |   2 
crates/gpui/src/elements/tooltip.rs                 |   6 
crates/journal/src/journal.rs                       |   2 
crates/language_selector/src/language_selector.rs   |  24 
crates/picker/src/picker.rs                         |   5 
crates/project_symbols/src/project_symbols.rs       |  61 +--
crates/search/src/buffer_search.rs                  |  53 +-
crates/terminal_view/src/terminal_view.rs           |  43 +-
crates/theme_selector/src/theme_selector.rs         |  22 
crates/welcome/src/base_keymap_picker.rs            |  20 
crates/workspace/src/dock.rs                        |   2 
crates/workspace/src/item.rs                        |   6 
crates/workspace/src/pane.rs                        | 134 ++++----
crates/workspace/src/persistence/model.rs           |  46 +-
crates/workspace/src/workspace.rs                   | 232 +++++++-------
crates/zed/src/main.rs                              |  16 
crates/zed/src/zed.rs                               |  92 +++--
31 files changed, 606 insertions(+), 681 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -63,20 +63,16 @@ impl ActivityIndicator {
         let auto_updater = AutoUpdater::get(cx);
         let this = cx.add_view(|cx: &mut ViewContext<Self>| {
             let mut status_events = languages.language_server_binary_statuses();
-            cx.spawn_weak(|this, mut cx| async move {
+            cx.spawn(|this, mut cx| async move {
                 while let Some((language, event)) = status_events.next().await {
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| {
-                            this.statuses.retain(|s| s.name != language.name());
-                            this.statuses.push(LspStatus {
-                                name: language.name(),
-                                status: event,
-                            });
-                            cx.notify();
-                        })?;
-                    } else {
-                        break;
-                    }
+                    this.update(&mut cx, |this, cx| {
+                        this.statuses.retain(|s| s.name != language.name());
+                        this.statuses.push(LspStatus {
+                            name: language.name(),
+                            status: event,
+                        });
+                        cx.notify();
+                    })?;
                 }
                 anyhow::Ok(())
             })

crates/auto_update/src/auto_update.rs 🔗

@@ -104,17 +104,15 @@ pub fn notify_of_any_new_update(
     cx.spawn(|mut cx| async move {
         let should_show_notification = should_show_notification.await?;
         if should_show_notification {
-            if let Some(workspace) = workspace.upgrade(&cx) {
-                workspace.update(&mut cx, |workspace, cx| {
-                    workspace.show_notification(0, cx, |cx| {
-                        cx.add_view(|_| UpdateNotification::new(version))
-                    });
-                    updater
-                        .read(cx)
-                        .set_should_show_update_notification(false, cx)
-                        .detach_and_log_err(cx);
-                })?;
-            }
+            workspace.update(&mut cx, |workspace, cx| {
+                workspace.show_notification(0, cx, |cx| {
+                    cx.add_view(|_| UpdateNotification::new(version))
+                });
+                updater
+                    .read(cx)
+                    .set_should_show_update_notification(false, cx)
+                    .detach_and_log_err(cx);
+            })?;
         }
         anyhow::Ok(())
     })

crates/client/src/client.rs 🔗

@@ -18,8 +18,8 @@ use gpui::{
     actions,
     platform::AppVersion,
     serde_json::{self, Value},
-    AnyModelHandle, AnyViewHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext,
-    AsyncAppContext, Entity, ModelHandle, Task, View, ViewContext, ViewHandle,
+    AnyModelHandle, AnyWeakModelHandle, AnyWeakViewHandle, AppContext, AsyncAppContext, Entity,
+    ModelHandle, Task, View, ViewContext, WeakViewHandle,
 };
 use lazy_static::lazy_static;
 use parking_lot::RwLock;
@@ -221,7 +221,7 @@ enum WeakSubscriber {
 
 enum Subscriber {
     Model(AnyModelHandle),
-    View(AnyViewHandle),
+    View(AnyWeakViewHandle),
 }
 
 #[derive(Clone, Debug)]
@@ -567,7 +567,7 @@ impl Client {
         H: 'static
             + Send
             + Sync
-            + Fn(ViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+            + Fn(WeakViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<()>>,
     {
         self.add_entity_message_handler::<M, E, _, _>(move |handle, message, client, cx| {
@@ -666,7 +666,7 @@ impl Client {
         H: 'static
             + Send
             + Sync
-            + Fn(ViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
+            + Fn(WeakViewHandle<E>, TypedEnvelope<M>, Arc<Self>, AsyncAppContext) -> F,
         F: 'static + Future<Output = Result<M::Response>>,
     {
         self.add_view_message_handler(move |entity, envelope, client, cx| {
@@ -1273,7 +1273,15 @@ impl Client {
                     pending.push(message);
                     return;
                 }
-                Some(weak_subscriber @ _) => subscriber = weak_subscriber.upgrade(cx),
+                Some(weak_subscriber @ _) => match weak_subscriber {
+                    WeakSubscriber::Model(handle) => {
+                        subscriber = handle.upgrade(cx).map(Subscriber::Model);
+                    }
+                    WeakSubscriber::View(handle) => {
+                        subscriber = Some(Subscriber::View(handle.clone()));
+                    }
+                    WeakSubscriber::Pending(_) => {}
+                },
                 _ => {}
             }
         }
@@ -1357,16 +1365,6 @@ impl Client {
     }
 }
 
-impl WeakSubscriber {
-    fn upgrade(&self, cx: &AsyncAppContext) -> Option<Subscriber> {
-        match self {
-            WeakSubscriber::Model(handle) => handle.upgrade(cx).map(Subscriber::Model),
-            WeakSubscriber::View(handle) => handle.upgrade(cx).map(Subscriber::View),
-            WeakSubscriber::Pending(_) => None,
-        }
-    }
-}
-
 fn read_credentials_from_keychain(cx: &AsyncAppContext) -> Option<Credentials> {
     if IMPERSONATE_LOGIN.is_some() {
         return None;

crates/collab_ui/src/collab_ui.rs 🔗

@@ -61,7 +61,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppCont
         });
 
         let workspace = if let Some(existing_workspace) = existing_workspace {
-            existing_workspace
+            existing_workspace.downgrade()
         } else {
             let active_call = cx.read(ActiveCall::global);
             let room = active_call
@@ -93,7 +93,7 @@ fn join_project(action: &JoinProject, app_state: Arc<AppState>, cx: &mut AppCont
                     workspace
                 },
             );
-            workspace
+            workspace.downgrade()
         };
 
         cx.activate_window(workspace.window_id());

crates/collab_ui/src/incoming_call_notification.rs 🔗

@@ -79,7 +79,7 @@ impl IncomingCallNotification {
             let join = active_call.update(cx, |active_call, cx| active_call.accept_incoming(cx));
             let caller_user_id = self.call.calling_user.id;
             let initial_project_id = self.call.initial_project.as_ref().map(|project| project.id);
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(|_, mut cx| async move {
                 join.await?;
                 if let Some(project_id) = initial_project_id {
                     cx.update(|cx| {

crates/context_menu/src/context_menu.rs 🔗

@@ -1,4 +1,5 @@
 use gpui::{
+    anyhow,
     elements::*,
     geometry::vector::Vector2F,
     impl_internal_actions,
@@ -209,7 +210,8 @@ impl ContextMenu {
                 cx.notify();
                 cx.spawn(|this, mut cx| async move {
                     cx.background().timer(Duration::from_millis(50)).await;
-                    this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))
+                    this.update(&mut cx, |this, cx| this.cancel(&Default::default(), cx))?;
+                    anyhow::Ok(())
                 })
                 .detach_and_log_err(cx);
             }

crates/editor/src/editor.rs 🔗

@@ -2477,7 +2477,7 @@ impl Editor {
         });
 
         let id = post_inc(&mut self.next_completion_id);
-        let task = cx.spawn_weak(|this, mut cx| {
+        let task = cx.spawn(|this, mut cx| {
             async move {
                 let menu = if let Some(completions) = completions.await.log_err() {
                     let mut menu = CompletionsMenu {
@@ -2510,9 +2510,6 @@ impl Editor {
                     None
                 };
 
-                let this = this
-                    .upgrade(&cx)
-                    .ok_or_else(|| anyhow!("editor was dropped"))?;
                 this.update(&mut cx, |this, cx| {
                     this.completion_tasks.retain(|(task_id, _)| *task_id > id);
 
@@ -2669,33 +2666,28 @@ impl Editor {
 
         let deployed_from_indicator = action.deployed_from_indicator;
         let mut task = self.code_actions_task.take();
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             while let Some(prev_task) = task {
                 prev_task.await;
-                task = this
-                    .upgrade(&cx)
-                    .ok_or_else(|| anyhow!("editor dropped"))?
-                    .update(&mut cx, |this, _| this.code_actions_task.take())?;
+                task = this.update(&mut cx, |this, _| this.code_actions_task.take())?;
             }
 
-            this.upgrade(&cx)
-                .ok_or_else(|| anyhow!("editor dropped"))?
-                .update(&mut cx, |this, cx| {
-                    if this.focused {
-                        if let Some((buffer, actions)) = this.available_code_actions.clone() {
-                            this.show_context_menu(
-                                ContextMenu::CodeActions(CodeActionsMenu {
-                                    buffer,
-                                    actions,
-                                    selected_item: Default::default(),
-                                    list: Default::default(),
-                                    deployed_from_indicator,
-                                }),
-                                cx,
-                            );
-                        }
+            this.update(&mut cx, |this, cx| {
+                if this.focused {
+                    if let Some((buffer, actions)) = this.available_code_actions.clone() {
+                        this.show_context_menu(
+                            ContextMenu::CodeActions(CodeActionsMenu {
+                                buffer,
+                                actions,
+                                selected_item: Default::default(),
+                                list: Default::default(),
+                                deployed_from_indicator,
+                            }),
+                            cx,
+                        );
                     }
-                })?;
+                }
+            })?;
 
             Ok::<_, anyhow::Error>(())
         })
@@ -2723,15 +2715,16 @@ impl Editor {
         let apply_code_actions = workspace.project().clone().update(cx, |project, cx| {
             project.apply_code_action(buffer, action, true, cx)
         });
+        let editor = editor.downgrade();
         Some(cx.spawn(|workspace, cx| async move {
             let project_transaction = apply_code_actions.await?;
-            Self::open_project_transaction(editor, workspace, project_transaction, title, cx).await
+            Self::open_project_transaction(&editor, workspace, project_transaction, title, cx).await
         }))
     }
 
     async fn open_project_transaction(
-        this: ViewHandle<Editor>,
-        workspace: ViewHandle<Workspace>,
+        this: &WeakViewHandle<Editor>,
+        workspace: WeakViewHandle<Workspace>,
         transaction: ProjectTransaction,
         title: String,
         mut cx: AsyncAppContext,
@@ -2826,21 +2819,19 @@ impl Editor {
         let actions = project.update(cx, |project, cx| {
             project.code_actions(&start_buffer, start..end, cx)
         });
-        self.code_actions_task = Some(cx.spawn_weak(|this, mut cx| async move {
+        self.code_actions_task = Some(cx.spawn(|this, mut cx| async move {
             let actions = actions.await;
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
-                    this.available_code_actions = actions.log_err().and_then(|actions| {
-                        if actions.is_empty() {
-                            None
-                        } else {
-                            Some((start_buffer, actions.into()))
-                        }
-                    });
-                    cx.notify();
-                })
-                .log_err();
-            }
+            this.update(&mut cx, |this, cx| {
+                this.available_code_actions = actions.log_err().and_then(|actions| {
+                    if actions.is_empty() {
+                        None
+                    } else {
+                        Some((start_buffer, actions.into()))
+                    }
+                });
+                cx.notify();
+            })
+            .log_err();
         }));
         None
     }
@@ -2865,9 +2856,8 @@ impl Editor {
             project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
         });
 
-        self.document_highlights_task = Some(cx.spawn_weak(|this, mut cx| async move {
-            let highlights = highlights.log_err().await;
-            if let Some((this, highlights)) = this.upgrade(&cx).zip(highlights) {
+        self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
+            if let Some(highlights) = highlights.await.log_err() {
                 this.update(&mut cx, |this, cx| {
                     if this.pending_rename.is_some() {
                         return;
@@ -2961,7 +2951,7 @@ impl Editor {
 
         let (buffer, buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
-        self.copilot_state.pending_refresh = cx.spawn_weak(|this, mut cx| async move {
+        self.copilot_state.pending_refresh = cx.spawn(|this, mut cx| async move {
             if debounce {
                 cx.background().timer(COPILOT_DEBOUNCE_TIMEOUT).await;
             }
@@ -2976,21 +2966,20 @@ impl Editor {
                 .flatten()
                 .collect_vec();
 
-            this.upgrade(&cx)?
-                .update(&mut cx, |this, cx| {
-                    if !completions.is_empty() {
-                        this.copilot_state.cycled = false;
-                        this.copilot_state.pending_cycling_refresh = Task::ready(None);
-                        this.copilot_state.completions.clear();
-                        this.copilot_state.active_completion_index = 0;
-                        this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
-                        for completion in completions {
-                            this.copilot_state.push_completion(completion);
-                        }
-                        this.update_visible_copilot_suggestion(cx);
+            this.update(&mut cx, |this, cx| {
+                if !completions.is_empty() {
+                    this.copilot_state.cycled = false;
+                    this.copilot_state.pending_cycling_refresh = Task::ready(None);
+                    this.copilot_state.completions.clear();
+                    this.copilot_state.active_completion_index = 0;
+                    this.copilot_state.excerpt_id = Some(cursor.excerpt_id);
+                    for completion in completions {
+                        this.copilot_state.push_completion(completion);
                     }
-                })
-                .log_err()?;
+                    this.update_visible_copilot_suggestion(cx);
+                }
+            })
+            .log_err()?;
             Some(())
         });
 
@@ -3014,23 +3003,22 @@ impl Editor {
             let cursor = self.selections.newest_anchor().head();
             let (buffer, buffer_position) =
                 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
-            self.copilot_state.pending_cycling_refresh = cx.spawn_weak(|this, mut cx| async move {
+            self.copilot_state.pending_cycling_refresh = cx.spawn(|this, mut cx| async move {
                 let completions = copilot
                     .update(&mut cx, |copilot, cx| {
                         copilot.completions_cycling(&buffer, buffer_position, cx)
                     })
                     .await;
 
-                this.upgrade(&cx)?
-                    .update(&mut cx, |this, cx| {
-                        this.copilot_state.cycled = true;
-                        for completion in completions.log_err().into_iter().flatten() {
-                            this.copilot_state.push_completion(completion);
-                        }
-                        this.copilot_state.cycle_completions(direction);
-                        this.update_visible_copilot_suggestion(cx);
-                    })
-                    .log_err()?;
+                this.update(&mut cx, |this, cx| {
+                    this.copilot_state.cycled = true;
+                    for completion in completions.log_err().into_iter().flatten() {
+                        this.copilot_state.push_completion(completion);
+                    }
+                    this.copilot_state.cycle_completions(direction);
+                    this.update_visible_copilot_suggestion(cx);
+                })
+                .log_err()?;
 
                 Some(())
             });
@@ -5956,10 +5944,11 @@ impl Editor {
             project.perform_rename(buffer.clone(), range.start, new_name.clone(), true, cx)
         });
 
+        let editor = editor.downgrade();
         Some(cx.spawn(|workspace, mut cx| async move {
             let project_transaction = rename.await?;
             Self::open_project_transaction(
-                editor.clone(),
+                &editor,
                 workspace,
                 project_transaction,
                 format!("Rename: {} → {}", old_name, new_name),
@@ -6770,11 +6759,12 @@ impl Editor {
         let editor = workspace.open_path(action.path.clone(), None, true, cx);
         let position = action.position;
         let anchor = action.anchor;
-        cx.spawn_weak(|_, mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             let editor = editor
                 .await?
                 .downcast::<Editor>()
-                .ok_or_else(|| anyhow!("opened item was not an editor"))?;
+                .ok_or_else(|| anyhow!("opened item was not an editor"))?
+                .downgrade();
             editor.update(&mut cx, |editor, cx| {
                 let buffer = editor
                     .buffer()
@@ -6796,6 +6786,7 @@ impl Editor {
 
                 anyhow::Ok(())
             })??;
+
             anyhow::Ok(())
         })
         .detach_and_log_err(cx);

crates/editor/src/hover_popover.rs 🔗

@@ -149,7 +149,7 @@ fn show_hover(
         }
     }
 
-    let task = cx.spawn_weak(|this, mut cx| {
+    let task = cx.spawn(|this, mut cx| {
         async move {
             // If we need to delay, delay a set amount initially before making the lsp request
             let delay = if !ignore_timeout {
@@ -201,15 +201,13 @@ fn show_hover(
                     })
             });
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, _| {
-                    this.hover_state.diagnostic_popover =
-                        local_diagnostic.map(|local_diagnostic| DiagnosticPopover {
-                            local_diagnostic,
-                            primary_diagnostic,
-                        });
-                })?;
-            }
+            this.update(&mut cx, |this, _| {
+                this.hover_state.diagnostic_popover =
+                    local_diagnostic.map(|local_diagnostic| DiagnosticPopover {
+                        local_diagnostic,
+                        primary_diagnostic,
+                    });
+            })?;
 
             // Construct new hover popover from hover request
             let hover_popover = hover_request.await.ok().flatten().and_then(|hover_result| {
@@ -239,23 +237,22 @@ fn show_hover(
                 })
             });
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
-                    if let Some(hover_popover) = hover_popover.as_ref() {
-                        // Highlight the selected symbol using a background highlight
-                        this.highlight_background::<HoverState>(
-                            vec![hover_popover.symbol_range.clone()],
-                            |theme| theme.editor.hover_popover.highlight,
-                            cx,
-                        );
-                    } else {
-                        this.clear_background_highlights::<HoverState>(cx);
-                    }
+            this.update(&mut cx, |this, cx| {
+                if let Some(hover_popover) = hover_popover.as_ref() {
+                    // Highlight the selected symbol using a background highlight
+                    this.highlight_background::<HoverState>(
+                        vec![hover_popover.symbol_range.clone()],
+                        |theme| theme.editor.hover_popover.highlight,
+                        cx,
+                    );
+                } else {
+                    this.clear_background_highlights::<HoverState>(cx);
+                }
+
+                this.hover_state.info_popover = hover_popover;
+                cx.notify();
+            })?;
 
-                    this.hover_state.info_popover = hover_popover;
-                    cx.notify();
-                })?;
-            }
             Ok::<_, anyhow::Error>(())
         }
         .log_err()

crates/editor/src/items.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     movement::surrounding_word, persistence::DB, scroll::ScrollAnchor, Anchor, Autoscroll, Editor,
     Event, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, NavigationData, ToPoint as _,
 };
-use anyhow::{Context, Result};
+use anyhow::{anyhow, Context, Result};
 use collections::HashSet;
 use futures::future::try_join_all;
 use gpui::{
@@ -67,6 +67,7 @@ impl FollowableItem for Editor {
                 .collect::<Vec<_>>()
         });
 
+        let pane = pane.downgrade();
         Some(cx.spawn(|mut cx| async move {
             let mut buffers = futures::future::try_join_all(buffers).await?;
             let editor = pane.read_with(&cx, |pane, cx| {
@@ -127,7 +128,7 @@ impl FollowableItem for Editor {
             };
 
             update_editor_from_message(
-                editor.clone(),
+                editor.downgrade(),
                 project,
                 proto::update_view::Editor {
                     selections: state.selections,
@@ -301,7 +302,7 @@ impl FollowableItem for Editor {
 }
 
 async fn update_editor_from_message(
-    this: ViewHandle<Editor>,
+    this: WeakViewHandle<Editor>,
     project: ModelHandle<Project>,
     message: proto::update_view::Editor,
     cx: &mut AsyncAppContext,
@@ -863,7 +864,9 @@ impl Item for Editor {
                     let buffer = project_item
                         .downcast::<Buffer>()
                         .context("Project item at stored path was not a buffer")?;
-
+                    let pane = pane
+                        .upgrade(&cx)
+                        .ok_or_else(|| anyhow!("pane was dropped"))?;
                     Ok(cx.update(|cx| {
                         cx.add_view(&pane, |cx| {
                             let mut editor = Editor::for_buffer(buffer, Some(project), cx);
@@ -171,7 +171,7 @@ pub fn show_link_definition(
         }
     }
 
-    let task = cx.spawn_weak(|this, mut cx| {
+    let task = cx.spawn(|this, mut cx| {
         async move {
             // query the LSP for definition info
             let definition_request = cx.update(|cx| {
@@ -202,67 +202,65 @@ pub fn show_link_definition(
                 )
             });
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
-                    // Clear any existing highlights
-                    this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
-                    this.link_go_to_definition_state.kind = Some(definition_kind);
-                    this.link_go_to_definition_state.symbol_range = result
-                        .as_ref()
-                        .and_then(|(symbol_range, _)| symbol_range.clone());
-
-                    if let Some((symbol_range, definitions)) = result {
-                        this.link_go_to_definition_state.definitions = definitions.clone();
-
-                        let buffer_snapshot = buffer.read(cx).snapshot();
-
-                        // Only show highlight if there exists a definition to jump to that doesn't contain
-                        // the current location.
-                        let any_definition_does_not_contain_current_location =
-                            definitions.iter().any(|definition| {
-                                let target = &definition.target;
-                                if target.buffer == buffer {
-                                    let range = &target.range;
-                                    // Expand range by one character as lsp definition ranges include positions adjacent
-                                    // but not contained by the symbol range
-                                    let start = buffer_snapshot.clip_offset(
-                                        range.start.to_offset(&buffer_snapshot).saturating_sub(1),
-                                        Bias::Left,
-                                    );
-                                    let end = buffer_snapshot.clip_offset(
-                                        range.end.to_offset(&buffer_snapshot) + 1,
-                                        Bias::Right,
-                                    );
-                                    let offset = buffer_position.to_offset(&buffer_snapshot);
-                                    !(start <= offset && end >= offset)
-                                } else {
-                                    true
-                                }
-                            });
-
-                        if any_definition_does_not_contain_current_location {
-                            // If no symbol range returned from language server, use the surrounding word.
-                            let highlight_range = symbol_range.unwrap_or_else(|| {
-                                let snapshot = &snapshot.buffer_snapshot;
-                                let (offset_range, _) = snapshot.surrounding_word(trigger_point);
-
-                                snapshot.anchor_before(offset_range.start)
-                                    ..snapshot.anchor_after(offset_range.end)
-                            });
-
-                            // Highlight symbol using theme link definition highlight style
-                            let style = cx.global::<Settings>().theme.editor.link_definition;
-                            this.highlight_text::<LinkGoToDefinitionState>(
-                                vec![highlight_range],
-                                style,
-                                cx,
-                            );
-                        } else {
-                            hide_link_definition(this, cx);
-                        }
+            this.update(&mut cx, |this, cx| {
+                // Clear any existing highlights
+                this.clear_text_highlights::<LinkGoToDefinitionState>(cx);
+                this.link_go_to_definition_state.kind = Some(definition_kind);
+                this.link_go_to_definition_state.symbol_range = result
+                    .as_ref()
+                    .and_then(|(symbol_range, _)| symbol_range.clone());
+
+                if let Some((symbol_range, definitions)) = result {
+                    this.link_go_to_definition_state.definitions = definitions.clone();
+
+                    let buffer_snapshot = buffer.read(cx).snapshot();
+
+                    // Only show highlight if there exists a definition to jump to that doesn't contain
+                    // the current location.
+                    let any_definition_does_not_contain_current_location =
+                        definitions.iter().any(|definition| {
+                            let target = &definition.target;
+                            if target.buffer == buffer {
+                                let range = &target.range;
+                                // Expand range by one character as lsp definition ranges include positions adjacent
+                                // but not contained by the symbol range
+                                let start = buffer_snapshot.clip_offset(
+                                    range.start.to_offset(&buffer_snapshot).saturating_sub(1),
+                                    Bias::Left,
+                                );
+                                let end = buffer_snapshot.clip_offset(
+                                    range.end.to_offset(&buffer_snapshot) + 1,
+                                    Bias::Right,
+                                );
+                                let offset = buffer_position.to_offset(&buffer_snapshot);
+                                !(start <= offset && end >= offset)
+                            } else {
+                                true
+                            }
+                        });
+
+                    if any_definition_does_not_contain_current_location {
+                        // If no symbol range returned from language server, use the surrounding word.
+                        let highlight_range = symbol_range.unwrap_or_else(|| {
+                            let snapshot = &snapshot.buffer_snapshot;
+                            let (offset_range, _) = snapshot.surrounding_word(trigger_point);
+
+                            snapshot.anchor_before(offset_range.start)
+                                ..snapshot.anchor_after(offset_range.end)
+                        });
+
+                        // Highlight symbol using theme link definition highlight style
+                        let style = cx.global::<Settings>().theme.editor.link_definition;
+                        this.highlight_text::<LinkGoToDefinitionState>(
+                            vec![highlight_range],
+                            style,
+                            cx,
+                        );
+                    } else {
+                        hide_link_definition(this, cx);
                     }
-                })?;
-            }
+                }
+            })?;
 
             Ok::<_, anyhow::Error>(())
         }

crates/editor/src/scroll.rs 🔗

@@ -245,16 +245,14 @@ impl ScrollManager {
         }
 
         if cx.default_global::<ScrollbarAutoHide>().0 {
-            self.hide_scrollbar_task = Some(cx.spawn_weak(|editor, mut cx| async move {
+            self.hide_scrollbar_task = Some(cx.spawn(|editor, mut cx| async move {
                 cx.background().timer(SCROLLBAR_SHOW_INTERVAL).await;
-                if let Some(editor) = editor.upgrade(&cx) {
-                    editor
-                        .update(&mut cx, |editor, cx| {
-                            editor.scroll_manager.show_scrollbars = false;
-                            cx.notify();
-                        })
-                        .log_err();
-                }
+                editor
+                    .update(&mut cx, |editor, cx| {
+                        editor.scroll_manager.show_scrollbars = false;
+                        cx.notify();
+                    })
+                    .log_err();
             }));
         } else {
             self.hide_scrollbar_task = None;

crates/feedback/src/feedback_editor.rs 🔗

@@ -124,11 +124,10 @@ impl FeedbackEditor {
             &["Yes, Submit!", "No"],
         );
 
-        let this = cx.handle();
         let client = cx.global::<Arc<Client>>().clone();
         let specs = self.system_specs.clone();
 
-        cx.spawn(|_, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let answer = answer.recv().await;
 
             if answer == Some(0) {

crates/gpui/src/app.rs 🔗

@@ -127,18 +127,8 @@ pub trait BorrowAppContext {
 }
 
 pub trait BorrowWindowContext {
-    type ReturnValue<T>;
-
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(
-        &self,
-        window_id: usize,
-        f: F,
-    ) -> Self::ReturnValue<T>;
-    fn update<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        f: F,
-    ) -> Self::ReturnValue<T>;
+    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T;
+    fn update<T, F: FnOnce(&mut WindowContext) -> T>(&mut self, window_id: usize, f: F) -> T;
 }
 
 #[derive(Clone)]
@@ -381,28 +371,6 @@ impl BorrowAppContext for AsyncAppContext {
     }
 }
 
-impl BorrowWindowContext for AsyncAppContext {
-    type ReturnValue<T> = Result<T>;
-
-    fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> Result<T> {
-        self.0
-            .borrow()
-            .read_window(window_id, f)
-            .ok_or_else(|| anyhow!("window was closed"))
-    }
-
-    fn update<T, F: FnOnce(&mut WindowContext) -> T>(
-        &mut self,
-        window_id: usize,
-        f: F,
-    ) -> Result<T> {
-        self.0
-            .borrow_mut()
-            .update_window(window_id, f)
-            .ok_or_else(|| anyhow!("window was closed"))
-    }
-}
-
 type ActionCallback = dyn FnMut(&mut dyn AnyView, &dyn Action, &mut WindowContext, usize);
 type GlobalActionCallback = dyn FnMut(&dyn Action, &mut AppContext);
 
@@ -3262,26 +3230,16 @@ impl<'a, 'b, V: View> ViewContext<'a, 'b, V> {
 
     pub fn spawn_labeled<F, Fut, S>(&mut self, task_label: &'static str, f: F) -> Task<S>
     where
-        F: FnOnce(ViewHandle<V>, AsyncAppContext) -> Fut,
+        F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
         Fut: 'static + Future<Output = S>,
         S: 'static,
     {
-        let handle = self.handle();
+        let handle = self.weak_handle();
         self.window_context
             .spawn_labeled(task_label, |cx| f(handle, cx))
     }
 
     pub fn spawn<F, Fut, S>(&mut self, f: F) -> Task<S>
-    where
-        F: FnOnce(ViewHandle<V>, AsyncAppContext) -> Fut,
-        Fut: 'static + Future<Output = S>,
-        S: 'static,
-    {
-        let handle = self.handle();
-        self.window_context.spawn(|cx| f(handle, cx))
-    }
-
-    pub fn spawn_weak<F, Fut, S>(&mut self, f: F) -> Task<S>
     where
         F: FnOnce(WeakViewHandle<V>, AsyncAppContext) -> Fut,
         Fut: 'static + Future<Output = S>,
@@ -3340,8 +3298,6 @@ impl<V> BorrowAppContext for ViewContext<'_, '_, V> {
 }
 
 impl<V> BorrowWindowContext for ViewContext<'_, '_, V> {
-    type ReturnValue<T> = T;
-
     fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         BorrowWindowContext::read_with(&*self.window_context, window_id, f)
     }
@@ -3394,8 +3350,6 @@ impl<V: View> BorrowAppContext for EventContext<'_, '_, '_, V> {
 }
 
 impl<V: View> BorrowWindowContext for EventContext<'_, '_, '_, V> {
-    type ReturnValue<T> = T;
-
     fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         BorrowWindowContext::read_with(&*self.view_context, window_id, f)
     }
@@ -3732,7 +3686,7 @@ impl<T: View> ViewHandle<T> {
         cx.read_view(self)
     }
 
-    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> C::ReturnValue<S>
+    pub fn read_with<C, F, S>(&self, cx: &C, read: F) -> S
     where
         C: BorrowWindowContext,
         F: FnOnce(&T, &ViewContext<T>) -> S,
@@ -3743,7 +3697,7 @@ impl<T: View> ViewHandle<T> {
         })
     }
 
-    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> C::ReturnValue<S>
+    pub fn update<C, F, S>(&self, cx: &mut C, update: F) -> S
     where
         C: BorrowWindowContext,
         F: FnOnce(&mut T, &mut ViewContext<T>) -> S,
@@ -4094,15 +4048,31 @@ impl<V: View> WeakViewHandle<V> {
         cx.read_with(|cx| cx.upgrade_view_handle(self))
     }
 
+    pub fn read_with<T>(
+        &self,
+        cx: &AsyncAppContext,
+        read: impl FnOnce(&V, &ViewContext<V>) -> T,
+    ) -> Result<T> {
+        cx.read(|cx| {
+            let handle = cx
+                .upgrade_view_handle(self)
+                .ok_or_else(|| anyhow!("view {} was dropped", V::ui_name()))?;
+            cx.read_window(self.window_id, |cx| handle.read_with(cx, read))
+                .ok_or_else(|| anyhow!("window was removed"))
+        })
+    }
+
     pub fn update<T>(
         &self,
-        cx: &mut impl BorrowAppContext,
+        cx: &mut AsyncAppContext,
         update: impl FnOnce(&mut V, &mut ViewContext<V>) -> T,
-    ) -> Option<T> {
+    ) -> Result<T> {
         cx.update(|cx| {
-            let handle = cx.upgrade_view_handle(self)?;
-
+            let handle = cx
+                .upgrade_view_handle(self)
+                .ok_or_else(|| anyhow!("view {} was dropped", V::ui_name()))?;
             cx.update_window(self.window_id, |cx| handle.update(cx, update))
+                .ok_or_else(|| anyhow!("window was removed"))
         })
     }
 }
@@ -4150,9 +4120,24 @@ impl AnyWeakViewHandle {
         self.view_id
     }
 
+    fn is<T: 'static>(&self) -> bool {
+        TypeId::of::<T>() == self.view_type
+    }
+
     pub fn upgrade(&self, cx: &impl BorrowAppContext) -> Option<AnyViewHandle> {
         cx.read_with(|cx| cx.upgrade_any_view_handle(self))
     }
+
+    pub fn downcast<T: View>(self) -> Option<WeakViewHandle<T>> {
+        if self.is::<T>() {
+            Some(WeakViewHandle {
+                any_handle: self,
+                view_type: PhantomData,
+            })
+        } else {
+            None
+        }
+    }
 }
 
 impl Hash for AnyWeakViewHandle {

crates/gpui/src/app/test_app_context.rs 🔗

@@ -390,8 +390,6 @@ impl BorrowAppContext for TestAppContext {
 }
 
 impl BorrowWindowContext for TestAppContext {
-    type ReturnValue<T> = T;
-
     fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         self.cx
             .borrow()

crates/gpui/src/app/window.rs 🔗

@@ -142,8 +142,6 @@ impl BorrowAppContext for WindowContext<'_> {
 }
 
 impl BorrowWindowContext for WindowContext<'_> {
-    type ReturnValue<T> = T;
-
     fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
         if self.window_id == window_id {
             f(self)

crates/gpui/src/elements/tooltip.rs 🔗

@@ -99,14 +99,12 @@ impl<V: View> Tooltip<V> {
 
                         let mut debounce = state.debounce.borrow_mut();
                         if debounce.is_none() {
-                            *debounce = Some(cx.spawn_weak({
+                            *debounce = Some(cx.spawn({
                                 let state = state.clone();
                                 |view, mut cx| async move {
                                     cx.background().timer(DEBOUNCE_TIMEOUT).await;
                                     state.visible.set(true);
-                                    if let Some(view) = view.upgrade(&cx) {
-                                        view.update(&mut cx, |_, cx| cx.notify()).log_err();
-                                    }
+                                    view.update(&mut cx, |_, cx| cx.notify()).log_err();
                                 }
                             }));
                         }

crates/journal/src/journal.rs 🔗

@@ -56,7 +56,7 @@ pub fn new_journal_entry(app_state: Arc<AppState>, cx: &mut AppContext) {
             .await;
 
         if let Some(Some(Ok(item))) = opened.first() {
-            if let Some(editor) = item.downcast::<Editor>() {
+            if let Some(editor) = item.downcast::<Editor>().map(|editor| editor.downgrade()) {
                 editor.update(&mut cx, |editor, cx| {
                     let len = editor.buffer().read(cx).len(cx);
                     editor.change_selections(Some(Autoscroll::center()), cx, |s| {

crates/language_selector/src/language_selector.rs 🔗

@@ -102,7 +102,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
             let language = self.language_registry.language_for_name(language_name);
             let project = self.project.downgrade();
             let buffer = self.buffer.downgrade();
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(|_, mut cx| async move {
                 let language = language.await?;
                 let project = project
                     .upgrade(&cx)
@@ -138,7 +138,7 @@ impl PickerDelegate for LanguageSelectorDelegate {
     ) -> gpui::Task<()> {
         let background = cx.background().clone();
         let candidates = self.candidates.clone();
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -162,17 +162,15 @@ impl PickerDelegate for LanguageSelectorDelegate {
                 .await
             };
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
-                    let delegate = this.delegate_mut();
-                    delegate.matches = matches;
-                    delegate.selected_index = delegate
-                        .selected_index
-                        .min(delegate.matches.len().saturating_sub(1));
-                    cx.notify();
-                })
-                .log_err();
-            }
+            this.update(&mut cx, |this, cx| {
+                let delegate = this.delegate_mut();
+                delegate.matches = matches;
+                delegate.selected_index = delegate
+                    .selected_index
+                    .min(delegate.matches.len().saturating_sub(1));
+                cx.notify();
+            })
+            .log_err();
         })
     }
 

crates/picker/src/picker.rs 🔗

@@ -238,10 +238,9 @@ impl<D: PickerDelegate> Picker<D> {
     pub fn update_matches(&mut self, query: String, cx: &mut ViewContext<Self>) {
         let update = self.delegate.update_matches(query, cx);
         self.matches_updated(cx);
-        self.pending_update_matches = cx.spawn_weak(|this, mut cx| async move {
+        self.pending_update_matches = cx.spawn(|this, mut cx| async move {
             update.await;
-            this.upgrade(&cx)?
-                .update(&mut cx, |this, cx| this.matches_updated(cx))
+            this.update(&mut cx, |this, cx| this.matches_updated(cx))
                 .log_err()
         });
     }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -1,4 +1,3 @@
-use anyhow::anyhow;
 use editor::{
     combine_syntax_and_fuzzy_match_highlights, scroll::autoscroll::Autoscroll,
     styled_runs_for_code_label, Bias, Editor,
@@ -117,11 +116,8 @@ impl PickerDelegate for ProjectSymbolsDelegate {
             });
             let symbol = symbol.clone();
             let workspace = self.workspace.clone();
-            cx.spawn_weak(|_, mut cx| async move {
+            cx.spawn(|_, mut cx| async move {
                 let buffer = buffer.await?;
-                let workspace = workspace
-                    .upgrade(&cx)
-                    .ok_or_else(|| anyhow!("workspace was dropped"))?;
                 workspace.update(&mut cx, |workspace, cx| {
                     let position = buffer
                         .read(cx)
@@ -161,36 +157,33 @@ impl PickerDelegate for ProjectSymbolsDelegate {
         let symbols = self
             .project
             .update(cx, |project, cx| project.symbols(&query, cx));
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let symbols = symbols.await.log_err();
-            if let Some(this) = this.upgrade(&cx) {
-                if let Some(symbols) = symbols {
-                    this.update(&mut cx, |this, cx| {
-                        let delegate = this.delegate_mut();
-                        let project = delegate.project.read(cx);
-                        let (visible_match_candidates, external_match_candidates) = symbols
-                            .iter()
-                            .enumerate()
-                            .map(|(id, symbol)| {
-                                StringMatchCandidate::new(
-                                    id,
-                                    symbol.label.text[symbol.label.filter_range.clone()]
-                                        .to_string(),
-                                )
-                            })
-                            .partition(|candidate| {
-                                project
-                                    .entry_for_path(&symbols[candidate.id].path, cx)
-                                    .map_or(false, |e| !e.is_ignored)
-                            });
-
-                        delegate.visible_match_candidates = visible_match_candidates;
-                        delegate.external_match_candidates = external_match_candidates;
-                        delegate.symbols = symbols;
-                        delegate.filter(&query, cx);
-                    })
-                    .log_err();
-                }
+            if let Some(symbols) = symbols {
+                this.update(&mut cx, |this, cx| {
+                    let delegate = this.delegate_mut();
+                    let project = delegate.project.read(cx);
+                    let (visible_match_candidates, external_match_candidates) = symbols
+                        .iter()
+                        .enumerate()
+                        .map(|(id, symbol)| {
+                            StringMatchCandidate::new(
+                                id,
+                                symbol.label.text[symbol.label.filter_range.clone()].to_string(),
+                            )
+                        })
+                        .partition(|candidate| {
+                            project
+                                .entry_for_path(&symbols[candidate.id].path, cx)
+                                .map_or(false, |e| !e.is_ignored)
+                        });
+
+                    delegate.visible_match_candidates = visible_match_candidates;
+                    delegate.external_match_candidates = external_match_candidates;
+                    delegate.symbols = symbols;
+                    delegate.filter(&query, cx);
+                })
+                .log_err();
             }
         })
     }

crates/search/src/buffer_search.rs 🔗

@@ -196,12 +196,12 @@ impl ToolbarItemView for BufferSearchBar {
         if let Some(searchable_item_handle) =
             item.and_then(|item| item.to_searchable_item_handle(cx))
         {
-            let handle = cx.weak_handle();
+            let this = cx.weak_handle();
             self.active_searchable_item_subscription =
                 Some(searchable_item_handle.subscribe_to_search_events(
                     cx,
                     Box::new(move |search_event, cx| {
-                        if let Some(this) = handle.upgrade(cx) {
+                        if let Some(this) = this.upgrade(cx) {
                             this.update(cx, |this, cx| {
                                 this.on_active_searchable_item_event(search_event, cx)
                             });
@@ -582,36 +582,33 @@ impl BufferSearchBar {
                 let matches = active_searchable_item.find_matches(query, cx);
 
                 let active_searchable_item = active_searchable_item.downgrade();
-                self.pending_search = Some(cx.spawn_weak(|this, mut cx| async move {
+                self.pending_search = Some(cx.spawn(|this, mut cx| async move {
                     let matches = matches.await;
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| {
-                            if let Some(active_searchable_item) = WeakSearchableItemHandle::upgrade(
-                                active_searchable_item.as_ref(),
-                                cx,
-                            ) {
-                                this.seachable_items_with_matches
-                                    .insert(active_searchable_item.downgrade(), matches);
-
-                                this.update_match_index(cx);
-                                if !this.dismissed {
-                                    let matches = this
-                                        .seachable_items_with_matches
-                                        .get(&active_searchable_item.downgrade())
-                                        .unwrap();
-                                    active_searchable_item.update_matches(matches, cx);
-                                    if select_closest_match {
-                                        if let Some(match_ix) = this.active_match_index {
-                                            active_searchable_item
-                                                .activate_match(match_ix, matches, cx);
-                                        }
+                    this.update(&mut cx, |this, cx| {
+                        if let Some(active_searchable_item) =
+                            WeakSearchableItemHandle::upgrade(active_searchable_item.as_ref(), cx)
+                        {
+                            this.seachable_items_with_matches
+                                .insert(active_searchable_item.downgrade(), matches);
+
+                            this.update_match_index(cx);
+                            if !this.dismissed {
+                                let matches = this
+                                    .seachable_items_with_matches
+                                    .get(&active_searchable_item.downgrade())
+                                    .unwrap();
+                                active_searchable_item.update_matches(matches, cx);
+                                if select_closest_match {
+                                    if let Some(match_ix) = this.active_match_index {
+                                        active_searchable_item
+                                            .activate_match(match_ix, matches, cx);
                                     }
                                 }
-                                cx.notify();
                             }
-                        })
-                        .log_err();
-                    }
+                            cx.notify();
+                        }
+                    })
+                    .log_err();
                 }));
             }
         }

crates/terminal_view/src/terminal_view.rs 🔗

@@ -2,13 +2,7 @@ mod persistence;
 pub mod terminal_button;
 pub mod terminal_element;
 
-use std::{
-    borrow::Cow,
-    ops::RangeInclusive,
-    path::{Path, PathBuf},
-    time::Duration,
-};
-
+use anyhow::anyhow;
 use context_menu::{ContextMenu, ContextMenuItem};
 use dirs::home_dir;
 use gpui::{
@@ -26,6 +20,12 @@ use serde::Deserialize;
 use settings::{Settings, TerminalBlink, WorkingDirectory};
 use smallvec::{smallvec, SmallVec};
 use smol::Timer;
+use std::{
+    borrow::Cow,
+    ops::RangeInclusive,
+    path::{Path, PathBuf},
+    time::Duration,
+};
 use terminal::{
     alacritty_terminal::{
         index::Point,
@@ -275,15 +275,10 @@ impl TerminalView {
             cx.notify();
 
             let epoch = self.next_blink_epoch();
-            cx.spawn(|this, mut cx| {
-                let this = this.downgrade();
-                async move {
-                    Timer::after(CURSOR_BLINK_INTERVAL).await;
-                    if let Some(this) = this.upgrade(&cx) {
-                        this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
-                            .log_err();
-                    }
-                }
+            cx.spawn(|this, mut cx| async move {
+                Timer::after(CURSOR_BLINK_INTERVAL).await;
+                this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
+                    .log_err();
             })
             .detach();
         }
@@ -294,15 +289,10 @@ impl TerminalView {
         cx.notify();
 
         let epoch = self.next_blink_epoch();
-        cx.spawn(|this, mut cx| {
-            let this = this.downgrade();
-            async move {
-                Timer::after(CURSOR_BLINK_INTERVAL).await;
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
-                        .log_err();
-                }
-            }
+        cx.spawn(|this, mut cx| async move {
+            Timer::after(CURSOR_BLINK_INTERVAL).await;
+            this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+                .log_err();
         })
         .detach();
     }
@@ -646,6 +636,9 @@ impl Item for TerminalView {
                     })
                 });
 
+            let pane = pane
+                .upgrade(&cx)
+                .ok_or_else(|| anyhow!("pane was dropped"))?;
             cx.update(|cx| {
                 let terminal = project.update(cx, |project, cx| {
                     project.create_terminal(cwd, window_id, cx)

crates/theme_selector/src/theme_selector.rs 🔗

@@ -163,7 +163,7 @@ impl PickerDelegate for ThemeSelectorDelegate {
             })
             .collect::<Vec<_>>();
 
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -187,17 +187,15 @@ impl PickerDelegate for ThemeSelectorDelegate {
                 .await
             };
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, cx| {
-                    let delegate = this.delegate_mut();
-                    delegate.matches = matches;
-                    delegate.selected_index = delegate
-                        .selected_index
-                        .min(delegate.matches.len().saturating_sub(1));
-                    delegate.show_selected_theme(cx);
-                })
-                .log_err();
-            }
+            this.update(&mut cx, |this, cx| {
+                let delegate = this.delegate_mut();
+                delegate.matches = matches;
+                delegate.selected_index = delegate
+                    .selected_index
+                    .min(delegate.matches.len().saturating_sub(1));
+                delegate.show_selected_theme(cx);
+            })
+            .log_err();
         })
     }
 

crates/welcome/src/base_keymap_picker.rs 🔗

@@ -81,7 +81,7 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
             })
             .collect::<Vec<_>>();
 
-        cx.spawn_weak(|this, mut cx| async move {
+        cx.spawn(|this, mut cx| async move {
             let matches = if query.is_empty() {
                 candidates
                     .into_iter()
@@ -105,16 +105,14 @@ impl PickerDelegate for BaseKeymapSelectorDelegate {
                 .await
             };
 
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, _| {
-                    let delegate = this.delegate_mut();
-                    delegate.matches = matches;
-                    delegate.selected_index = delegate
-                        .selected_index
-                        .min(delegate.matches.len().saturating_sub(1));
-                })
-                .log_err();
-            }
+            this.update(&mut cx, |this, _| {
+                let delegate = this.delegate_mut();
+                delegate.matches = matches;
+                delegate.selected_index = delegate
+                    .selected_index
+                    .min(delegate.matches.len().saturating_sub(1));
+            })
+            .log_err();
         })
     }
 

crates/workspace/src/dock.rs 🔗

@@ -811,8 +811,6 @@ mod tests {
     }
 
     impl BorrowWindowContext for DockTestContext<'_> {
-        type ReturnValue<T> = T;
-
         fn read_with<T, F: FnOnce(&WindowContext) -> T>(&self, window_id: usize, f: F) -> T {
             BorrowWindowContext::read_with(self.cx, window_id, f)
         }

crates/workspace/src/item.rs 🔗

@@ -3,7 +3,7 @@ use crate::{
     FollowableItemBuilders, ItemNavHistory, Pane, ToolbarItemLocation, ViewId, Workspace,
     WorkspaceId,
 };
-use anyhow::{anyhow, Result};
+use anyhow::Result;
 use client::{proto, Client};
 use gpui::{
     fonts::HighlightStyle, AnyElement, AnyViewHandle, AppContext, ModelHandle, Task, View,
@@ -479,10 +479,8 @@ impl<T: Item> ItemHandle for ViewHandle<T> {
                                         },
                                     );
                                 } else {
-                                    cx.spawn_weak(|workspace, mut cx| async move {
+                                    cx.spawn(|workspace, mut cx| async move {
                                         workspace
-                                            .upgrade(&cx)
-                                            .ok_or_else(|| anyhow!("workspace was dropped"))?
                                             .update(&mut cx, |workspace, cx| {
                                                 item.git_diff_recalc(
                                                     workspace.project().clone(),

crates/workspace/src/pane.rs 🔗

@@ -214,26 +214,10 @@ pub fn init(cx: &mut AppContext) {
         Pane::reopen_closed_item(workspace, cx).detach();
     });
     cx.add_action(|workspace: &mut Workspace, action: &GoBack, cx| {
-        Pane::go_back(
-            workspace,
-            action
-                .pane
-                .as_ref()
-                .and_then(|weak_handle| weak_handle.upgrade(cx)),
-            cx,
-        )
-        .detach();
+        Pane::go_back(workspace, action.pane.clone(), cx).detach();
     });
     cx.add_action(|workspace: &mut Workspace, action: &GoForward, cx| {
-        Pane::go_forward(
-            workspace,
-            action
-                .pane
-                .as_ref()
-                .and_then(|weak_handle| weak_handle.upgrade(cx)),
-            cx,
-        )
-        .detach();
+        Pane::go_forward(workspace, action.pane.clone(), cx).detach();
     });
 }
 
@@ -392,12 +376,12 @@ impl Pane {
 
     pub fn go_back(
         workspace: &mut Workspace,
-        pane: Option<ViewHandle<Pane>>,
+        pane: Option<WeakViewHandle<Pane>>,
         cx: &mut ViewContext<Workspace>,
     ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
-            pane.unwrap_or_else(|| workspace.active_pane().clone()),
+            pane.unwrap_or_else(|| workspace.active_pane().downgrade()),
             NavigationMode::GoingBack,
             cx,
         )
@@ -405,12 +389,12 @@ impl Pane {
 
     pub fn go_forward(
         workspace: &mut Workspace,
-        pane: Option<ViewHandle<Pane>>,
+        pane: Option<WeakViewHandle<Pane>>,
         cx: &mut ViewContext<Workspace>,
     ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
-            pane.unwrap_or_else(|| workspace.active_pane().clone()),
+            pane.unwrap_or_else(|| workspace.active_pane().downgrade()),
             NavigationMode::GoingForward,
             cx,
         )
@@ -422,7 +406,7 @@ impl Pane {
     ) -> Task<Result<()>> {
         Self::navigate_history(
             workspace,
-            workspace.active_pane().clone(),
+            workspace.active_pane().downgrade(),
             NavigationMode::ReopeningClosedItem,
             cx,
         )
@@ -450,62 +434,62 @@ impl Pane {
 
     fn navigate_history(
         workspace: &mut Workspace,
-        pane: ViewHandle<Pane>,
+        pane: WeakViewHandle<Pane>,
         mode: NavigationMode,
         cx: &mut ViewContext<Workspace>,
     ) -> Task<Result<()>> {
-        cx.focus(&pane);
-
-        let to_load = pane.update(cx, |pane, cx| {
-            loop {
-                // Retrieve the weak item handle from the history.
-                let entry = pane.nav_history.borrow_mut().pop(mode, cx)?;
-
-                // If the item is still present in this pane, then activate it.
-                if let Some(index) = entry
-                    .item
-                    .upgrade(cx)
-                    .and_then(|v| pane.index_for_item(v.as_ref()))
-                {
-                    let prev_active_item_index = pane.active_item_index;
-                    pane.nav_history.borrow_mut().set_mode(mode);
-                    pane.activate_item(index, true, true, cx);
-                    pane.nav_history
-                        .borrow_mut()
-                        .set_mode(NavigationMode::Normal);
-
-                    let mut navigated = prev_active_item_index != pane.active_item_index;
-                    if let Some(data) = entry.data {
-                        navigated |= pane.active_item()?.navigate(data, cx);
-                    }
+        let to_load = if let Some(pane) = pane.upgrade(cx) {
+            cx.focus(&pane);
+
+            pane.update(cx, |pane, cx| {
+                loop {
+                    // Retrieve the weak item handle from the history.
+                    let entry = pane.nav_history.borrow_mut().pop(mode, cx)?;
+
+                    // If the item is still present in this pane, then activate it.
+                    if let Some(index) = entry
+                        .item
+                        .upgrade(cx)
+                        .and_then(|v| pane.index_for_item(v.as_ref()))
+                    {
+                        let prev_active_item_index = pane.active_item_index;
+                        pane.nav_history.borrow_mut().set_mode(mode);
+                        pane.activate_item(index, true, true, cx);
+                        pane.nav_history
+                            .borrow_mut()
+                            .set_mode(NavigationMode::Normal);
 
-                    if navigated {
-                        break None;
+                        let mut navigated = prev_active_item_index != pane.active_item_index;
+                        if let Some(data) = entry.data {
+                            navigated |= pane.active_item()?.navigate(data, cx);
+                        }
+
+                        if navigated {
+                            break None;
+                        }
+                    }
+                    // If the item is no longer present in this pane, then retrieve its
+                    // project path in order to reopen it.
+                    else {
+                        break pane
+                            .nav_history
+                            .borrow()
+                            .paths_by_item
+                            .get(&entry.item.id())
+                            .cloned()
+                            .map(|project_path| (project_path, entry));
                     }
                 }
-                // If the item is no longer present in this pane, then retrieve its
-                // project path in order to reopen it.
-                else {
-                    break pane
-                        .nav_history
-                        .borrow()
-                        .paths_by_item
-                        .get(&entry.item.id())
-                        .cloned()
-                        .map(|project_path| (project_path, entry));
-                }
-            }
-        });
+            })
+        } else {
+            None
+        };
 
         if let Some((project_path, entry)) = to_load {
             // If the item was no longer present, then load it again from its previous path.
-            let pane = pane.downgrade();
             let task = workspace.load_path(project_path, cx);
             cx.spawn(|workspace, mut cx| async move {
                 let task = task.await;
-                let pane = pane
-                    .upgrade(&cx)
-                    .ok_or_else(|| anyhow!("pane was dropped"))?;
                 let mut navigated = false;
                 if let Some((project_entry_id, build_item)) = task.log_err() {
                     let prev_active_item_id = pane.update(&mut cx, |pane, _| {
@@ -514,15 +498,18 @@ impl Pane {
                     })?;
 
                     let item = workspace.update(&mut cx, |workspace, cx| {
-                        Self::open_item(
+                        let pane = pane
+                            .upgrade(cx)
+                            .ok_or_else(|| anyhow!("pane was dropped"))?;
+                        anyhow::Ok(Self::open_item(
                             workspace,
                             pane.clone(),
                             project_entry_id,
                             true,
                             cx,
                             build_item,
-                        )
-                    })?;
+                        ))
+                    })??;
 
                     pane.update(&mut cx, |pane, cx| {
                         navigated |= Some(item.id()) != prev_active_item_id;
@@ -973,6 +960,7 @@ impl Pane {
         // of what content they would be saving.
         items_to_close.sort_by_key(|item| !item.is_singleton(cx));
 
+        let pane = pane.downgrade();
         cx.spawn(|workspace, mut cx| async move {
             let mut saved_project_items_ids = HashSet::default();
             for item in items_to_close.clone() {
@@ -1084,7 +1072,7 @@ impl Pane {
 
     pub async fn save_item(
         project: ModelHandle<Project>,
-        pane: &ViewHandle<Pane>,
+        pane: &WeakViewHandle<Pane>,
         item_ix: usize,
         item: &dyn ItemHandle,
         should_prompt_for_save: bool,
@@ -2006,7 +1994,9 @@ impl NavHistory {
 
     fn did_update(&self, cx: &mut WindowContext) {
         if let Some(pane) = self.pane.upgrade(cx) {
-            cx.defer(move |cx| pane.update(cx, |pane, cx| pane.history_updated(cx)));
+            cx.defer(move |cx| {
+                pane.update(cx, |pane, cx| pane.history_updated(cx));
+            });
         }
     }
 }

crates/workspace/src/persistence/model.rs 🔗

@@ -1,26 +1,24 @@
-use std::{
-    path::{Path, PathBuf},
-    sync::Arc,
+use crate::{
+    dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
 };
-
-use anyhow::{Context, Result};
-
+use anyhow::{anyhow, Context, Result};
 use async_recursion::async_recursion;
-use gpui::{platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle};
-
 use db::sqlez::{
     bindable::{Bind, Column, StaticColumnCount},
     statement::Statement,
 };
+use gpui::{
+    platform::WindowBounds, AsyncAppContext, Axis, ModelHandle, Task, ViewHandle, WeakViewHandle,
+};
 use project::Project;
 use settings::DockAnchor;
+use std::{
+    path::{Path, PathBuf},
+    sync::Arc,
+};
 use util::ResultExt;
 use uuid::Uuid;
 
-use crate::{
-    dock::DockPosition, ItemDeserializers, Member, Pane, PaneAxis, Workspace, WorkspaceId,
-};
-
 #[derive(Debug, Clone, PartialEq, Eq)]
 pub struct WorkspaceLocation(Arc<Vec<PathBuf>>);
 
@@ -97,7 +95,7 @@ impl SerializedPaneGroup {
         &self,
         project: &ModelHandle<Project>,
         workspace_id: WorkspaceId,
-        workspace: &ViewHandle<Workspace>,
+        workspace: &WeakViewHandle<Workspace>,
         cx: &mut AsyncAppContext,
     ) -> Option<(Member, Option<ViewHandle<Pane>>)> {
         match self {
@@ -132,7 +130,7 @@ impl SerializedPaneGroup {
             }
             SerializedPaneGroup::Pane(serialized_pane) => {
                 let pane = workspace
-                    .update(cx, |workspace, cx| workspace.add_pane(cx))
+                    .update(cx, |workspace, cx| workspace.add_pane(cx).downgrade())
                     .log_err()?;
                 let active = serialized_pane.active;
                 serialized_pane
@@ -144,8 +142,10 @@ impl SerializedPaneGroup {
                     .read_with(cx, |pane, _| pane.items_len() != 0)
                     .log_err()?
                 {
+                    let pane = pane.upgrade(cx)?;
                     Some((Member::Pane(pane.clone()), active.then(|| pane)))
                 } else {
+                    let pane = pane.upgrade(cx)?;
                     workspace
                         .update(cx, |workspace, cx| workspace.remove_pane(pane, cx))
                         .log_err()?;
@@ -170,9 +170,9 @@ impl SerializedPane {
     pub async fn deserialize_to(
         &self,
         project: &ModelHandle<Project>,
-        pane_handle: &ViewHandle<Pane>,
+        pane_handle: &WeakViewHandle<Pane>,
         workspace_id: WorkspaceId,
-        workspace: &ViewHandle<Workspace>,
+        workspace: &WeakViewHandle<Workspace>,
         cx: &mut AsyncAppContext,
     ) -> Result<()> {
         let mut active_item_index = None;
@@ -181,13 +181,7 @@ impl SerializedPane {
             let item_handle = pane_handle
                 .update(cx, |_, cx| {
                     if let Some(deserializer) = cx.global::<ItemDeserializers>().get(&item.kind) {
-                        deserializer(
-                            project,
-                            workspace.downgrade(),
-                            workspace_id,
-                            item.item_id,
-                            cx,
-                        )
+                        deserializer(project, workspace.clone(), workspace_id, item.item_id, cx)
                     } else {
                         Task::ready(Err(anyhow::anyhow!(
                             "Deserializer does not exist for item kind: {}",
@@ -200,8 +194,12 @@ impl SerializedPane {
 
             if let Some(item_handle) = item_handle {
                 workspace.update(cx, |workspace, cx| {
+                    let pane_handle = pane_handle
+                        .upgrade(cx)
+                        .ok_or_else(|| anyhow!("pane was dropped"))?;
                     Pane::add_item(workspace, &pane_handle, item_handle, false, false, None, cx);
-                })?;
+                    anyhow::Ok(())
+                })??;
             }
 
             if item.active {

crates/workspace/src/workspace.rs 🔗

@@ -314,7 +314,7 @@ pub fn init(app_state: Arc<AppState>, cx: &mut AppContext) {
                 Some(workspace.prepare_to_close(false, cx))
             };
 
-            Some(cx.spawn_weak(|_, mut cx| async move {
+            Some(cx.spawn(|_, mut cx| async move {
                 let window_id_to_replace = if let Some(close_task) = close_task {
                     if !close_task.await? {
                         return Ok(());
@@ -588,7 +588,7 @@ impl DelayedDebouncedEditAction {
         self.cancel_channel = Some(sender);
 
         let previous_task = self.task.take();
-        self.task = Some(cx.spawn_weak(|workspace, mut cx| async move {
+        self.task = Some(cx.spawn(|workspace, mut cx| async move {
             let mut timer = cx.background().timer(delay).fuse();
             if let Some(previous_task) = previous_task {
                 previous_task.await;
@@ -599,13 +599,11 @@ impl DelayedDebouncedEditAction {
                     _ = timer => {}
             }
 
-            if let Some(workspace) = workspace.upgrade(&cx) {
-                if let Some(result) = workspace
-                    .update(&mut cx, |workspace, cx| (f)(workspace, cx))
-                    .log_err()
-                {
-                    result.await.log_err();
-                }
+            if let Some(result) = workspace
+                .update(&mut cx, |workspace, cx| (f)(workspace, cx))
+                .log_err()
+            {
+                result.await.log_err();
             }
         }));
     }
@@ -733,16 +731,14 @@ impl Workspace {
         let client = project.read(cx).client();
         let mut current_user = user_store.read(cx).watch_current_user();
         let mut connection_status = client.status();
-        let _observe_current_user = cx.spawn_weak(|this, mut cx| async move {
+        let _observe_current_user = cx.spawn(|this, mut cx| async move {
             current_user.recv().await;
             connection_status.recv().await;
             let mut stream =
                 Stream::map(current_user, drop).merge(Stream::map(connection_status, drop));
 
             while stream.recv().await.is_some() {
-                if let Some(this) = this.upgrade(&cx) {
-                    this.update(&mut cx, |_, cx| cx.notify())?;
-                }
+                this.update(&mut cx, |_, cx| cx.notify())?;
             }
             anyhow::Ok(())
         });
@@ -752,10 +748,9 @@ impl Workspace {
         // that each asynchronous operation can be run in order.
         let (leader_updates_tx, mut leader_updates_rx) =
             mpsc::unbounded::<(PeerId, proto::UpdateFollowers)>();
-        let _apply_leader_updates = cx.spawn_weak(|this, mut cx| async move {
+        let _apply_leader_updates = cx.spawn(|this, mut cx| async move {
             while let Some((leader_id, update)) = leader_updates_rx.next().await {
-                let Some(this) = this.upgrade(&cx) else { break };
-                Self::process_leader_update(this, leader_id, update, &mut cx)
+                Self::process_leader_update(&this, leader_id, update, &mut cx)
                     .await
                     .log_err();
             }
@@ -869,7 +864,7 @@ impl Workspace {
         requesting_window_id: Option<usize>,
         cx: &mut AppContext,
     ) -> Task<(
-        ViewHandle<Workspace>,
+        WeakViewHandle<Workspace>,
         Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
     )> {
         let project_handle = Project::local(
@@ -987,6 +982,7 @@ impl Workspace {
                     .1
                 });
 
+            let workspace = workspace.downgrade();
             notify_if_database_failed(&workspace, &mut cx);
 
             // Call open path for each of the project paths
@@ -1129,7 +1125,7 @@ impl Workspace {
     ) -> Option<Task<Result<()>>> {
         let window_id = cx.window_id();
         let prepare = self.prepare_to_close(false, cx);
-        Some(cx.spawn_weak(|_, mut cx| async move {
+        Some(cx.spawn(|_, mut cx| async move {
             if prepare.await? {
                 cx.remove_window(window_id);
             }
@@ -1211,7 +1207,7 @@ impl Workspace {
             .flat_map(|pane| {
                 pane.read(cx).items().filter_map(|item| {
                     if item.is_dirty(cx) {
-                        Some((pane.clone(), item.boxed_clone()))
+                        Some((pane.downgrade(), item.boxed_clone()))
                     } else {
                         None
                     }
@@ -1220,7 +1216,7 @@ impl Workspace {
             .collect::<Vec<_>>();
 
         let project = self.project.clone();
-        cx.spawn_weak(|_, mut cx| async move {
+        cx.spawn(|_, mut cx| async move {
             for (pane, item) in dirty_items {
                 let (singleton, project_entry_ids) =
                     cx.read(|cx| (item.is_singleton(cx), item.project_entry_ids(cx)));
@@ -1975,32 +1971,30 @@ impl Workspace {
             leader_id: Some(leader_id),
         });
 
-        Some(cx.spawn_weak(|this, mut cx| async move {
+        Some(cx.spawn(|this, mut cx| async move {
             let response = request.await?;
-            if let Some(this) = this.upgrade(&cx) {
-                this.update(&mut cx, |this, _| {
-                    let state = this
-                        .follower_states_by_leader
-                        .get_mut(&leader_id)
-                        .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
-                        .ok_or_else(|| anyhow!("following interrupted"))?;
-                    state.active_view_id = if let Some(active_view_id) = response.active_view_id {
-                        Some(ViewId::from_proto(active_view_id)?)
-                    } else {
-                        None
-                    };
-                    Ok::<_, anyhow::Error>(())
-                })??;
-                Self::add_views_from_leader(
-                    this.clone(),
-                    leader_id,
-                    vec![pane],
-                    response.views,
-                    &mut cx,
-                )
-                .await?;
-                this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
-            }
+            this.update(&mut cx, |this, _| {
+                let state = this
+                    .follower_states_by_leader
+                    .get_mut(&leader_id)
+                    .and_then(|states_by_pane| states_by_pane.get_mut(&pane))
+                    .ok_or_else(|| anyhow!("following interrupted"))?;
+                state.active_view_id = if let Some(active_view_id) = response.active_view_id {
+                    Some(ViewId::from_proto(active_view_id)?)
+                } else {
+                    None
+                };
+                Ok::<_, anyhow::Error>(())
+            })??;
+            Self::add_views_from_leader(
+                this.clone(),
+                leader_id,
+                vec![pane],
+                response.views,
+                &mut cx,
+            )
+            .await?;
+            this.update(&mut cx, |this, cx| this.leader_updated(leader_id, cx))?;
             Ok(())
         }))
     }
@@ -2226,7 +2220,7 @@ impl Workspace {
     // RPC handlers
 
     async fn handle_follow(
-        this: ViewHandle<Self>,
+        this: WeakViewHandle<Self>,
         envelope: TypedEnvelope<proto::Follow>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -2274,7 +2268,7 @@ impl Workspace {
     }
 
     async fn handle_unfollow(
-        this: ViewHandle<Self>,
+        this: WeakViewHandle<Self>,
         envelope: TypedEnvelope<proto::Unfollow>,
         _: Arc<Client>,
         mut cx: AsyncAppContext,
@@ -2289,7 +2283,7 @@ impl Workspace {
     }
 
     async fn handle_update_followers(
-        this: ViewHandle<Self>,
+        this: WeakViewHandle<Self>,
         envelope: TypedEnvelope<proto::UpdateFollowers>,
         _: Arc<Client>,
         cx: AsyncAppContext,
@@ -2303,7 +2297,7 @@ impl Workspace {
     }
 
     async fn process_leader_update(
-        this: ViewHandle<Self>,
+        this: &WeakViewHandle<Self>,
         leader_id: PeerId,
         update: proto::UpdateFollowers,
         cx: &mut AsyncAppContext,
@@ -2363,7 +2357,7 @@ impl Workspace {
     }
 
     async fn add_views_from_leader(
-        this: ViewHandle<Self>,
+        this: WeakViewHandle<Self>,
         leader_id: PeerId,
         panes: Vec<ViewHandle<Pane>>,
         views: Vec<proto::View>,
@@ -2691,82 +2685,80 @@ impl Workspace {
         cx: &mut AppContext,
     ) {
         cx.spawn(|mut cx| async move {
-            if let Some(workspace) = workspace.upgrade(&cx) {
-                let (project, dock_pane_handle, old_center_pane) =
-                    workspace.read_with(&cx, |workspace, _| {
-                        (
-                            workspace.project().clone(),
-                            workspace.dock_pane().clone(),
-                            workspace.last_active_center_pane.clone(),
-                        )
-                    })?;
-
-                serialized_workspace
-                    .dock_pane
-                    .deserialize_to(
-                        &project,
-                        &dock_pane_handle,
-                        serialized_workspace.id,
-                        &workspace,
-                        &mut cx,
+            let (project, dock_pane_handle, old_center_pane) =
+                workspace.read_with(&cx, |workspace, _| {
+                    (
+                        workspace.project().clone(),
+                        workspace.dock_pane().downgrade(),
+                        workspace.last_active_center_pane.clone(),
                     )
-                    .await?;
+                })?;
 
-                // Traverse the splits tree and add to things
-                let center_group = serialized_workspace
-                    .center_group
-                    .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
-                    .await;
+            serialized_workspace
+                .dock_pane
+                .deserialize_to(
+                    &project,
+                    &dock_pane_handle,
+                    serialized_workspace.id,
+                    &workspace,
+                    &mut cx,
+                )
+                .await?;
 
-                // Remove old panes from workspace panes list
-                workspace.update(&mut cx, |workspace, cx| {
-                    if let Some((center_group, active_pane)) = center_group {
-                        workspace.remove_panes(workspace.center.root.clone(), cx);
+            // Traverse the splits tree and add to things
+            let center_group = serialized_workspace
+                .center_group
+                .deserialize(&project, serialized_workspace.id, &workspace, &mut cx)
+                .await;
 
-                        // Swap workspace center group
-                        workspace.center = PaneGroup::with_root(center_group);
+            // Remove old panes from workspace panes list
+            workspace.update(&mut cx, |workspace, cx| {
+                if let Some((center_group, active_pane)) = center_group {
+                    workspace.remove_panes(workspace.center.root.clone(), cx);
 
-                        // Change the focus to the workspace first so that we retrigger focus in on the pane.
-                        cx.focus_self();
+                    // Swap workspace center group
+                    workspace.center = PaneGroup::with_root(center_group);
 
-                        if let Some(active_pane) = active_pane {
-                            cx.focus(&active_pane);
-                        } else {
-                            cx.focus(workspace.panes.last().unwrap());
-                        }
+                    // Change the focus to the workspace first so that we retrigger focus in on the pane.
+                    cx.focus_self();
+
+                    if let Some(active_pane) = active_pane {
+                        cx.focus(&active_pane);
                     } else {
-                        let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
-                        if let Some(old_center_handle) = old_center_handle {
-                            cx.focus(&old_center_handle)
-                        } else {
-                            cx.focus_self()
-                        }
+                        cx.focus(workspace.panes.last().unwrap());
                     }
-
-                    if workspace.left_sidebar().read(cx).is_open()
-                        != serialized_workspace.left_sidebar_open
-                    {
-                        workspace.toggle_sidebar(SidebarSide::Left, cx);
+                } else {
+                    let old_center_handle = old_center_pane.and_then(|weak| weak.upgrade(cx));
+                    if let Some(old_center_handle) = old_center_handle {
+                        cx.focus(&old_center_handle)
+                    } else {
+                        cx.focus_self()
                     }
+                }
 
-                    // Note that without after_window, the focus_self() and
-                    // the focus the dock generates start generating alternating
-                    // focus due to the deferred execution each triggering each other
-                    cx.after_window_update(move |workspace, cx| {
-                        Dock::set_dock_position(
-                            workspace,
-                            serialized_workspace.dock_position,
-                            true,
-                            cx,
-                        );
-                    });
+                if workspace.left_sidebar().read(cx).is_open()
+                    != serialized_workspace.left_sidebar_open
+                {
+                    workspace.toggle_sidebar(SidebarSide::Left, cx);
+                }
 
-                    cx.notify();
-                })?;
+                // Note that without after_window, the focus_self() and
+                // the focus the dock generates start generating alternating
+                // focus due to the deferred execution each triggering each other
+                cx.after_window_update(move |workspace, cx| {
+                    Dock::set_dock_position(
+                        workspace,
+                        serialized_workspace.dock_position,
+                        true,
+                        cx,
+                    );
+                });
 
-                // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
-                workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?
-            }
+                cx.notify();
+            })?;
+
+            // Serialize ourself to make sure our timestamps and any pane / item changes are replicated
+            workspace.read_with(&cx, |workspace, cx| workspace.serialize_workspace(cx))?;
             anyhow::Ok(())
         })
         .detach_and_log_err(cx);
@@ -2778,7 +2770,7 @@ impl Workspace {
     }
 }
 
-fn notify_if_database_failed(workspace: &ViewHandle<Workspace>, cx: &mut AsyncAppContext) {
+fn notify_if_database_failed(workspace: &WeakViewHandle<Workspace>, cx: &mut AsyncAppContext) {
     workspace.update(cx, |workspace, cx| {
         if (*db::ALL_FILE_DB_FAILED).load(std::sync::atomic::Ordering::Acquire) {
             workspace.show_notification_once(0, cx, |cx| {
@@ -2989,7 +2981,7 @@ pub struct WorkspaceCreated(WeakViewHandle<Workspace>);
 pub fn activate_workspace_for_project(
     cx: &mut AppContext,
     predicate: impl Fn(&mut Project, &mut ModelContext<Project>) -> bool,
-) -> Option<ViewHandle<Workspace>> {
+) -> Option<WeakViewHandle<Workspace>> {
     for window_id in cx.window_ids().collect::<Vec<_>>() {
         let handle = cx
             .update_window(window_id, |cx| {
@@ -3004,8 +2996,8 @@ pub fn activate_workspace_for_project(
             })
             .flatten();
 
-        if handle.is_some() {
-            return handle;
+        if let Some(handle) = handle {
+            return Some(handle.downgrade());
         }
     }
     None
@@ -3023,7 +3015,7 @@ pub fn open_paths(
     cx: &mut AppContext,
 ) -> Task<
     Result<(
-        ViewHandle<Workspace>,
+        WeakViewHandle<Workspace>,
         Vec<Option<Result<Box<dyn ItemHandle>, anyhow::Error>>>,
     )>,
 > {
@@ -3709,7 +3701,7 @@ mod tests {
 
         workspace
             .update(cx, |workspace, cx| {
-                Pane::go_back(workspace, Some(pane.clone()), cx)
+                Pane::go_back(workspace, Some(pane.downgrade()), cx)
             })
             .await
             .unwrap();

crates/zed/src/main.rs 🔗

@@ -674,13 +674,15 @@ async fn handle_cli_connection(
                             let wait = async move {
                                 if paths.is_empty() {
                                     let (done_tx, done_rx) = oneshot::channel();
-                                    let _subscription = cx.update(|cx| {
-                                        cx.observe_release(&workspace, move |_, _| {
-                                            let _ = done_tx.send(());
-                                        })
-                                    });
-                                    drop(workspace);
-                                    let _ = done_rx.await;
+                                    if let Some(workspace) = workspace.upgrade(&cx) {
+                                        let _subscription = cx.update(|cx| {
+                                            cx.observe_release(&workspace, move |_, _| {
+                                                let _ = done_tx.send(());
+                                            })
+                                        });
+                                        drop(workspace);
+                                        let _ = done_rx.await;
+                                    }
                                 } else {
                                     let _ =
                                         futures::future::try_join_all(item_release_futures).await;

crates/zed/src/zed.rs 🔗

@@ -374,7 +374,14 @@ pub fn build_window_options(
 fn restart(_: &Restart, cx: &mut gpui::AppContext) {
     let mut workspaces = cx
         .window_ids()
-        .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+        .filter_map(|window_id| {
+            Some(
+                cx.root_view(window_id)?
+                    .clone()
+                    .downcast::<Workspace>()?
+                    .downgrade(),
+            )
+        })
         .collect::<Vec<_>>();
 
     // If multiple windows have unsaved changes, and need a save prompt,
@@ -419,7 +426,14 @@ fn restart(_: &Restart, cx: &mut gpui::AppContext) {
 fn quit(_: &Quit, cx: &mut gpui::AppContext) {
     let mut workspaces = cx
         .window_ids()
-        .filter_map(|window_id| cx.root_view(window_id)?.clone().downcast::<Workspace>())
+        .filter_map(|window_id| {
+            Some(
+                cx.root_view(window_id)?
+                    .clone()
+                    .downcast::<Workspace>()?
+                    .downgrade(),
+            )
+        })
         .collect::<Vec<_>>();
 
     // If multiple windows have unsaved changes, and need a save prompt,
@@ -503,49 +517,49 @@ fn open_log_file(
 
     workspace
         .with_local_workspace(&app_state.clone(), cx, move |_, cx| {
-            cx.spawn_weak(|workspace, mut cx| async move {
+            cx.spawn(|workspace, mut cx| async move {
                 let (old_log, new_log) = futures::join!(
                     app_state.fs.load(&paths::OLD_LOG),
                     app_state.fs.load(&paths::LOG)
                 );
 
-                if let Some(workspace) = workspace.upgrade(&cx) {
-                    let mut lines = VecDeque::with_capacity(MAX_LINES);
-                    for line in old_log
-                        .iter()
-                        .flat_map(|log| log.lines())
-                        .chain(new_log.iter().flat_map(|log| log.lines()))
-                    {
-                        if lines.len() == MAX_LINES {
-                            lines.pop_front();
-                        }
-                        lines.push_back(line);
+                let mut lines = VecDeque::with_capacity(MAX_LINES);
+                for line in old_log
+                    .iter()
+                    .flat_map(|log| log.lines())
+                    .chain(new_log.iter().flat_map(|log| log.lines()))
+                {
+                    if lines.len() == MAX_LINES {
+                        lines.pop_front();
                     }
-                    let log = lines
-                        .into_iter()
-                        .flat_map(|line| [line, "\n"])
-                        .collect::<String>();
-
-                    workspace
-                        .update(&mut cx, |workspace, cx| {
-                            let project = workspace.project().clone();
-                            let buffer = project
-                                .update(cx, |project, cx| project.create_buffer("", None, cx))
-                                .expect("creating buffers on a local workspace always succeeds");
-                            buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
+                    lines.push_back(line);
+                }
+                let log = lines
+                    .into_iter()
+                    .flat_map(|line| [line, "\n"])
+                    .collect::<String>();
 
-                            let buffer = cx.add_model(|cx| {
-                                MultiBuffer::singleton(buffer, cx).with_title("Log".into())
-                            });
-                            workspace.add_item(
-                                Box::new(cx.add_view(|cx| {
+                workspace
+                    .update(&mut cx, |workspace, cx| {
+                        let project = workspace.project().clone();
+                        let buffer = project
+                            .update(cx, |project, cx| project.create_buffer("", None, cx))
+                            .expect("creating buffers on a local workspace always succeeds");
+                        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, log)], None, cx));
+
+                        let buffer = cx.add_model(|cx| {
+                            MultiBuffer::singleton(buffer, cx).with_title("Log".into())
+                        });
+                        workspace.add_item(
+                            Box::new(
+                                cx.add_view(|cx| {
                                     Editor::for_multibuffer(buffer, Some(project), cx)
-                                })),
-                                cx,
-                            );
-                        })
-                        .log_err();
-                }
+                                }),
+                            ),
+                            cx,
+                        );
+                    })
+                    .log_err();
             })
             .detach();
         })
@@ -558,9 +572,7 @@ fn open_telemetry_log_file(
     cx: &mut ViewContext<Workspace>,
 ) {
     workspace.with_local_workspace(&app_state.clone(), cx, move |_, cx| {
-        cx.spawn_weak(|workspace, mut cx| async move {
-            let workspace = workspace.upgrade(&cx)?;
-
+        cx.spawn(|workspace, mut cx| async move {
             async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {
                 let path = app_state.client.telemetry_log_file_path()?;
                 app_state.fs.load(&path).await.log_err()