WIP: lower our usage of the copilot API by seperating out the cycling completion

Mikayla Maki and antonio created

Restore copilot setting visibility

co-authored-by: antonio <antonio@zed.dev>

Change summary

crates/editor/src/editor.rs | 143 +++++++++++++++++++++++++-------------
1 file changed, 94 insertions(+), 49 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -1015,15 +1015,17 @@ impl CodeActionsMenu {
 pub struct CopilotState {
     excerpt_id: Option<ExcerptId>,
     pending_refresh: Task<Option<()>>,
+    pending_cycling_refresh: Task<Option<()>>,
+    cycled: bool,
     completions: Vec<copilot::Completion>,
     active_completion_index: usize,
-    cycled: bool,
 }
 
 impl Default for CopilotState {
     fn default() -> Self {
         Self {
             excerpt_id: None,
+            pending_cycling_refresh: Task::ready(Some(())),
             pending_refresh: Task::ready(Some(())),
             completions: Default::default(),
             active_completion_index: 0,
@@ -1071,6 +1073,22 @@ impl CopilotState {
         }
     }
 
+    fn cycle_completions(&mut self, direction: Direction) {
+        match direction {
+            Direction::Prev => {
+                self.active_completion_index = if self.active_completion_index == 0 {
+                    self.completions.len() - 1
+                } else {
+                    self.active_completion_index - 1
+                };
+            }
+            Direction::Next => {
+                self.active_completion_index =
+                    (self.active_completion_index + 1) % self.completions.len();
+            }
+        }
+    }
+
     fn push_completion(&mut self, new_completion: copilot::Completion) {
         for completion in &self.completions {
             if *completion == new_completion {
@@ -2029,13 +2047,13 @@ impl Editor {
             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
 
             if had_active_copilot_suggestion {
-                this.refresh_copilot_suggestions(false, cx);
+                this.refresh_copilot_suggestions(cx);
                 if !this.has_active_copilot_suggestion(cx) {
                     this.trigger_completion_on_input(&text, cx);
                 }
             } else {
                 this.trigger_completion_on_input(&text, cx);
-                this.refresh_copilot_suggestions(false, cx);
+                this.refresh_copilot_suggestions(cx);
             }
         });
     }
@@ -2117,7 +2135,7 @@ impl Editor {
                 .collect();
 
             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(new_selections));
-            this.refresh_copilot_suggestions(false, cx);
+            this.refresh_copilot_suggestions(cx);
         });
     }
 
@@ -2515,7 +2533,7 @@ impl Editor {
                 });
             }
 
-            this.refresh_copilot_suggestions(false, cx);
+            this.refresh_copilot_suggestions(cx);
         });
 
         let project = self.project.clone()?;
@@ -2808,11 +2826,7 @@ impl Editor {
         None
     }
 
-    fn refresh_copilot_suggestions(
-        &mut self,
-        cycling: bool,
-        cx: &mut ViewContext<Self>,
-    ) -> Option<()> {
+    fn refresh_copilot_suggestions(&mut self, cx: &mut ViewContext<Self>) -> Option<()> {
         let copilot = Copilot::global(cx)?;
         if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
             self.clear_copilot_suggestions(cx);
@@ -2838,30 +2852,31 @@ impl Editor {
 
             let mut completions = Vec::new();
 
-            let completions_iter = if cycling {
-                copilot
-                    .update(&mut cx, |copilot, cx| {
-                        copilot.completions(&buffer, buffer_position, cx)
-                    })
-                    .await
-            } else {
-                copilot
-                    .update(&mut cx, |copilot, cx| {
-                        copilot.completions_cycling(&buffer, buffer_position, cx)
-                    })
-                    .await
-            };
+            // let completions_iter = if cycling {
+            let completions_iter = copilot
+                .update(&mut cx, |copilot, cx| {
+                    copilot.completions(&buffer, buffer_position, cx)
+                })
+                .await;
+            // } else {
+            //     copilot
+            //         .update(&mut cx, |copilot, cx| {
+            //             copilot.completions_cycling(&buffer, buffer_position, cx)
+            //         })
+            //         .await
+            // };
             completions.extend(completions_iter.log_err().into_iter().flatten());
 
             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.copilot_state.cycled = cycling;
                     this.update_visible_copilot_suggestion(cx);
                 }
             });
@@ -2872,9 +2887,49 @@ impl Editor {
         Some(())
     }
 
