Replace `ViewContext::spawn` with `ViewContext::spawn_weak`

Antonio Scandurra created

Change summary

crates/activity_indicator/src/activity_indicator.rs |  2 
crates/collab_ui/src/incoming_call_notification.rs  |  2 
crates/context_menu/src/context_menu.rs             |  4 +
crates/editor/src/editor.rs                         | 16 ++--
crates/editor/src/hover_popover.rs                  |  2 
crates/editor/src/items.rs                          |  9 ++
crates/editor/src/link_go_to_definition.rs          |  2 
crates/editor/src/scroll.rs                         |  2 
crates/gpui/src/app.rs                              | 36 +++++++-----
crates/gpui/src/elements/tooltip.rs                 |  2 
crates/language_selector/src/language_selector.rs   |  4 
crates/picker/src/picker.rs                         |  2 
crates/project_symbols/src/project_symbols.rs       |  4 
crates/search/src/buffer_search.rs                  |  2 
crates/terminal_view/src/terminal_view.rs           | 43 ++++++--------
crates/theme_selector/src/theme_selector.rs         |  2 
crates/welcome/src/base_keymap_picker.rs            |  2 
crates/workspace/src/item.rs                        |  2 
crates/workspace/src/workspace.rs                   | 14 ++--
crates/zed/src/zed.rs                               |  4 
20 files changed, 83 insertions(+), 73 deletions(-)

Detailed changes

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -63,7 +63,7 @@ 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| {

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 {
@@ -2669,7 +2669,7 @@ 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
@@ -2731,7 +2731,7 @@ impl Editor {
 
     async fn open_project_transaction(
         this: ViewHandle<Editor>,
-        workspace: ViewHandle<Workspace>,
+        workspace: WeakViewHandle<Workspace>,
         transaction: ProjectTransaction,
         title: String,
         mut cx: AsyncAppContext,
@@ -2826,7 +2826,7 @@ 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| {
@@ -2865,7 +2865,7 @@ impl Editor {
             project.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
         });
 
-        self.document_highlights_task = Some(cx.spawn_weak(|this, mut cx| async move {
+        self.document_highlights_task = Some(cx.spawn(|this, mut cx| async move {
             let highlights = highlights.log_err().await;
             if let Some((this, highlights)) = this.upgrade(&cx).zip(highlights) {
                 this.update(&mut cx, |this, cx| {
@@ -2961,7 +2961,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;
             }
@@ -3014,7 +3014,7 @@ 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)
@@ -6770,7 +6770,7 @@ 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>()

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 {

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::{
@@ -286,6 +286,9 @@ impl FollowableItem for Editor {
         let update_view::Variant::Editor(message) = message;
         let project = project.clone();
         cx.spawn(|this, mut cx| async move {
+            let this = this
+                .upgrade(&cx)
+                .ok_or_else(|| anyhow!("editor was dropped"))?;
             update_editor_from_message(this, project, message, &mut cx).await
         })
     }
@@ -863,7 +866,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| {

crates/editor/src/scroll.rs 🔗

@@ -245,7 +245,7 @@ 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

crates/gpui/src/app.rs 🔗

@@ -3262,26 +3262,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>,
@@ -4094,15 +4084,31 @@ impl<V: View> WeakViewHandle<V> {
         cx.read_with(|cx| cx.upgrade_view_handle(self))
     }
 
+    pub fn read_with<T>(
+        &self,
+        cx: &impl BorrowAppContext,
+        read: impl FnOnce(&V, &ViewContext<V>) -> T,
+    ) -> Result<T> {
+        cx.read_with(|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,
         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"))
         })
     }
 }

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

@@ -99,7 +99,7 @@ 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;

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()

crates/picker/src/picker.rs 🔗

@@ -238,7 +238,7 @@ 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))

crates/project_symbols/src/project_symbols.rs 🔗

@@ -117,7 +117,7 @@ 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)
@@ -161,7 +161,7 @@ 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 {

crates/search/src/buffer_search.rs 🔗

@@ -582,7 +582,7 @@ 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| {

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,14 +275,11 @@ 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;
+                if let Some(this) = this.upgrade(&cx) {
+                    this.update(&mut cx, |this, cx| this.blink_cursors(epoch, cx))
+                        .log_err();
                 }
             })
             .detach();
@@ -294,14 +291,11 @@ 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;
+            if let Some(this) = this.upgrade(&cx) {
+                this.update(&mut cx, |this, cx| this.resume_cursor_blinking(epoch, cx))
+                    .log_err();
             }
         })
         .detach();
@@ -646,6 +640,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()

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()

crates/workspace/src/item.rs 🔗

@@ -479,7 +479,7 @@ 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"))?

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;
@@ -733,7 +733,7 @@ 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 =
@@ -752,7 +752,7 @@ 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)
@@ -1129,7 +1129,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);
             }
@@ -1220,7 +1220,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,7 +1975,7 @@ 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, _| {

crates/zed/src/zed.rs 🔗

@@ -503,7 +503,7 @@ 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)
@@ -558,7 +558,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 {
+        cx.spawn(|workspace, mut cx| async move {
             let workspace = workspace.upgrade(&cx)?;
 
             async fn fetch_log_string(app_state: &Arc<AppState>) -> Option<String> {