Merge pull request #2345 from zed-industries/autocomplete-vs-copilot

Antonio Scandurra created

Make autocompletion and copilot mutually exclusive

Change summary

crates/copilot/src/copilot.rs                   | 34 ++++---
crates/editor/src/display_map.rs                |  4 
crates/editor/src/display_map/suggestion_map.rs |  5 +
crates/editor/src/editor.rs                     | 79 ++++++------------
4 files changed, 56 insertions(+), 66 deletions(-)

Detailed changes

crates/copilot/src/copilot.rs 🔗

@@ -522,6 +522,9 @@ impl Copilot {
         let language = snapshot.language_at(position);
         let language_name = language.map(|language| language.name());
         let language_name = language_name.as_deref();
+        let tab_size = settings.tab_size(language_name);
+        let hard_tabs = settings.hard_tabs(language_name);
+        let language_id = id_for_language(language);
 
         let path;
         let relative_path;
@@ -537,22 +540,23 @@ impl Copilot {
             relative_path = PathBuf::new();
         }
 
-        let params = request::GetCompletionsParams {
-            doc: request::GetCompletionsDocument {
-                source: snapshot.text(),
-                tab_size: settings.tab_size(language_name).into(),
-                indent_size: 1,
-                insert_spaces: !settings.hard_tabs(language_name),
-                uri,
-                path: path.to_string_lossy().into(),
-                relative_path: relative_path.to_string_lossy().into(),
-                language_id: id_for_language(language),
-                position: point_to_lsp(position),
-                version: 0,
-            },
-        };
         cx.background().spawn(async move {
-            let result = server.request::<R>(params).await?;
+            let result = server
+                .request::<R>(request::GetCompletionsParams {
+                    doc: request::GetCompletionsDocument {
+                        source: snapshot.text(),
+                        tab_size: tab_size.into(),
+                        indent_size: 1,
+                        insert_spaces: !hard_tabs,
+                        uri,
+                        path: path.to_string_lossy().into(),
+                        relative_path: relative_path.to_string_lossy().into(),
+                        language_id,
+                        position: point_to_lsp(position),
+                        version: 0,
+                    },
+                })
+                .await?;
             let completions = result
                 .completions
                 .into_iter()

crates/editor/src/display_map.rs 🔗

@@ -230,6 +230,10 @@ impl DisplayMap {
         self.text_highlights.remove(&Some(type_id))
     }
 
+    pub fn has_suggestion(&self) -> bool {
+        self.suggestion_map.has_suggestion()
+    }
+
     pub fn replace_suggestion<T>(
         &self,
         new_suggestion: Option<Suggestion<T>>,

crates/editor/src/display_map/suggestion_map.rs 🔗

@@ -171,6 +171,11 @@ impl SuggestionMap {
 
         (snapshot.clone(), suggestion_edits)
     }
+
+    pub fn has_suggestion(&self) -> bool {
+        let snapshot = self.0.lock();
+        snapshot.suggestion.is_some()
+    }
 }
 
 #[derive(Clone)]

crates/editor/src/editor.rs 🔗

@@ -2188,7 +2188,9 @@ impl Editor {
     }
 
     fn trigger_completion_on_input(&mut self, text: &str, cx: &mut ViewContext<Self>) {
-        if !cx.global::<Settings>().show_completions_on_input {
+        if !cx.global::<Settings>().show_completions_on_input
+            || self.has_active_copilot_suggestion(cx)
+        {
             return;
         }
 
@@ -2373,11 +2375,11 @@ impl Editor {
                         }
 
                         this.completion_tasks.retain(|(id, _)| *id > menu.id);
-                        if this.focused {
+                        if this.focused && !menu.matches.is_empty() {
                             this.show_context_menu(ContextMenu::Completions(menu), cx);
+                        } else {
+                            this.hide_context_menu(cx);
                         }
-
-                        cx.notify();
                     });
                 }
                 Ok::<_, anyhow::Error>(())
@@ -2806,33 +2808,9 @@ impl Editor {
         let cursor = if selection.start == selection.end {
             selection.start.bias_left(&snapshot)
         } else {
-            self.clear_copilot_suggestions(cx);
             return None;
         };
-
-        if let Some(new_text) = self
-            .copilot_state
-            .text_for_active_completion(cursor, &snapshot)
-        {
-            self.display_map.update(cx, |map, cx| {
-                map.replace_suggestion(
-                    Some(Suggestion {
-                        position: cursor,
-                        text: new_text.into(),
-                    }),
-                    cx,
-                )
-            });
-            self.copilot_state
-                .completions
-                .swap(0, self.copilot_state.active_completion_index);
-            self.copilot_state.completions.truncate(1);
-            self.copilot_state.active_completion_index = 0;
-            cx.notify();
-        } else {
-            self.display_map
-                .update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
-        }
+        self.refresh_active_copilot_suggestion(cx);
 
         if !copilot.read(cx).status().is_authorized() {
             return None;
@@ -2860,23 +2838,7 @@ impl Editor {
                     for completion in completions {
                         this.copilot_state.push_completion(completion);
                     }
-
-                    let buffer = this.buffer.read(cx).snapshot(cx);
-                    if let Some(text) = this
-                        .copilot_state
-                        .text_for_active_completion(cursor, &buffer)
-                    {
-                        this.display_map.update(cx, |map, cx| {
-                            map.replace_suggestion(
-                                Some(Suggestion {
-                                    position: cursor,
-                                    text: text.into(),
-                                }),
-                                cx,
-                            )
-                        });
-                    }
-                    cx.notify();
+                    this.refresh_active_copilot_suggestion(cx);
                 }
             });
 
@@ -2901,7 +2863,7 @@ impl Editor {
         self.copilot_state.active_completion_index =
             (self.copilot_state.active_completion_index + 1) % self.copilot_state.completions.len();
 
-        self.sync_suggestion(cx);
+        self.refresh_active_copilot_suggestion(cx);
     }
 
     fn previous_copilot_suggestion(
@@ -2927,7 +2889,7 @@ impl Editor {
                 self.copilot_state.active_completion_index - 1
             };
 
-        self.sync_suggestion(cx);
+        self.refresh_active_copilot_suggestion(cx);
     }
 
     fn toggle_copilot_suggestions(&mut self, _: &copilot::Toggle, cx: &mut ViewContext<Self>) {
@@ -2957,11 +2919,15 @@ impl Editor {
         cx.notify();
     }
 
-    fn sync_suggestion(&mut self, cx: &mut ViewContext<Self>) {
+    fn refresh_active_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let cursor = self.selections.newest_anchor().head();
 
-        if let Some(text) = self
+        if self.context_menu.is_some() {
+            self.display_map
+                .update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
+            cx.notify();
+        } else if let Some(text) = self
             .copilot_state
             .text_for_active_completion(cursor, &snapshot)
         {
@@ -2975,6 +2941,10 @@ impl Editor {
                 )
             });
             cx.notify();
+        } else if self.has_active_copilot_suggestion(cx) {
+            self.display_map
+                .update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
+            cx.notify();
         }
     }
 
@@ -3005,6 +2975,10 @@ impl Editor {
         !was_empty
     }
 
+    fn has_active_copilot_suggestion(&self, cx: &AppContext) -> bool {
+        self.display_map.read(cx).has_suggestion()
+    }
+
     pub fn render_code_actions_indicator(
         &self,
         style: &EditorStyle,
@@ -3120,13 +3094,16 @@ impl Editor {
             self.completion_tasks.clear();
         }
         self.context_menu = Some(menu);
+        self.refresh_active_copilot_suggestion(cx);
         cx.notify();
     }
 
     fn hide_context_menu(&mut self, cx: &mut ViewContext<Self>) -> Option<ContextMenu> {
         cx.notify();
         self.completion_tasks.clear();
-        self.context_menu.take()
+        let context_menu = self.context_menu.take();
+        self.refresh_active_copilot_suggestion(cx);
+        context_menu
     }
 
     pub fn insert_snippet(