Show "tab Accept" only for zeta (#23463)

Agus Zubiaga created

#23460 brought up we are showing the new "tab Accept" marker for single
line suggestions for non-zeta providers. We think this might be valid
for any provider, but we only want to enable it for zeta initially so it
doesn't affect an existing user base.

Release Notes:

- N/A

Change summary

crates/editor/src/editor.rs                       | 61 +++++++++++++++-
crates/editor/src/element.rs                      | 59 +++++-----------
crates/editor/src/inline_completion_tests.rs      |  6 -
crates/inline_completion/src/inline_completion.rs |  8 ++
crates/zeta/src/zeta.rs                           |  4 +
5 files changed, 85 insertions(+), 53 deletions(-)

Detailed changes

crates/editor/src/editor.rs 🔗

@@ -485,10 +485,16 @@ enum InlineCompletionText {
     },
 }
 
+pub(crate) enum EditDisplayMode {
+    TabAccept,
+    DiffPopover,
+    Inline,
+}
+
 enum InlineCompletion {
     Edit {
         edits: Vec<(Range<Anchor>, String)>,
-        single_line: bool,
+        display_mode: EditDisplayMode,
     },
     Move(Anchor),
 }
@@ -4691,7 +4697,7 @@ impl Editor {
             }
             InlineCompletion::Edit {
                 edits,
-                single_line: _,
+                display_mode: _,
             } => {
                 if let Some(provider) = self.inline_completion_provider() {
                     provider.accept(cx);
@@ -4741,7 +4747,7 @@ impl Editor {
             }
             InlineCompletion::Edit {
                 edits,
-                single_line: _,
+                display_mode: _,
             } => {
                 // Find an insertion that starts at the cursor position.
                 let snapshot = self.buffer.read(cx).snapshot(cx);
@@ -4941,10 +4947,23 @@ impl Editor {
 
             invalidation_row_range = edit_start_row..edit_end_row;
 
-            let single_line = first_edit_start_point.row == last_edit_end_point.row
-                && !edits.iter().any(|(_, edit)| edit.contains('\n'));
+            let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
+                if provider.show_tab_accept_marker()
+                    && first_edit_start_point.row == last_edit_end_point.row
+                    && !edits.iter().any(|(_, edit)| edit.contains('\n'))
+                {
+                    EditDisplayMode::TabAccept
+                } else {
+                    EditDisplayMode::Inline
+                }
+            } else {
+                EditDisplayMode::DiffPopover
+            };
 
-            completion = InlineCompletion::Edit { edits, single_line };
+            completion = InlineCompletion::Edit {
+                edits,
+                display_mode,
+            };
         };
 
         let invalidation_range = multibuffer
@@ -4987,7 +5006,7 @@ impl Editor {
             let text = match &self.active_inline_completion.as_ref()?.completion {
                 InlineCompletion::Edit {
                     edits,
-                    single_line: _,
+                    display_mode: _,
                 } => inline_completion_edit_text(&editor_snapshot, edits, true, cx),
                 InlineCompletion::Move(target) => {
                     let target_point =
@@ -15275,3 +15294,31 @@ pub struct KillRing(ClipboardItem);
 impl Global for KillRing {}
 
 const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
+
+fn all_edits_insertions_or_deletions(
+    edits: &Vec<(Range<Anchor>, String)>,
+    snapshot: &MultiBufferSnapshot,
+) -> bool {
+    let mut all_insertions = true;
+    let mut all_deletions = true;
+
+    for (range, new_text) in edits.iter() {
+        let range_is_empty = range.to_offset(&snapshot).is_empty();
+        let text_is_empty = new_text.is_empty();
+
+        if range_is_empty != text_is_empty {
+            if range_is_empty {
+                all_deletions = false;
+            } else {
+                all_insertions = false;
+            }
+        } else {
+            return false;
+        }
+
+        if !all_insertions && !all_deletions {
+            return false;
+        }
+    }
+    all_insertions || all_deletions
+}

crates/editor/src/element.rs 🔗

@@ -18,12 +18,13 @@ use crate::{
     mouse_context_menu::{self, MenuPosition, MouseContextMenu},
     scroll::{axis_pair, scroll_amount::ScrollAmount, AxisPair},
     BlockId, ChunkReplacement, CursorShape, CustomBlockId, DisplayPoint, DisplayRow,
-    DocumentHighlightRead, DocumentHighlightWrite, Editor, EditorMode, EditorSettings,
-    EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions, HalfPageDown,
-    HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData, LineDown,
-    LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase, Selection,
-    SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR, FILE_HEADER_HEIGHT,
-    GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN, MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
+    DocumentHighlightRead, DocumentHighlightWrite, EditDisplayMode, Editor, EditorMode,
+    EditorSettings, EditorSnapshot, EditorStyle, ExpandExcerpts, FocusedBlock, GutterDimensions,
+    HalfPageDown, HalfPageUp, HandleInput, HoveredCursor, HoveredHunk, InlineCompletion, JumpData,
+    LineDown, LineUp, OpenExcerpts, PageDown, PageUp, Point, RowExt, RowRangeExt, SelectPhase,
+    Selection, SoftWrap, StickyHeaderExcerpt, ToPoint, ToggleFold, CURSORS_VISIBLE_FOR,
+    FILE_HEADER_HEIGHT, GIT_BLAME_MAX_AUTHOR_CHARS_DISPLAYED, MAX_LINE_LEN,
+    MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
 };
 use client::ParticipantIndex;
 use collections::{BTreeMap, HashMap, HashSet};
@@ -50,7 +51,7 @@ use language::{
 use lsp::DiagnosticSeverity;
 use multi_buffer::{
     Anchor, AnchorRangeExt, ExcerptId, ExcerptInfo, ExpandExcerptDirection, MultiBufferPoint,
-    MultiBufferRow, MultiBufferSnapshot, ToOffset,
+    MultiBufferRow, ToOffset,
 };
 use project::project_settings::{GitGutterSetting, ProjectSettings};
 use settings::Settings;
@@ -1628,7 +1629,8 @@ impl EditorElement {
             if let Some(inline_completion) = editor.active_inline_completion.as_ref() {
                 match &inline_completion.completion {
                     InlineCompletion::Edit {
-                        single_line: true, ..
+                        display_mode: EditDisplayMode::TabAccept,
+                        ..
                     } => padding += INLINE_ACCEPT_SUGGESTION_EM_WIDTHS,
                     _ => {}
                 }
@@ -3386,7 +3388,10 @@ impl EditorElement {
                     Some(element)
                 }
             }
-            InlineCompletion::Edit { edits, single_line } => {
+            InlineCompletion::Edit {
+                edits,
+                display_mode,
+            } => {
                 if self.editor.read(cx).has_active_completions_menu() {
                     return None;
                 }
@@ -3410,8 +3415,8 @@ impl EditorElement {
                     return None;
                 }
 
-                if all_edits_insertions_or_deletions(edits, &editor_snapshot.buffer_snapshot) {
-                    if *single_line {
+                match display_mode {
+                    EditDisplayMode::TabAccept => {
                         let range = &edits.first()?.0;
                         let target_display_point = range.end.to_display_point(editor_snapshot);
 
@@ -3433,8 +3438,8 @@ impl EditorElement {
 
                         return Some(element);
                     }
-
-                    return None;
+                    EditDisplayMode::Inline => return None,
+                    EditDisplayMode::DiffPopover => {}
                 }
 
                 let crate::InlineCompletionText::Edit { text, highlights } =
@@ -5249,34 +5254,6 @@ fn inline_completion_tab_indicator(
         .into_any()
 }
 
-fn all_edits_insertions_or_deletions(
-    edits: &Vec<(Range<Anchor>, String)>,
-    snapshot: &MultiBufferSnapshot,
-) -> bool {
-    let mut all_insertions = true;
-    let mut all_deletions = true;
-
-    for (range, new_text) in edits.iter() {
-        let range_is_empty = range.to_offset(&snapshot).is_empty();
-        let text_is_empty = new_text.is_empty();
-
-        if range_is_empty != text_is_empty {
-            if range_is_empty {
-                all_deletions = false;
-            } else {
-                all_insertions = false;
-            }
-        } else {
-            return false;
-        }
-
-        if !all_insertions && !all_deletions {
-            return false;
-        }
-    }
-    all_insertions || all_deletions
-}
-
 #[allow(clippy::too_many_arguments)]
 fn prepaint_gutter_button(
     button: IconButton,

crates/editor/src/inline_completion_tests.rs 🔗

@@ -286,11 +286,7 @@ fn assert_editor_active_edit_completion(
             .as_ref()
             .expect("editor has no active completion");
 
-        if let InlineCompletion::Edit {
-            edits,
-            single_line: _,
-        } = &completion_state.completion
-        {
+        if let InlineCompletion::Edit { edits, .. } = &completion_state.completion {
             assert(editor.buffer().read(cx).snapshot(cx), edits);
         } else {
             panic!("expected edit completion");

crates/inline_completion/src/inline_completion.rs 🔗

@@ -22,6 +22,9 @@ pub trait InlineCompletionProvider: 'static + Sized {
     fn display_name() -> &'static str;
     fn show_completions_in_menu() -> bool;
     fn show_completions_in_normal_mode() -> bool;
+    fn show_tab_accept_marker() -> bool {
+        false
+    }
     fn is_enabled(
         &self,
         buffer: &Model<Buffer>,
@@ -67,6 +70,7 @@ pub trait InlineCompletionProviderHandle {
     ) -> bool;
     fn show_completions_in_menu(&self) -> bool;
     fn show_completions_in_normal_mode(&self) -> bool;
+    fn show_tab_accept_marker(&self) -> bool;
     fn needs_terms_acceptance(&self, cx: &AppContext) -> bool;
     fn is_refreshing(&self, cx: &AppContext) -> bool;
     fn refresh(
@@ -113,6 +117,10 @@ where
         T::show_completions_in_normal_mode()
     }
 
+    fn show_tab_accept_marker(&self) -> bool {
+        T::show_tab_accept_marker()
+    }
+
     fn is_enabled(
         &self,
         buffer: &Model<Buffer>,

crates/zeta/src/zeta.rs 🔗

@@ -1024,6 +1024,10 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
         true
     }
 
+    fn show_tab_accept_marker() -> bool {
+        true
+    }
+
     fn is_enabled(
         &self,
         buffer: &Model<Buffer>,