Finish writing test for the new autocompletion behavior

Antonio Scandurra and Nathan Sobo created

Co-Authored-By: Nathan Sobo <nathan@zed.dev>

Change summary

crates/editor/src/editor.rs | 83 +++++++++++++++++++++++++++++++++-----
crates/gpui/src/executor.rs | 34 ++++++++++++---
2 files changed, 97 insertions(+), 20 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -4000,7 +4000,8 @@ impl Editor {
             if kind == Some(CharKind::Word) && word_range.to_inclusive().contains(&cursor_position)
             {
                 let query = Self::completion_query(&buffer, cursor_position);
-                smol::block_on(completion_state.filter(query.as_deref(), cx.background().clone()));
+                cx.background()
+                    .block(completion_state.filter(query.as_deref(), cx.background().clone()));
                 self.show_completions(&ShowCompletions, cx);
             } else {
                 self.hide_completions(cx);
@@ -7133,15 +7134,7 @@ mod tests {
         let (_, editor) = cx.add_window(|cx| build_editor(buffer, settings, cx));
 
         editor.update(&mut cx, |editor, cx| {
-            editor.select_ranges(
-                [
-                    Point::new(0, 3)..Point::new(0, 3),
-                    Point::new(1, 3)..Point::new(1, 3),
-                    Point::new(2, 3)..Point::new(2, 3),
-                ],
-                None,
-                cx,
-            );
+            editor.select_ranges([Point::new(0, 3)..Point::new(0, 3)], None, cx);
             editor.handle_input(&Input(".".to_string()), cx);
         });
 
@@ -7150,8 +7143,8 @@ mod tests {
             "/the/file",
             Point::new(0, 4),
             &[
-                (Point::new(0, 4)..Point::new(0, 4), "first completion"),
-                (Point::new(0, 4)..Point::new(0, 4), "second completion"),
+                (Point::new(0, 4)..Point::new(0, 4), "first_completion"),
+                (Point::new(0, 4)..Point::new(0, 4), "second_completion"),
             ],
         )
         .await;
@@ -7189,6 +7182,71 @@ mod tests {
             .unindent()
         );
 
+        editor.update(&mut cx, |editor, cx| {
+            editor.select_ranges(
+                [
+                    Point::new(1, 3)..Point::new(1, 3),
+                    Point::new(2, 5)..Point::new(2, 5),
+                ],
+                None,
+                cx,
+            );
+
+            editor.handle_input(&Input(" ".to_string()), cx);
+            assert!(editor.completion_state.is_none());
+            editor.handle_input(&Input("s".to_string()), cx);
+            assert!(editor.completion_state.is_none());
+        });
+
+        handle_completion_request(
+            &mut fake,
+            "/the/file",
+            Point::new(2, 7),
+            &[
+                (Point::new(2, 6)..Point::new(2, 7), "fourth_completion"),
+                (Point::new(2, 6)..Point::new(2, 7), "fifth_completion"),
+                (Point::new(2, 6)..Point::new(2, 7), "sixth_completion"),
+            ],
+        )
+        .await;
+        editor
+            .condition(&cx, |editor, _| editor.completion_state.is_some())
+            .await;
+
+        editor.update(&mut cx, |editor, cx| {
+            editor.handle_input(&Input("i".to_string()), cx);
+        });
+
+        handle_completion_request(
+            &mut fake,
+            "/the/file",
+            Point::new(2, 8),
+            &[
+                (Point::new(2, 6)..Point::new(2, 8), "fourth_completion"),
+                (Point::new(2, 6)..Point::new(2, 8), "fifth_completion"),
+                (Point::new(2, 6)..Point::new(2, 8), "sixth_completion"),
+            ],
+        )
+        .await;
+        editor.next_notification(&cx).await;
+
+        let apply_additional_edits = editor.update(&mut cx, |editor, cx| {
+            let apply_additional_edits = editor.confirm_completion(None, cx).unwrap();
+            assert_eq!(
+                editor.text(cx),
+                "
+                    one.second_completion
+                    two sixth_completion
+                    three sixth_completion
+                    additional edit
+                "
+                .unindent()
+            );
+            apply_additional_edits
+        });
+        handle_resolve_completion_request(&mut fake, None).await;
+        apply_additional_edits.await.unwrap();
+
         async fn handle_completion_request(
             fake: &mut FakeLanguageServer,
             path: &str,
@@ -7208,6 +7266,7 @@ mod tests {
             let completions = completions
                 .iter()
                 .map(|(range, new_text)| lsp::CompletionItem {
+                    label: new_text.to_string(),
                     text_edit: Some(lsp::CompletionTextEdit::Edit(lsp::TextEdit {
                         range: lsp::Range::new(
                             lsp::Position::new(range.start.row, range.start.column),

crates/gpui/src/executor.rs 🔗

@@ -236,16 +236,14 @@ impl Deterministic {
         }
     }
 
-    fn block_on(&self, future: &mut AnyLocalFuture) -> Option<Box<dyn Any>> {
+    fn block<F, T>(&self, future: &mut F, max_ticks: usize) -> Option<T>
+    where
+        F: Unpin + Future<Output = T>,
+    {
         let unparker = self.parker.lock().unparker();
         let waker = waker_fn(move || {
             unparker.unpark();
         });
-        let max_ticks = {
-            let mut state = self.state.lock();
-            let range = state.block_on_ticks.clone();
-            state.rng.gen_range(range)
-        };
 
         let mut cx = Context::from_waker(&waker);
         for _ in 0..max_ticks {
@@ -258,7 +256,7 @@ impl Deterministic {
                 runnable.run();
             } else {
                 drop(state);
-                if let Poll::Ready(result) = future.as_mut().poll(&mut cx) {
+                if let Poll::Ready(result) = future.poll(&mut cx) {
                     return Some(result);
                 }
                 let mut state = self.state.lock();
@@ -488,6 +486,19 @@ impl Background {
         Task::send(any_task)
     }
 
+    pub fn block<F, T>(&self, future: F) -> T
+    where
+        F: Future<Output = T>,
+    {
+        smol::pin!(future);
+        match self {
+            Self::Production { .. } => smol::block_on(&mut future),
+            Self::Deterministic { executor, .. } => {
+                executor.block(&mut future, usize::MAX).unwrap()
+            }
+        }
+    }
+
     pub fn block_with_timeout<F, T>(
         &self,
         timeout: Duration,
@@ -501,7 +512,14 @@ impl Background {
         if !timeout.is_zero() {
             let output = match self {
                 Self::Production { .. } => smol::block_on(util::timeout(timeout, &mut future)).ok(),
-                Self::Deterministic { executor, .. } => executor.block_on(&mut future),
+                Self::Deterministic { executor, .. } => {
+                    let max_ticks = {
+                        let mut state = executor.state.lock();
+                        let range = state.block_on_ticks.clone();
+                        state.rng.gen_range(range)
+                    };
+                    executor.block(&mut future, max_ticks)
+                }
             };
             if let Some(output) = output {
                 return Ok(*output.downcast().unwrap());