zed1: Cancel completion resolution when new list (#3389)

Julia created

Release Notes:

- Fixed a bug where Zed would continue to request documentation for
completion lists which were stale or no longer visible.

Change summary

crates/editor/src/editor.rs | 51 +++++++++++++++++++++++---------------
1 file changed, 31 insertions(+), 20 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1001,17 +1001,18 @@ impl CompletionsMenu {
 
     fn pre_resolve_completion_documentation(
         &self,
-        project: Option<ModelHandle<Project>>,
+        editor: &Editor,
         cx: &mut ViewContext<Editor>,
-    ) {
+    ) -> Option<Task<()>> {
         let settings = settings::get::<EditorSettings>(cx);
         if !settings.show_completion_documentation {
-            return;
+            return None;
         }
 
-        let Some(project) = project else {
-            return;
+        let Some(project) = editor.project.clone() else {
+            return None;
         };
+
         let client = project.read(cx).client();
         let language_registry = project.read(cx).languages().clone();
 
@@ -1021,7 +1022,7 @@ impl CompletionsMenu {
         let completions = self.completions.clone();
         let completion_indices: Vec<_> = self.matches.iter().map(|m| m.candidate_id).collect();
 
-        cx.spawn(move |this, mut cx| async move {
+        Some(cx.spawn(move |this, mut cx| async move {
             if is_remote {
                 let Some(project_id) = project_id else {
                     log::error!("Remote project without remote_id");
@@ -1083,8 +1084,7 @@ impl CompletionsMenu {
                     _ = this.update(&mut cx, |_, cx| cx.notify());
                 }
             }
-        })
-        .detach();
+        }))
     }
 
     fn attempt_resolve_selected_completion_documentation(
@@ -3580,7 +3580,8 @@ impl Editor {
         let id = post_inc(&mut self.next_completion_id);
         let task = cx.spawn(|this, mut cx| {
             async move {
-                let menu = if let Some(completions) = completions.await.log_err() {
+                let completions = completions.await.log_err();
+                let (menu, pre_resolve_task) = if let Some(completions) = completions {
                     let mut menu = CompletionsMenu {
                         id,
                         initial_position: position,
@@ -3601,21 +3602,26 @@ impl Editor {
                         selected_item: 0,
                         list: Default::default(),
                     };
+
                     menu.filter(query.as_deref(), cx.background()).await;
+
                     if menu.matches.is_empty() {
-                        None
+                        (None, None)
                     } else {
-                        _ = this.update(&mut cx, |editor, cx| {
-                            menu.pre_resolve_completion_documentation(editor.project.clone(), cx);
-                        });
-                        Some(menu)
+                        let pre_resolve_task = this
+                            .update(&mut cx, |editor, cx| {
+                                menu.pre_resolve_completion_documentation(editor, cx)
+                            })
+                            .ok()
+                            .flatten();
+                        (Some(menu), pre_resolve_task)
                     }
                 } else {
-                    None
+                    (None, None)
                 };
 
                 this.update(&mut cx, |this, cx| {
-                    this.completion_tasks.retain(|(task_id, _)| *task_id > id);
+                    this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
 
                     let mut context_menu = this.context_menu.write();
                     match context_menu.as_ref() {
@@ -3636,10 +3642,10 @@ impl Editor {
                         drop(context_menu);
                         this.discard_copilot_suggestion(cx);
                         cx.notify();
-                    } else if this.completion_tasks.is_empty() {
-                        // If there are no more completion tasks and the last menu was
-                        // empty, we should hide it. If it was already hidden, we should
-                        // also show the copilot suggestion when available.
+                    } else if this.completion_tasks.len() <= 1 {
+                        // If there are no more completion tasks (omitting ourself) and
+                        // the last menu was empty, we should hide it. If it was already
+                        // hidden, we should also show the copilot suggestion when available.
                         drop(context_menu);
                         if this.hide_context_menu(cx).is_none() {
                             this.update_visible_copilot_suggestion(cx);
@@ -3647,10 +3653,15 @@ impl Editor {
                     }
                 })?;
 
+                if let Some(pre_resolve_task) = pre_resolve_task {
+                    pre_resolve_task.await;
+                }
+
                 Ok::<_, anyhow::Error>(())
             }
             .log_err()
         });
+
         self.completion_tasks.push((id, task));
     }