Prevent `tab` from accepting a copilot suggestion when it isn't visible

Antonio Scandurra created

Change summary

crates/editor/src/display_map.rs                |  9 +++++--
crates/editor/src/display_map/suggestion_map.rs | 17 ++++++++++----
crates/editor/src/editor.rs                     | 21 +++++++-----------
3 files changed, 26 insertions(+), 21 deletions(-)

Detailed changes

crates/editor/src/display_map.rs 🔗

@@ -7,7 +7,7 @@ mod wrap_map;
 use crate::{Anchor, AnchorRangeExt, MultiBuffer, MultiBufferSnapshot, ToOffset, ToPoint};
 pub use block_map::{BlockMap, BlockPoint};
 use collections::{HashMap, HashSet};
-use fold_map::FoldMap;
+use fold_map::{FoldMap, FoldOffset};
 use gpui::{
     color::Color,
     fonts::{FontId, HighlightStyle},
@@ -238,19 +238,22 @@ impl DisplayMap {
         &self,
         new_suggestion: Option<Suggestion<T>>,
         cx: &mut ModelContext<Self>,
-    ) where
+    ) -> Option<Suggestion<FoldOffset>>
+    where
         T: ToPoint,
     {
         let snapshot = self.buffer.read(cx).snapshot(cx);
         let edits = self.buffer_subscription.consume().into_inner();
         let tab_size = Self::tab_size(&self.buffer, cx);
         let (snapshot, edits) = self.fold_map.read(snapshot, edits);
-        let (snapshot, edits) = self.suggestion_map.replace(new_suggestion, snapshot, edits);
+        let (snapshot, edits, old_suggestion) =
+            self.suggestion_map.replace(new_suggestion, snapshot, edits);
         let (snapshot, edits) = self.tab_map.sync(snapshot, edits, tab_size);
         let (snapshot, edits) = self
             .wrap_map
             .update(cx, |map, cx| map.sync(snapshot, edits, cx));
         self.block_map.read(snapshot, edits);
+        old_suggestion
     }
 
     pub fn set_font(&self, font_id: FontId, font_size: f32, cx: &mut ModelContext<Self>) -> bool {

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

@@ -79,7 +79,11 @@ impl SuggestionMap {
         new_suggestion: Option<Suggestion<T>>,
         fold_snapshot: FoldSnapshot,
         fold_edits: Vec<FoldEdit>,
-    ) -> (SuggestionSnapshot, Vec<SuggestionEdit>)
+    ) -> (
+        SuggestionSnapshot,
+        Vec<SuggestionEdit>,
+        Option<Suggestion<FoldOffset>>,
+    )
     where
         T: ToPoint,
     {
@@ -99,7 +103,8 @@ impl SuggestionMap {
         let mut snapshot = self.0.lock();
 
         let mut patch = Patch::new(edits);
-        if let Some(suggestion) = snapshot.suggestion.take() {
+        let old_suggestion = snapshot.suggestion.take();
+        if let Some(suggestion) = &old_suggestion {
             patch = patch.compose([SuggestionEdit {
                 old: SuggestionOffset(suggestion.position.0)
                     ..SuggestionOffset(suggestion.position.0 + suggestion.text.len()),
@@ -119,7 +124,7 @@ impl SuggestionMap {
 
         snapshot.suggestion = new_suggestion;
         snapshot.version += 1;
-        (snapshot.clone(), patch.into_inner())
+        (snapshot.clone(), patch.into_inner(), old_suggestion)
     }
 
     pub fn sync(
@@ -589,7 +594,7 @@ mod tests {
         let (suggestion_map, suggestion_snapshot) = SuggestionMap::new(fold_snapshot.clone());
         assert_eq!(suggestion_snapshot.text(), "abcdefghi");
 
-        let (suggestion_snapshot, _) = suggestion_map.replace(
+        let (suggestion_snapshot, _, _) = suggestion_map.replace(
             Some(Suggestion {
                 position: 3,
                 text: "123\n456".into(),
@@ -854,7 +859,9 @@ mod tests {
             };
 
             log::info!("replacing suggestion with {:?}", new_suggestion);
-            self.replace(new_suggestion, fold_snapshot, Default::default())
+            let (snapshot, edits, _) =
+                self.replace(new_suggestion, fold_snapshot, Default::default());
+            (snapshot, edits)
         }
     }
 }

crates/editor/src/editor.rs 🔗

@@ -53,7 +53,7 @@ pub use language::{char_kind, CharKind};
 use language::{
     AutoindentMode, BracketPair, Buffer, CodeAction, CodeLabel, Completion, CursorShape,
     Diagnostic, DiagnosticSeverity, IndentKind, IndentSize, Language, OffsetRangeExt, OffsetUtf16,
-    Point, Selection, SelectionGoal, TransactionId,
+    Point, Rope, Selection, SelectionGoal, TransactionId,
 };
 use link_go_to_definition::{
     hide_link_definition, show_link_definition, LinkDefinitionKind, LinkGoToDefinitionState,
@@ -1834,7 +1834,7 @@ impl Editor {
             return;
         }
 
-        if self.hide_copilot_suggestion(cx) {
+        if self.hide_copilot_suggestion(cx).is_some() {
             return;
         }
 
@@ -2865,14 +2865,8 @@ impl Editor {
     }
 
     fn accept_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
-        let snapshot = self.buffer.read(cx).snapshot(cx);
-        let cursor = self.selections.newest_anchor().head();
-        if let Some(text) = self
-            .copilot_state
-            .text_for_active_completion(cursor, &snapshot)
-        {
+        if let Some(text) = self.hide_copilot_suggestion(cx) {
             self.insert_with_autoindent_mode(&text.to_string(), None, cx);
-            self.hide_copilot_suggestion(cx);
             true
         } else {
             false
@@ -2883,14 +2877,15 @@ impl Editor {
         self.display_map.read(cx).has_suggestion()
     }
 
-    fn hide_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> bool {
+    fn hide_copilot_suggestion(&mut self, cx: &mut ViewContext<Self>) -> Option<Rope> {
         if self.has_active_copilot_suggestion(cx) {
-            self.display_map
+            let old_suggestion = self
+                .display_map
                 .update(cx, |map, cx| map.replace_suggestion::<usize>(None, cx));
             cx.notify();
-            true
+            old_suggestion.map(|suggestion| suggestion.text)
         } else {
-            false
+            None
         }
     }