Revise "Hide/Show Inline Completions" menu (#23808)

Richard Feldman , Nathan , Danilo Leal , and Marshall Bowers created

> **Note:** https://github.com/zed-industries/zed/pull/23813 should be
merged first!

@nathansobo and I paired on revising this menu, including adding the
"Predict Edits at Cursor" menu item (to make the keyboard shortcut more
discoverable; clicking it makes the inline edits show up, as shown in
the second screenshot) and switching from "Hide/Show" language to
checkboxes.

## Before
<img width="282" alt="Screenshot 2025-01-28 at 4 51 37 PM"
src="https://github.com/user-attachments/assets/309c82c1-8fb5-44db-950e-1a8789a63993"
/>

## After
<img width="1138" alt="Screenshot 2025-01-28 at 4 50 05 PM"
src="https://github.com/user-attachments/assets/302a126c-9389-42a4-bb7d-2896bce859e7"
/>

We also switched to use `SharedString` in more places, where it made
more sense.

@danilo-leal This isn't necessarily *exactly* what we want, but we were
pairing and decided to get it in a state where we can actually try it
out and tweak from here.

Release Notes:

- N/A

---------

Co-authored-by: Nathan <nathan@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>
Co-authored-by: Marshall Bowers <git@maxdeviant.com>

Change summary

crates/inline_completion_button/src/inline_completion_button.rs | 68 ++
crates/ui/src/components/list/list_sub_header.rs                |  8 
2 files changed, 52 insertions(+), 24 deletions(-)

Detailed changes

crates/inline_completion_button/src/inline_completion_button.rs 🔗

@@ -1,14 +1,15 @@
 use anyhow::Result;
 use client::UserStore;
 use copilot::{Copilot, Status};
-use editor::{scroll::Autoscroll, Editor};
+use editor::{actions::ShowInlineCompletion, scroll::Autoscroll, Editor};
 use feature_flags::{
     FeatureFlagAppExt, PredictEditsFeatureFlag, PredictEditsRateCompletionsFeatureFlag,
 };
 use fs::Fs;
 use gpui::{
     actions, div, pulsating_between, Action, Animation, AnimationExt, App, AsyncWindowContext,
-    Corner, Entity, IntoElement, ParentElement, Render, Subscription, WeakEntity,
+    Corner, Entity, FocusHandle, Focusable, IntoElement, ParentElement, Render, Subscription,
+    WeakEntity,
 };
 use language::{
     language_settings::{
@@ -43,6 +44,7 @@ struct CopilotErrorToast;
 pub struct InlineCompletionButton {
     editor_subscription: Option<(Subscription, usize)>,
     editor_enabled: Option<bool>,
+    editor_focus_handle: Option<FocusHandle>,
     language: Option<Arc<Language>>,
     file: Option<Arc<dyn File>>,
     inline_completion_provider: Option<Arc<dyn inline_completion::InlineCompletionProviderHandle>>,
@@ -329,6 +331,7 @@ impl InlineCompletionButton {
         Self {
             editor_subscription: None,
             editor_enabled: None,
+            editor_focus_handle: None,
             language: None,
             file: None,
             inline_completion_provider: None,
@@ -364,21 +367,26 @@ impl InlineCompletionButton {
         })
     }
 
+    // Predict Edits at Cursor – alt-tab
+    // Automatically Predict:
+    // ✓ PATH
+    // ✓ Rust
+    // ✓ All Files
     pub fn build_language_settings_menu(&self, mut menu: ContextMenu, cx: &mut App) -> ContextMenu {
         let fs = self.fs.clone();
 
+        menu = menu.header("Predict Edits For:");
+
         if let Some(language) = self.language.clone() {
             let fs = fs.clone();
             let language_enabled =
                 language_settings::language_settings(Some(language.name()), None, cx)
                     .show_inline_completions;
 
-            menu = menu.entry(
-                format!(
-                    "{} Inline Completions for {}",
-                    if language_enabled { "Hide" } else { "Show" },
-                    language.name()
-                ),
+            menu = menu.toggleable_entry(
+                language.name(),
+                language_enabled,
+                IconPosition::Start,
                 None,
                 move |_, cx| {
                     toggle_inline_completions_for_language(language.clone(), fs.clone(), cx)
@@ -387,16 +395,14 @@ impl InlineCompletionButton {
         }
 
         let settings = AllLanguageSettings::get_global(cx);
-
         if let Some(file) = &self.file {
             let path = file.path().clone();
             let path_enabled = settings.inline_completions_enabled_for_path(&path);
 
-            menu = menu.entry(
-                format!(
-                    "{} Inline Completions for This Path",
-                    if path_enabled { "Hide" } else { "Show" }
-                ),
+            menu = menu.toggleable_entry(
+                "This File",
+                path_enabled,
+                IconPosition::Start,
                 None,
                 move |window, cx| {
                     if let Some(workspace) = window.root().flatten() {
@@ -416,15 +422,32 @@ impl InlineCompletionButton {
         }
 
         let globally_enabled = settings.inline_completions_enabled(None, None, cx);
-        menu.entry(
-            if globally_enabled {
-                "Hide Inline Completions for All Files"
-            } else {
-                "Show Inline Completions for All Files"
-            },
+        menu = menu.toggleable_entry(
+            "All Files",
+            globally_enabled,
+            IconPosition::Start,
             None,
             move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
-        )
+        );
+
+        if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
+            menu = menu
+                .separator()
+                .entry(
+                    "Predict Edit at Cursor",
+                    Some(Box::new(ShowInlineCompletion)),
+                    {
+                        let editor_focus_handle = editor_focus_handle.clone();
+
+                        move |window, cx| {
+                            editor_focus_handle.dispatch_action(&ShowInlineCompletion, window, cx);
+                        }
+                    },
+                )
+                .context(editor_focus_handle);
+        }
+
+        menu
     }
 
     fn build_copilot_context_menu(
@@ -468,7 +491,7 @@ impl InlineCompletionButton {
             self.build_language_settings_menu(menu, cx).when(
                 cx.has_flag::<PredictEditsRateCompletionsFeatureFlag>(),
                 |this| {
-                    this.separator().entry(
+                    this.entry(
                         "Rate Completions",
                         Some(RateCompletions.boxed_clone()),
                         move |window, cx| {
@@ -504,6 +527,7 @@ impl InlineCompletionButton {
         self.inline_completion_provider = editor.inline_completion_provider();
         self.language = language.cloned();
         self.file = file;
+        self.editor_focus_handle = Some(editor.focus_handle(cx));
 
         cx.notify();
     }

crates/ui/src/components/list/list_sub_header.rs 🔗

@@ -49,7 +49,7 @@ impl RenderOnce for ListSubHeader {
             .px(DynamicSpacing::Base02.rems(cx))
             .child(
                 div()
-                    .h_6()
+                    .h_5()
                     .when(self.inset, |this| this.px_2())
                     .when(self.selected, |this| {
                         this.bg(cx.theme().colors().ghost_element_selected)
@@ -70,7 +70,11 @@ impl RenderOnce for ListSubHeader {
                                     Icon::new(i).color(Color::Muted).size(IconSize::Small)
                                 }),
                             )
-                            .child(Label::new(self.label.clone()).color(Color::Muted)),
+                            .child(
+                                Label::new(self.label.clone())
+                                    .color(Color::Muted)
+                                    .size(LabelSize::Small),
+                            ),
                     ),
             )
     }