+    fn cycle_suggestions(
+        &mut self,
+        direction: Direction,
+        cx: &mut ViewContext<Self>,
+    ) -> Option<()> {
+        let copilot = Copilot::global(cx)?;
+        if self.mode != EditorMode::Full || !copilot.read(cx).status().is_authorized() {
+            return None;
+        }
+
+        if self.copilot_state.cycled {
+            self.copilot_state.cycle_completions(direction);
+            self.update_visible_copilot_suggestion(cx);
+        } else {
+            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 {
+                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);
+                });
+
+                Some(())
+            });
+        }
+
+        Some(())
+    }
+
     fn copilot_suggest(&mut self, _: &copilot::Suggest, cx: &mut ViewContext<Self>) {
         if !self.has_active_copilot_suggestion(cx) {
-            self.refresh_copilot_suggestions(false, cx);
+            self.refresh_copilot_suggestions(cx);
             return;
         }
 
@@ -2882,14 +2937,11 @@ impl Editor {
     }
 
     fn next_copilot_suggestion(&mut self, _: &copilot::NextSuggestion, cx: &mut ViewContext<Self>) {
-        if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled {
-            self.refresh_copilot_suggestions(true, cx);
-            return;
+        if self.has_active_copilot_suggestion(cx) {
+            self.cycle_suggestions(Direction::Next, cx);
+        } else {
+            self.refresh_copilot_suggestions(cx);
         }
-
-        self.copilot_state.active_completion_index =
-            (self.copilot_state.active_completion_index + 1) % self.copilot_state.completions.len();
-        self.update_visible_copilot_suggestion(cx);
     }
 
     fn previous_copilot_suggestion(
@@ -2897,18 +2949,11 @@ impl Editor {
         _: &copilot::PreviousSuggestion,
         cx: &mut ViewContext<Self>,
     ) {
-        if !self.has_active_copilot_suggestion(cx) || !self.copilot_state.cycled {
-            self.refresh_copilot_suggestions(true, cx);
-            return;
+        if self.has_active_copilot_suggestion(cx) {
+            self.cycle_suggestions(Direction::Prev, cx);
+        } else {
+            self.refresh_copilot_suggestions(cx);
         }
-
-        self.copilot_state.active_completion_index =
-            if self.copilot_state.active_completion_index == 0 {
-                self.copilot_state.completions.len() - 1
-            } else {
-                self.copilot_state.active_completion_index - 1
-            };
-        self.update_visible_copilot_suggestion(cx);
     }
 
     fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
@@ -3255,7 +3300,7 @@ impl Editor {
 
             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
             this.insert("", cx);
-            this.refresh_copilot_suggestions(false, cx);
+            this.refresh_copilot_suggestions(cx);
         });
     }
 
@@ -3271,7 +3316,7 @@ impl Editor {
                 })
             });
             this.insert("", cx);
-            this.refresh_copilot_suggestions(false, cx);
+            this.refresh_copilot_suggestions(cx);
         });
     }
 
@@ -3367,7 +3412,7 @@ impl Editor {
         self.transact(cx, |this, cx| {
             this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
             this.change_selections(Some(Autoscroll::fit()), cx, |s| s.select(selections));
-            this.refresh_copilot_suggestions(false, cx);
+            this.refresh_copilot_suggestions(cx);
         });
     }
 
@@ -4047,7 +4092,7 @@ impl Editor {
             }
             self.request_autoscroll(Autoscroll::fit(), cx);
             self.unmark_text(cx);
-            self.refresh_copilot_suggestions(false, cx);
+            self.refresh_copilot_suggestions(cx);
             cx.emit(Event::Edited);
         }
     }
@@ -4062,7 +4107,7 @@ impl Editor {
             }
             self.request_autoscroll(Autoscroll::fit(), cx);
             self.unmark_text(cx);
-            self.refresh_copilot_suggestions(false, cx);
+            self.refresh_copilot_suggestions(cx);
             cx.emit(Event::Edited);
         }
     }
@@ -6524,7 +6569,7 @@ impl Editor {
     }
 
     fn settings_changed(&mut self, cx: &mut ViewContext<Self>) {
-        self.refresh_copilot_suggestions(false, cx);
+        self.refresh_copilot_suggestions(cx);
     }
 
     pub fn set_searchable(&mut self, searchable: bool) {