edit prediction: Improve UX around `disabled_globs` and `show_inline_completions` (#24207)

Bennet Bo Fenner , Danilo , and Danilo Leal created

Release Notes:

- N/A

---------

Co-authored-by: Danilo <danilo@zed.dev>
Co-authored-by: Danilo Leal <daniloleal09@gmail.com>

Change summary

Cargo.lock                                                      |   1 
assets/icons/zed_predict_disabled.svg                           |   6 
crates/copilot/src/copilot_completion_provider.rs               |  21 
crates/editor/src/editor.rs                                     | 143 
crates/inline_completion_button/Cargo.toml                      |   5 
crates/inline_completion_button/src/inline_completion_button.rs | 250 
crates/language/src/language_settings.rs                        |  13 
crates/supermaven/src/supermaven_completion_provider.rs         |  14 
crates/ui/src/components/context_menu.rs                        | 491 +-
crates/ui/src/components/icon.rs                                |   1 
crates/vim/src/vim.rs                                           |   2 
crates/zed/src/zed/quick_action_bar.rs                          |  35 
crates/zeta/src/zeta.rs                                         |  15 
13 files changed, 578 insertions(+), 419 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -6384,6 +6384,7 @@ dependencies = [
  "lsp",
  "paths",
  "project",
+ "regex",
  "serde_json",
  "settings",
  "supermaven",

assets/icons/zed_predict_disabled.svg 🔗

@@ -0,0 +1,6 @@
+<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
+<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M6.75 9.31247L8.25 10.5576V11.75H1.75V10.0803L4.49751 7.44273L5.65909 8.40693L3.73923 10.25H6.75V9.31247ZM8.25 5.85739V4.25H6.31358L8.25 5.85739ZM1.75 5.16209V7.1H3.25V6.4072L1.75 5.16209Z" fill="black"/>
+<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M10.9624 9.40853L11.9014 8L10.6241 6.08397L9.37598 6.91603L10.0986 8L9.80184 8.44518L10.9624 9.40853Z" fill="black"/>
+<path opacity="0.6" fill-rule="evenodd" clip-rule="evenodd" d="M12.8936 11.0116L14.9014 8L12.6241 4.58397L11.376 5.41603L13.0986 8L11.7331 10.0483L12.8936 11.0116Z" fill="black"/>
+<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1225 13.809C14.0341 13.9146 13.877 13.9289 13.7711 13.8409L1.19311 3.40021C1.08659 3.31178 1.07221 3.15362 1.16104 3.04743L1.87752 2.19101C1.96588 2.0854 2.123 2.07112 2.22895 2.15906L14.8069 12.5998C14.9134 12.6882 14.9278 12.8464 14.839 12.9526L14.1225 13.809Z" fill="black"/>
+</svg>

crates/copilot/src/copilot_completion_provider.rs 🔗

@@ -2,10 +2,7 @@ use crate::{Completion, Copilot};
 use anyhow::Result;
 use gpui::{App, Context, Entity, EntityId, Task};
 use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
-use language::{
-    language_settings::{all_language_settings, AllLanguageSettings},
-    Buffer, OffsetRangeExt, ToOffset,
-};
+use language::{language_settings::AllLanguageSettings, Buffer, OffsetRangeExt, ToOffset};
 use settings::Settings;
 use std::{path::Path, time::Duration};
 
@@ -73,19 +70,11 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
 
     fn is_enabled(
         &self,
-        buffer: &Entity<Buffer>,
-        cursor_position: language::Anchor,
+        _buffer: &Entity<Buffer>,
+        _cursor_position: language::Anchor,
         cx: &App,
     ) -> bool {
-        if !self.copilot.read(cx).status().is_authorized() {
-            return false;
-        }
-
-        let buffer = buffer.read(cx);
-        let file = buffer.file();
-        let language = buffer.language_at(cursor_position);
-        let settings = all_language_settings(file, cx);
-        settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
+        self.copilot.read(cx).status().is_authorized()
     }
 
     fn refresh(
@@ -205,7 +194,7 @@ impl InlineCompletionProvider for CopilotCompletionProvider {
     fn discard(&mut self, cx: &mut Context<Self>) {
         let settings = AllLanguageSettings::get_global(cx);
 
-        let copilot_enabled = settings.inline_completions_enabled(None, None, cx);
+        let copilot_enabled = settings.show_inline_completions(None, cx);
 
         if !copilot_enabled {
             return;

crates/editor/src/editor.rs 🔗

@@ -680,7 +680,7 @@ pub struct Editor {
     stale_inline_completion_in_menu: Option<InlineCompletionState>,
     // enable_inline_completions is a switch that Vim can use to disable
     // edit predictions based on its mode.
-    enable_inline_completions: bool,
+    show_inline_completions: bool,
     show_inline_completions_override: Option<bool>,
     menu_inline_completions_policy: MenuInlineCompletionsPolicy,
     inlay_hint_cache: InlayHintCache,
@@ -1388,7 +1388,7 @@ impl Editor {
             next_editor_action_id: EditorActionId::default(),
             editor_actions: Rc::default(),
             show_inline_completions_override: None,
-            enable_inline_completions: true,
+            show_inline_completions: true,
             menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider,
             custom_context_menu: None,
             show_git_blame_gutter: false,
@@ -1818,9 +1818,9 @@ impl Editor {
         self.input_enabled = input_enabled;
     }
 
-    pub fn set_inline_completions_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
-        self.enable_inline_completions = enabled;
-        if !self.enable_inline_completions {
+    pub fn set_show_inline_completions_enabled(&mut self, enabled: bool, cx: &mut Context<Self>) {
+        self.show_inline_completions = enabled;
+        if !self.show_inline_completions {
             self.take_active_inline_completion(cx);
             cx.notify();
         }
@@ -1871,8 +1871,11 @@ impl Editor {
             if let Some((buffer, cursor_buffer_position)) =
                 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
             {
-                let show_inline_completions =
-                    !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx);
+                let show_inline_completions = !self.should_show_inline_completions_in_buffer(
+                    &buffer,
+                    cursor_buffer_position,
+                    cx,
+                );
                 self.set_show_inline_completions(Some(show_inline_completions), window, cx);
             }
         }
@@ -1888,42 +1891,6 @@ impl Editor {
         self.refresh_inline_completion(false, true, window, cx);
     }
 
-    pub fn inline_completions_enabled(&self, cx: &App) -> bool {
-        let cursor = self.selections.newest_anchor().head();
-        if let Some((buffer, buffer_position)) =
-            self.buffer.read(cx).text_anchor_for_position(cursor, cx)
-        {
-            self.should_show_inline_completions(&buffer, buffer_position, cx)
-        } else {
-            false
-        }
-    }
-
-    fn should_show_inline_completions(
-        &self,
-        buffer: &Entity<Buffer>,
-        buffer_position: language::Anchor,
-        cx: &App,
-    ) -> bool {
-        if !self.snippet_stack.is_empty() {
-            return false;
-        }
-
-        if self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) {
-            return false;
-        }
-
-        if let Some(provider) = self.inline_completion_provider() {
-            if let Some(show_inline_completions) = self.show_inline_completions_override {
-                show_inline_completions
-            } else {
-                self.mode == EditorMode::Full && provider.is_enabled(buffer, buffer_position, cx)
-            }
-        } else {
-            false
-        }
-    }
-
     fn inline_completions_disabled_in_scope(
         &self,
         buffer: &Entity<Buffer>,
@@ -4650,9 +4617,18 @@ impl Editor {
         let (buffer, cursor_buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
 
+        if !self.inline_completions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
+            self.discard_inline_completion(false, cx);
+            return None;
+        }
+
         if !user_requested
-            && (!self.enable_inline_completions
-                || !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx)
+            && (!self.show_inline_completions
+                || !self.should_show_inline_completions_in_buffer(
+                    &buffer,
+                    cursor_buffer_position,
+                    cx,
+                )
                 || !self.is_focused(window)
                 || buffer.read(cx).is_empty())
         {
@@ -4665,6 +4641,77 @@ impl Editor {
         Some(())
     }
 
+    pub fn should_show_inline_completions(&self, cx: &App) -> bool {
+        let cursor = self.selections.newest_anchor().head();
+        if let Some((buffer, cursor_position)) =
+            self.buffer.read(cx).text_anchor_for_position(cursor, cx)
+        {
+            self.should_show_inline_completions_in_buffer(&buffer, cursor_position, cx)
+        } else {
+            false
+        }
+    }
+
+    fn should_show_inline_completions_in_buffer(
+        &self,
+        buffer: &Entity<Buffer>,
+        buffer_position: language::Anchor,
+        cx: &App,
+    ) -> bool {
+        if !self.snippet_stack.is_empty() {
+            return false;
+        }
+
+        if self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) {
+            return false;
+        }
+
+        if let Some(show_inline_completions) = self.show_inline_completions_override {
+            show_inline_completions
+        } else {
+            let buffer = buffer.read(cx);
+            self.mode == EditorMode::Full
+                && language_settings(
+                    buffer.language_at(buffer_position).map(|l| l.name()),
+                    buffer.file(),
+                    cx,
+                )
+                .show_inline_completions
+        }
+    }
+
+    pub fn inline_completions_enabled(&self, cx: &App) -> bool {
+        let cursor = self.selections.newest_anchor().head();
+        if let Some((buffer, cursor_position)) =
+            self.buffer.read(cx).text_anchor_for_position(cursor, cx)
+        {
+            self.inline_completions_enabled_in_buffer(&buffer, cursor_position, cx)
+        } else {
+            false
+        }
+    }
+
+    fn inline_completions_enabled_in_buffer(
+        &self,
+        buffer: &Entity<Buffer>,
+        buffer_position: language::Anchor,
+        cx: &App,
+    ) -> bool {
+        maybe!({
+            let provider = self.inline_completion_provider()?;
+            if !provider.is_enabled(&buffer, buffer_position, cx) {
+                return Some(false);
+            }
+            let buffer = buffer.read(cx);
+            let Some(file) = buffer.file() else {
+                return Some(true);
+            };
+            let settings = all_language_settings(Some(file), cx);
+            Some(settings.inline_completions_enabled_for_path(file.path()))
+        })
+        .unwrap_or(false)
+    }
+
     fn cycle_inline_completion(
         &mut self,
         direction: Direction,
@@ -4675,8 +4722,8 @@ impl Editor {
         let cursor = self.selections.newest_anchor().head();
         let (buffer, cursor_buffer_position) =
             self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
-        if !self.enable_inline_completions
-            || !self.should_show_inline_completions(&buffer, cursor_buffer_position, cx)
+        if !self.show_inline_completions
+            || !self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx)
         {
             return None;
         }
@@ -5014,7 +5061,7 @@ impl Editor {
                 || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion()));
         if completions_menu_has_precedence
             || !offset_selection.is_empty()
-            || !self.enable_inline_completions
+            || !self.show_inline_completions
             || self
                 .active_inline_completion
                 .as_ref()

crates/inline_completion_button/Cargo.toml 🔗

@@ -14,6 +14,7 @@ doctest = false
 
 [dependencies]
 anyhow.workspace = true
+client.workspace = true
 copilot.workspace = true
 editor.workspace = true
 feature_flags.workspace = true
@@ -22,14 +23,14 @@ gpui.workspace = true
 inline_completion.workspace = true
 language.workspace = true
 paths.workspace = true
+regex.workspace = true
 settings.workspace = true
 supermaven.workspace = true
+telemetry.workspace = true
 ui.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true
 zeta.workspace = true
-client.workspace = true
-telemetry.workspace = true
 
 [dev-dependencies]
 copilot = { workspace = true, features = ["test-support"] }

crates/inline_completion_button/src/inline_completion_button.rs 🔗

@@ -17,8 +17,12 @@ use language::{
     },
     File, Language,
 };
+use regex::Regex;
 use settings::{update_settings_file, Settings, SettingsStore};
-use std::{path::Path, sync::Arc, time::Duration};
+use std::{
+    sync::{Arc, LazyLock},
+    time::Duration,
+};
 use supermaven::{AccountStatus, Supermaven};
 use ui::{
     prelude::*, Clickable, ContextMenu, ContextMenuEntry, IconButton, IconButtonShape, PopoverMenu,
@@ -71,9 +75,7 @@ impl Render for InlineCompletionButton {
                 };
                 let status = copilot.read(cx).status();
 
-                let enabled = self.editor_enabled.unwrap_or_else(|| {
-                    all_language_settings.inline_completions_enabled(None, None, cx)
-                });
+                let enabled = self.editor_enabled.unwrap_or(false);
 
                 let icon = match status {
                     Status::Error(_) => IconName::CopilotError,
@@ -228,25 +230,35 @@ impl Render for InlineCompletionButton {
                     return div();
                 }
 
-                fn icon_button() -> IconButton {
-                    IconButton::new("zed-predict-pending-button", IconName::ZedPredict)
-                        .shape(IconButtonShape::Square)
-                }
+                let enabled = self.editor_enabled.unwrap_or(false);
+
+                let zeta_icon = if enabled {
+                    IconName::ZedPredict
+                } else {
+                    IconName::ZedPredictDisabled
+                };
 
                 let current_user_terms_accepted =
                     self.user_store.read(cx).current_user_has_accepted_terms();
 
-                if !current_user_terms_accepted.unwrap_or(false) {
-                    let signed_in = current_user_terms_accepted.is_some();
-                    let tooltip_meta = if signed_in {
-                        "Read Terms of Service"
-                    } else {
-                        "Sign in to use"
-                    };
+                let icon_button = || {
+                    let base = IconButton::new("zed-predict-pending-button", zeta_icon)
+                        .shape(IconButtonShape::Square);
+
+                    match (
+                        current_user_terms_accepted,
+                        self.popover_menu_handle.is_deployed(),
+                        enabled,
+                    ) {
+                        (Some(false) | None, _, _) => {
+                            let signed_in = current_user_terms_accepted.is_some();
+                            let tooltip_meta = if signed_in {
+                                "Read Terms of Service"
+                            } else {
+                                "Sign in to use"
+                            };
 
-                    return div().child(
-                        icon_button()
-                            .tooltip(move |window, cx| {
+                            base.tooltip(move |window, cx| {
                                 Tooltip::with_meta(
                                     "Edit Predictions",
                                     None,
@@ -255,27 +267,37 @@ impl Render for InlineCompletionButton {
                                     cx,
                                 )
                             })
-                            .on_click(cx.listener(move |_, _, window, cx| {
-                                telemetry::event!(
-                                    "Pending ToS Clicked",
-                                    source = "Edit Prediction Status Button"
-                                );
-                                window.dispatch_action(
-                                    zed_actions::OpenZedPredictOnboarding.boxed_clone(),
-                                    cx,
-                                );
-                            })),
-                    );
-                }
+                            .on_click(cx.listener(
+                                move |_, _, window, cx| {
+                                    telemetry::event!(
+                                        "Pending ToS Clicked",
+                                        source = "Edit Prediction Status Button"
+                                    );
+                                    window.dispatch_action(
+                                        zed_actions::OpenZedPredictOnboarding.boxed_clone(),
+                                        cx,
+                                    );
+                                },
+                            ))
+                        }
+                        (Some(true), true, _) => base,
+                        (Some(true), false, true) => base.tooltip(|window, cx| {
+                            Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
+                        }),
+                        (Some(true), false, false) => base.tooltip(|window, cx| {
+                            Tooltip::with_meta(
+                                "Edit Prediction",
+                                Some(&ToggleMenu),
+                                "Disabled For This File",
+                                window,
+                                cx,
+                            )
+                        }),
+                    }
+                };
 
                 let this = cx.entity().clone();
 
-                if !self.popover_menu_handle.is_deployed() {
-                    icon_button().tooltip(|window, cx| {
-                        Tooltip::for_action("Edit Prediction", &ToggleMenu, window, cx)
-                    });
-                }
-
                 let mut popover_menu = PopoverMenu::new("zeta")
                     .menu(move |window, cx| {
                         Some(this.update(cx, |this, cx| this.build_zeta_context_menu(window, cx)))
@@ -362,15 +384,10 @@ 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:");
+        menu = menu.header("Show Predict Edits For");
 
         if let Some(language) = self.language.clone() {
             let fs = fs.clone();
@@ -381,66 +398,39 @@ impl InlineCompletionButton {
             menu = menu.toggleable_entry(
                 language.name(),
                 language_enabled,
-                IconPosition::Start,
+                IconPosition::End,
                 None,
                 move |_, cx| {
-                    toggle_inline_completions_for_language(language.clone(), fs.clone(), cx)
+                    toggle_show_inline_completions_for_language(language.clone(), fs.clone(), cx)
                 },
             );
         }
 
         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.toggleable_entry(
-                "This File",
-                path_enabled,
-                IconPosition::Start,
-                None,
-                move |window, cx| {
-                    if let Some(workspace) = window.root().flatten() {
-                        let workspace = workspace.downgrade();
-                        window
-                            .spawn(cx, |cx| {
-                                configure_disabled_globs(
-                                    workspace,
-                                    path_enabled.then_some(path.clone()),
-                                    cx,
-                                )
-                            })
-                            .detach_and_log_err(cx);
-                    }
-                },
-            );
-        }
-
-        let globally_enabled = settings.inline_completions_enabled(None, None, cx);
+        let globally_enabled = settings.show_inline_completions(None, cx);
         menu = menu.toggleable_entry(
             "All Files",
             globally_enabled,
-            IconPosition::Start,
+            IconPosition::End,
             None,
             move |_, cx| toggle_inline_completions_globally(fs.clone(), cx),
         );
+        menu = menu.separator().header("Privacy Settings");
 
         if let Some(provider) = &self.inline_completion_provider {
             let data_collection = provider.data_collection_state(cx);
-
             if data_collection.is_supported() {
                 let provider = provider.clone();
                 let enabled = data_collection.is_enabled();
 
-                menu = menu
-                    .separator()
-                    .header("Help Improve The Model")
-                    .header("Valid Only For OSS Projects");
                 menu = menu.item(
                     // TODO: We want to add something later that communicates whether
                     // the current project is open-source.
                     ContextMenuEntry::new("Share Training Data")
-                        .toggleable(IconPosition::Start, enabled)
+                        .toggleable(IconPosition::End, data_collection.is_enabled())
+                        .documentation_aside(|_| {
+                            Label::new("Zed automatically detects if your project is open-source. This setting is only applicable in such cases.").into_any_element()
+                        })
                         .handler(move |_, cx| {
                             provider.toggle_data_collection(cx);
 
@@ -455,11 +445,42 @@ impl InlineCompletionButton {
                                     source = "Edit Prediction Status Menu"
                                 );
                             }
-                        }),
-                );
+                        })
+                )
             }
         }
 
+        menu = menu.item(
+            ContextMenuEntry::new("Exclude Files")
+                .documentation_aside(|_| {
+                    Label::new("This item takes you to the settings where you can specify files that will never be captured by any edit prediction model. You can list both specific file extensions and individual file names.").into_any_element()
+                })
+                .handler(move |window, cx| {
+                    if let Some(workspace) = window.root().flatten() {
+                        let workspace = workspace.downgrade();
+                        window
+                            .spawn(cx, |cx| {
+                                open_disabled_globs_setting_in_editor(
+                                    workspace,
+                                    cx,
+                                )
+                            })
+                            .detach_and_log_err(cx);
+                    }
+                }),
+        );
+
+        if self.file.as_ref().map_or(false, |file| {
+            !all_language_settings(Some(file), cx).inline_completions_enabled_for_path(file.path())
+        }) {
+            menu = menu.item(
+                ContextMenuEntry::new("This file is excluded.")
+                    .disabled(true)
+                    .icon(IconName::ZedPredictDisabled)
+                    .icon_size(IconSize::Small),
+            );
+        }
+
         if let Some(editor_focus_handle) = self.editor_focus_handle.clone() {
             menu = menu
                 .separator()
@@ -546,12 +567,11 @@ impl InlineCompletionButton {
         self.editor_enabled = {
             let file = file.as_ref();
             Some(
-                file.map(|file| !file.is_private()).unwrap_or(true)
-                    && all_language_settings(file, cx).inline_completions_enabled(
-                        language,
-                        file.map(|file| file.path().as_ref()),
-                        cx,
-                    ),
+                file.map(|file| {
+                    all_language_settings(Some(file), cx)
+                        .inline_completions_enabled_for_path(file.path())
+                })
+                .unwrap_or(true),
             )
         };
         self.inline_completion_provider = editor.inline_completion_provider();
@@ -616,9 +636,8 @@ impl SupermavenButtonStatus {
     }
 }
 
-async fn configure_disabled_globs(
+async fn open_disabled_globs_setting_in_editor(
     workspace: WeakEntity<Workspace>,
-    path_to_disable: Option<Arc<Path>>,
     mut cx: AsyncWindowContext,
 ) -> Result<()> {
     let settings_editor = workspace
@@ -637,34 +656,34 @@ async fn configure_disabled_globs(
             let text = item.buffer().read(cx).snapshot(cx).text();
 
             let settings = cx.global::<SettingsStore>();
-            let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
-                let copilot = file.inline_completions.get_or_insert_with(Default::default);
-                let globs = copilot.disabled_globs.get_or_insert_with(|| {
-                    settings
-                        .get::<AllLanguageSettings>(None)
-                        .inline_completions
-                        .disabled_globs
-                        .iter()
-                        .map(|glob| glob.glob().to_string())
-                        .collect()
-                });
 
-                if let Some(path_to_disable) = &path_to_disable {
-                    globs.push(path_to_disable.to_string_lossy().into_owned());
-                } else {
-                    globs.clear();
-                }
+            // Ensure that we always have "inline_completions { "disabled_globs": [] }"
+            let edits = settings.edits_for_update::<AllLanguageSettings>(&text, |file| {
+                file.inline_completions
+                    .get_or_insert_with(Default::default)
+                    .disabled_globs
+                    .get_or_insert_with(Vec::new);
             });
 
             if !edits.is_empty() {
+                item.edit(edits.iter().cloned(), cx);
+            }
+
+            let text = item.buffer().read(cx).snapshot(cx).text();
+
+            static DISABLED_GLOBS_REGEX: LazyLock<Regex> = LazyLock::new(|| {
+                Regex::new(r#""disabled_globs":\s*\[\s*(?P<content>(?:.|\n)*?)\s*\]"#).unwrap()
+            });
+            // Only capture [...]
+            let range = DISABLED_GLOBS_REGEX.captures(&text).and_then(|captures| {
+                captures
+                    .name("content")
+                    .map(|inner_match| inner_match.start()..inner_match.end())
+            });
+            if let Some(range) = range {
                 item.change_selections(Some(Autoscroll::newest()), window, cx, |selections| {
-                    selections.select_ranges(edits.iter().map(|e| e.0.clone()));
+                    selections.select_ranges(vec![range]);
                 });
-
-                // When *enabling* a path, don't actually perform an edit, just select the range.
-                if path_to_disable.is_some() {
-                    item.edit(edits.iter().cloned(), cx);
-                }
             }
         })?;
 
@@ -672,8 +691,7 @@ async fn configure_disabled_globs(
 }
 
 fn toggle_inline_completions_globally(fs: Arc<dyn Fs>, cx: &mut App) {
-    let show_inline_completions =
-        all_language_settings(None, cx).inline_completions_enabled(None, None, cx);
+    let show_inline_completions = all_language_settings(None, cx).show_inline_completions(None, cx);
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
         file.defaults.show_inline_completions = Some(!show_inline_completions)
     });
@@ -687,9 +705,13 @@ fn set_completion_provider(fs: Arc<dyn Fs>, cx: &mut App, provider: InlineComple
     });
 }
 
-fn toggle_inline_completions_for_language(language: Arc<Language>, fs: Arc<dyn Fs>, cx: &mut App) {
+fn toggle_show_inline_completions_for_language(
+    language: Arc<Language>,
+    fs: Arc<dyn Fs>,
+    cx: &mut App,
+) {
     let show_inline_completions =
-        all_language_settings(None, cx).inline_completions_enabled(Some(&language), None, cx);
+        all_language_settings(None, cx).show_inline_completions(Some(&language), cx);
     update_settings_file::<AllLanguageSettings>(fs, cx, move |file, _| {
         file.languages
             .entry(language.name())

crates/language/src/language_settings.rs 🔗

@@ -886,18 +886,7 @@ impl AllLanguageSettings {
     }
 
     /// Returns whether edit predictions are enabled for the given language and path.
-    pub fn inline_completions_enabled(
-        &self,
-        language: Option<&Arc<Language>>,
-        path: Option<&Path>,
-        cx: &App,
-    ) -> bool {
-        if let Some(path) = path {
-            if !self.inline_completions_enabled_for_path(path) {
-                return false;
-            }
-        }
-
+    pub fn show_inline_completions(&self, language: Option<&Arc<Language>>, cx: &App) -> bool {
         self.language(None, language.map(|l| l.name()).as_ref(), cx)
             .show_inline_completions
     }

crates/supermaven/src/supermaven_completion_provider.rs 🔗

@@ -3,7 +3,7 @@ use anyhow::Result;
 use futures::StreamExt as _;
 use gpui::{App, Context, Entity, EntityId, Task};
 use inline_completion::{Direction, InlineCompletion, InlineCompletionProvider};
-use language::{language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot};
+use language::{Anchor, Buffer, BufferSnapshot};
 use std::{
     ops::{AddAssign, Range},
     path::Path,
@@ -113,16 +113,8 @@ impl InlineCompletionProvider for SupermavenCompletionProvider {
         false
     }
 
-    fn is_enabled(&self, buffer: &Entity<Buffer>, cursor_position: Anchor, cx: &App) -> bool {
-        if !self.supermaven.read(cx).is_enabled() {
-            return false;
-        }
-
-        let buffer = buffer.read(cx);
-        let file = buffer.file();
-        let language = buffer.language_at(cursor_position);
-        let settings = all_language_settings(file, cx);
-        settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
+    fn is_enabled(&self, _buffer: &Entity<Buffer>, _cursor_position: Anchor, cx: &App) -> bool {
+        self.supermaven.read(cx).is_enabled()
     }
 
     fn is_refreshing(&self) -> bool {

crates/ui/src/components/context_menu.rs 🔗

@@ -47,6 +47,7 @@ pub struct ContextMenuEntry {
     handler: Rc<dyn Fn(Option<&FocusHandle>, &mut Window, &mut App)>,
     action: Option<Box<dyn Action>>,
     disabled: bool,
+    documentation_aside: Option<Rc<dyn Fn(&mut App) -> AnyElement>>,
 }
 
 impl ContextMenuEntry {
@@ -61,6 +62,7 @@ impl ContextMenuEntry {
             handler: Rc::new(|_, _, _| {}),
             action: None,
             disabled: false,
+            documentation_aside: None,
         }
     }
 
@@ -108,6 +110,14 @@ impl ContextMenuEntry {
         self.disabled = disabled;
         self
     }
+
+    pub fn documentation_aside(
+        mut self,
+        element: impl Fn(&mut App) -> AnyElement + 'static,
+    ) -> Self {
+        self.documentation_aside = Some(Rc::new(element));
+        self
+    }
 }
 
 impl From<ContextMenuEntry> for ContextMenuItem {
@@ -125,6 +135,7 @@ pub struct ContextMenu {
     clicked: bool,
     _on_blur_subscription: Subscription,
     keep_open_on_confirm: bool,
+    documentation_aside: Option<(usize, Rc<dyn Fn(&mut App) -> AnyElement>)>,
 }
 
 impl Focusable for ContextMenu {
@@ -161,6 +172,7 @@ impl ContextMenu {
                     clicked: false,
                     _on_blur_subscription,
                     keep_open_on_confirm: false,
+                    documentation_aside: None,
                 },
                 window,
                 cx,
@@ -209,6 +221,7 @@ impl ContextMenu {
             icon_color: None,
             action,
             disabled: false,
+            documentation_aside: None,
         }));
         self
     }
@@ -231,6 +244,7 @@ impl ContextMenu {
             icon_color: None,
             action,
             disabled: false,
+            documentation_aside: None,
         }));
         self
     }
@@ -281,6 +295,7 @@ impl ContextMenu {
             icon_size: IconSize::Small,
             icon_color: None,
             disabled: false,
+            documentation_aside: None,
         }));
         self
     }
@@ -294,7 +309,6 @@ impl ContextMenu {
             toggle: None,
             label: label.into(),
             action: Some(action.boxed_clone()),
-
             handler: Rc::new(move |context, window, cx| {
                 if let Some(context) = &context {
                     window.focus(context);
@@ -306,6 +320,7 @@ impl ContextMenu {
             icon_position: IconPosition::End,
             icon_color: None,
             disabled: true,
+            documentation_aside: None,
         }));
         self
     }
@@ -314,7 +329,6 @@ impl ContextMenu {
         self.items.push(ContextMenuItem::Entry(ContextMenuEntry {
             toggle: None,
             label: label.into(),
-
             action: Some(action.boxed_clone()),
             handler: Rc::new(move |_, window, cx| window.dispatch_action(action.boxed_clone(), cx)),
             icon: Some(IconName::ArrowUpRight),
@@ -322,6 +336,7 @@ impl ContextMenu {
             icon_position: IconPosition::End,
             icon_color: None,
             disabled: false,
+            documentation_aside: None,
         }));
         self
     }
@@ -356,15 +371,16 @@ impl ContextMenu {
     }
 
     fn select_first(&mut self, _: &SelectFirst, _: &mut Window, cx: &mut Context<Self>) {
-        self.selected_index = self.items.iter().position(|item| item.is_selectable());
+        if let Some(ix) = self.items.iter().position(|item| item.is_selectable()) {
+            self.select_index(ix);
+        }
         cx.notify();
     }
 
     pub fn select_last(&mut self) -> Option<usize> {
         for (ix, item) in self.items.iter().enumerate().rev() {
             if item.is_selectable() {
-                self.selected_index = Some(ix);
-                return Some(ix);
+                return self.select_index(ix);
             }
         }
         None
@@ -384,7 +400,7 @@ impl ContextMenu {
             } else {
                 for (ix, item) in self.items.iter().enumerate().skip(next_index) {
                     if item.is_selectable() {
-                        self.selected_index = Some(ix);
+                        self.select_index(ix);
                         cx.notify();
                         break;
                     }
@@ -402,7 +418,7 @@ impl ContextMenu {
             } else {
                 for (ix, item) in self.items.iter().enumerate().take(ix).rev() {
                     if item.is_selectable() {
-                        self.selected_index = Some(ix);
+                        self.select_index(ix);
                         cx.notify();
                         break;
                     }
@@ -413,6 +429,20 @@ impl ContextMenu {
         }
     }
 
+    fn select_index(&mut self, ix: usize) -> Option<usize> {
+        self.documentation_aside = None;
+        let item = self.items.get(ix)?;
+        if item.is_selectable() {
+            self.selected_index = Some(ix);
+            if let ContextMenuItem::Entry(entry) = item {
+                if let Some(callback) = &entry.documentation_aside {
+                    self.documentation_aside = Some((ix, callback.clone()));
+                }
+            }
+        }
+        Some(ix)
+    }
+
     pub fn on_action_dispatch(
         &mut self,
         dispatched: &dyn Action,
@@ -436,7 +466,7 @@ impl ContextMenu {
                 false
             }
         }) {
-            self.selected_index = Some(ix);
+            self.select_index(ix);
             self.delayed = true;
             cx.notify();
             let action = dispatched.boxed_clone();
@@ -479,198 +509,275 @@ impl Render for ContextMenu {
     fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
         let ui_font_size = ThemeSettings::get_global(cx).ui_font_size;
 
-        WithRemSize::new(ui_font_size)
-            .occlude()
-            .elevation_2(cx)
-            .flex()
-            .flex_row()
+        let aside = self
+            .documentation_aside
+            .as_ref()
+            .map(|(_, callback)| callback.clone());
+
+        h_flex()
+            .w_full()
+            .items_start()
+            .gap_1()
+            .when_some(aside, |this, aside| {
+                this.child(
+                    WithRemSize::new(ui_font_size)
+                        .occlude()
+                        .elevation_2(cx)
+                        .p_2()
+                        .max_w_80()
+                        .child(aside(cx)),
+                )
+            })
             .child(
-                v_flex()
-                    .id("context-menu")
-                    .min_w(px(200.))
-                    .max_h(vh(0.75, window))
-                    .flex_1()
-                    .overflow_y_scroll()
-                    .track_focus(&self.focus_handle(cx))
-                    .on_mouse_down_out(
-                        cx.listener(|this, _, window, cx| this.cancel(&menu::Cancel, window, cx)),
-                    )
-                    .key_context("menu")
-                    .on_action(cx.listener(ContextMenu::select_first))
-                    .on_action(cx.listener(ContextMenu::handle_select_last))
-                    .on_action(cx.listener(ContextMenu::select_next))
-                    .on_action(cx.listener(ContextMenu::select_prev))
-                    .on_action(cx.listener(ContextMenu::confirm))
-                    .on_action(cx.listener(ContextMenu::cancel))
-                    .when(!self.delayed, |mut el| {
-                        for item in self.items.iter() {
-                            if let ContextMenuItem::Entry(ContextMenuEntry {
-                                action: Some(action),
-                                disabled: false,
-                                ..
-                            }) = item
-                            {
-                                el = el.on_boxed_action(
-                                    &**action,
-                                    cx.listener(ContextMenu::on_action_dispatch),
-                                );
-                            }
-                        }
-                        el
-                    })
-                    .child(List::new().children(self.items.iter_mut().enumerate().map(
-                        |(ix, item)| {
-                            match item {
-                                ContextMenuItem::Separator => ListSeparator.into_any_element(),
-                                ContextMenuItem::Header(header) => {
-                                    ListSubHeader::new(header.clone())
-                                        .inset(true)
-                                        .into_any_element()
+                WithRemSize::new(ui_font_size)
+                    .occlude()
+                    .elevation_2(cx)
+                    .flex()
+                    .flex_row()
+                    .child(
+                        v_flex()
+                            .id("context-menu")
+                            .min_w(px(200.))
+                            .max_h(vh(0.75, window))
+                            .flex_1()
+                            .overflow_y_scroll()
+                            .track_focus(&self.focus_handle(cx))
+                            .on_mouse_down_out(cx.listener(|this, _, window, cx| {
+                                this.cancel(&menu::Cancel, window, cx)
+                            }))
+                            .key_context("menu")
+                            .on_action(cx.listener(ContextMenu::select_first))
+                            .on_action(cx.listener(ContextMenu::handle_select_last))
+                            .on_action(cx.listener(ContextMenu::select_next))
+                            .on_action(cx.listener(ContextMenu::select_prev))
+                            .on_action(cx.listener(ContextMenu::confirm))
+                            .on_action(cx.listener(ContextMenu::cancel))
+                            .when(!self.delayed, |mut el| {
+                                for item in self.items.iter() {
+                                    if let ContextMenuItem::Entry(ContextMenuEntry {
+                                        action: Some(action),
+                                        disabled: false,
+                                        ..
+                                    }) = item
+                                    {
+                                        el = el.on_boxed_action(
+                                            &**action,
+                                            cx.listener(ContextMenu::on_action_dispatch),
+                                        );
+                                    }
                                 }
-                                ContextMenuItem::Label(label) => ListItem::new(ix)
-                                    .inset(true)
-                                    .disabled(true)
-                                    .child(Label::new(label.clone()))
-                                    .into_any_element(),
-                                ContextMenuItem::Entry(ContextMenuEntry {
-                                    toggle,
-                                    label,
-                                    handler,
-                                    icon,
-                                    icon_position,
-                                    icon_size,
-                                    icon_color,
-                                    action,
-                                    disabled,
-                                }) => {
-                                    let handler = handler.clone();
-                                    let menu = cx.entity().downgrade();
-                                    let icon_color = if *disabled {
-                                        Color::Muted
-                                    } else {
-                                        icon_color.unwrap_or(Color::Default)
-                                    };
-                                    let label_color = if *disabled {
-                                        Color::Muted
-                                    } else {
-                                        Color::Default
-                                    };
-                                    let label_element = if let Some(icon_name) = icon {
-                                        h_flex()
-                                            .gap_1p5()
-                                            .when(*icon_position == IconPosition::Start, |flex| {
-                                                flex.child(
-                                                    Icon::new(*icon_name)
-                                                        .size(*icon_size)
-                                                        .color(icon_color),
-                                                )
-                                            })
-                                            .child(Label::new(label.clone()).color(label_color))
-                                            .when(*icon_position == IconPosition::End, |flex| {
-                                                flex.child(
-                                                    Icon::new(*icon_name)
-                                                        .size(*icon_size)
-                                                        .color(icon_color),
-                                                )
-                                            })
-                                            .into_any_element()
-                                    } else {
-                                        Label::new(label.clone())
-                                            .color(label_color)
-                                            .into_any_element()
-                                    };
-
-                                    ListItem::new(ix)
-                                        .inset(true)
-                                        .disabled(*disabled)
-                                        .toggle_state(Some(ix) == self.selected_index)
-                                        .when_some(*toggle, |list_item, (position, toggled)| {
-                                            let contents = if toggled {
-                                                v_flex().flex_none().child(
-                                                    Icon::new(IconName::Check).color(Color::Accent),
-                                                )
+                                el
+                            })
+                            .child(List::new().children(self.items.iter_mut().enumerate().map(
+                                |(ix, item)| {
+                                    match item {
+                                        ContextMenuItem::Separator => {
+                                            ListSeparator.into_any_element()
+                                        }
+                                        ContextMenuItem::Header(header) => {
+                                            ListSubHeader::new(header.clone())
+                                                .inset(true)
+                                                .into_any_element()
+                                        }
+                                        ContextMenuItem::Label(label) => ListItem::new(ix)
+                                            .inset(true)
+                                            .disabled(true)
+                                            .child(Label::new(label.clone()))
+                                            .into_any_element(),
+                                        ContextMenuItem::Entry(ContextMenuEntry {
+                                            toggle,
+                                            label,
+                                            handler,
+                                            icon,
+                                            icon_position,
+                                            icon_size,
+                                            icon_color,
+                                            action,
+                                            disabled,
+                                            documentation_aside,
+                                        }) => {
+                                            let handler = handler.clone();
+                                            let menu = cx.entity().downgrade();
+                                            let icon_color = if *disabled {
+                                                Color::Muted
+                                            } else {
+                                                icon_color.unwrap_or(Color::Default)
+                                            };
+                                            let label_color = if *disabled {
+                                                Color::Muted
                                             } else {
-                                                v_flex()
-                                                    .flex_none()
-                                                    .size(IconSize::default().rems())
+                                                Color::Default
                                             };
-                                            match position {
-                                                IconPosition::Start => {
-                                                    list_item.start_slot(contents)
-                                                }
-                                                IconPosition::End => list_item.end_slot(contents),
-                                            }
-                                        })
-                                        .child(
-                                            h_flex()
-                                                .w_full()
-                                                .justify_between()
-                                                .child(label_element)
-                                                .debug_selector(|| format!("MENU_ITEM-{}", label))
-                                                .children(action.as_ref().and_then(|action| {
-                                                    self.action_context
-                                                        .as_ref()
-                                                        .map(|focus| {
-                                                            KeyBinding::for_action_in(
-                                                                &**action, focus, window,
+                                            let label_element = if let Some(icon_name) = icon {
+                                                h_flex()
+                                                    .gap_1p5()
+                                                    .when(
+                                                        *icon_position == IconPosition::Start,
+                                                        |flex| {
+                                                            flex.child(
+                                                                Icon::new(*icon_name)
+                                                                    .size(*icon_size)
+                                                                    .color(icon_color),
                                                             )
-                                                        })
-                                                        .unwrap_or_else(|| {
-                                                            KeyBinding::for_action(
-                                                                &**action, window,
+                                                        },
+                                                    )
+                                                    .child(
+                                                        Label::new(label.clone())
+                                                            .color(label_color),
+                                                    )
+                                                    .when(
+                                                        *icon_position == IconPosition::End,
+                                                        |flex| {
+                                                            flex.child(
+                                                                Icon::new(*icon_name)
+                                                                    .size(*icon_size)
+                                                                    .color(icon_color),
                                                             )
-                                                        })
-                                                        .map(|binding| div().ml_4().child(binding))
-                                                })),
-                                        )
-                                        .on_click({
-                                            let context = self.action_context.clone();
-                                            move |_, window, cx| {
-                                                handler(context.as_ref(), window, cx);
-                                                menu.update(cx, |menu, cx| {
-                                                    menu.clicked = true;
-                                                    cx.emit(DismissEvent);
+                                                        },
+                                                    )
+                                                    .into_any_element()
+                                            } else {
+                                                Label::new(label.clone())
+                                                    .color(label_color)
+                                                    .into_any_element()
+                                            };
+                                            let documentation_aside_callback =
+                                                documentation_aside.clone();
+                                            div()
+                                                .id(("context-menu-child", ix))
+                                                .when_some(
+                                                    documentation_aside_callback,
+                                                    |this, documentation_aside_callback| {
+                                                        this.occlude().on_hover(cx.listener(
+                                                            move |menu, hovered, _, cx| {
+                                                                if *hovered {
+                                                                    menu.documentation_aside = Some((ix, documentation_aside_callback.clone()));
+                                                                    cx.notify();
+                                                                } else if matches!(menu.documentation_aside, Some((id, _)) if id == ix) {
+                                                                    menu.documentation_aside = None;
+                                                                    cx.notify();
+                                                                }
+                                                            },
+                                                        ))
+                                                    },
+                                                )
+                                                .child(
+                                                    ListItem::new(ix)
+                                                        .inset(true)
+                                                        .disabled(*disabled)
+                                                        .toggle_state(
+                                                            Some(ix) == self.selected_index,
+                                                        )
+                                                        .when_some(
+                                                            *toggle,
+                                                            |list_item, (position, toggled)| {
+                                                                let contents = if toggled {
+                                                                    v_flex().flex_none().child(
+                                                                        Icon::new(IconName::Check)
+                                                                            .color(Color::Accent),
+                                                                    )
+                                                                } else {
+                                                                    v_flex().flex_none().size(
+                                                                        IconSize::default().rems(),
+                                                                    )
+                                                                };
+                                                                match position {
+                                                                    IconPosition::Start => {
+                                                                        list_item
+                                                                            .start_slot(contents)
+                                                                    }
+                                                                    IconPosition::End => {
+                                                                        list_item.end_slot(contents)
+                                                                    }
+                                                                }
+                                                            },
+                                                        )
+                                                        .child(
+                                                            h_flex()
+                                                                .w_full()
+                                                                .justify_between()
+                                                                .child(label_element)
+                                                                .debug_selector(|| {
+                                                                    format!("MENU_ITEM-{}", label)
+                                                                })
+                                                                .children(
+                                                                    action.as_ref().and_then(
+                                                                        |action| {
+                                                                            self.action_context
+                                                                    .as_ref()
+                                                                    .map(|focus| {
+                                                                        KeyBinding::for_action_in(
+                                                                            &**action, focus,
+                                                                            window,
+                                                                        )
+                                                                    })
+                                                                    .unwrap_or_else(|| {
+                                                                        KeyBinding::for_action(
+                                                                            &**action, window,
+                                                                        )
+                                                                    })
+                                                                    .map(|binding| {
+                                                                        div().ml_4().child(binding)
+                                                                    })
+                                                                        },
+                                                                    ),
+                                                                ),
+                                                        )
+                                                        .on_click({
+                                                            let context =
+                                                                self.action_context.clone();
+                                                            move |_, window, cx| {
+                                                                handler(
+                                                                    context.as_ref(),
+                                                                    window,
+                                                                    cx,
+                                                                );
+                                                                menu.update(cx, |menu, cx| {
+                                                                    menu.clicked = true;
+                                                                    cx.emit(DismissEvent);
+                                                                })
+                                                                .ok();
+                                                            }
+                                                        }),
+                                                )
+                                                .into_any_element()
+                                        }
+                                        ContextMenuItem::CustomEntry {
+                                            entry_render,
+                                            handler,
+                                            selectable,
+                                        } => {
+                                            let handler = handler.clone();
+                                            let menu = cx.entity().downgrade();
+                                            let selectable = *selectable;
+                                            ListItem::new(ix)
+                                                .inset(true)
+                                                .toggle_state(if selectable {
+                                                    Some(ix) == self.selected_index
+                                                } else {
+                                                    false
                                                 })
-                                                .ok();
-                                            }
-                                        })
-                                        .into_any_element()
-                                }
-                                ContextMenuItem::CustomEntry {
-                                    entry_render,
-                                    handler,
-                                    selectable,
-                                } => {
-                                    let handler = handler.clone();
-                                    let menu = cx.entity().downgrade();
-                                    let selectable = *selectable;
-                                    ListItem::new(ix)
-                                        .inset(true)
-                                        .toggle_state(if selectable {
-                                            Some(ix) == self.selected_index
-                                        } else {
-                                            false
-                                        })
-                                        .selectable(selectable)
-                                        .when(selectable, |item| {
-                                            item.on_click({
-                                                let context = self.action_context.clone();
-                                                move |_, window, cx| {
-                                                    handler(context.as_ref(), window, cx);
-                                                    menu.update(cx, |menu, cx| {
-                                                        menu.clicked = true;
-                                                        cx.emit(DismissEvent);
+                                                .selectable(selectable)
+                                                .when(selectable, |item| {
+                                                    item.on_click({
+                                                        let context = self.action_context.clone();
+                                                        move |_, window, cx| {
+                                                            handler(context.as_ref(), window, cx);
+                                                            menu.update(cx, |menu, cx| {
+                                                                menu.clicked = true;
+                                                                cx.emit(DismissEvent);
+                                                            })
+                                                            .ok();
+                                                        }
                                                     })
-                                                    .ok();
-                                                }
-                                            })
-                                        })
-                                        .child(entry_render(window, cx))
-                                        .into_any_element()
-                                }
-                            }
-                        },
-                    ))),
+                                                })
+                                                .child(entry_render(window, cx))
+                                                .into_any_element()
+                                        }
+                                    }
+                                },
+                            ))),
+                    ),
             )
     }
 }

crates/vim/src/vim.rs 🔗

@@ -1289,7 +1289,7 @@ impl Vim {
                     .map_or(false, |provider| provider.show_completions_in_normal_mode()),
                 _ => false,
             };
-            editor.set_inline_completions_enabled(enable_inline_completions, cx);
+            editor.set_show_inline_completions_enabled(enable_inline_completions, cx);
         });
         cx.notify()
     }

crates/zed/src/zed/quick_action_bar.rs 🔗

@@ -16,8 +16,8 @@ use gpui::{
 use search::{buffer_search, BufferSearchBar};
 use settings::{Settings, SettingsStore};
 use ui::{
-    prelude::*, ButtonStyle, ContextMenu, IconButton, IconButtonShape, IconName, IconSize,
-    PopoverMenu, PopoverMenuHandle, Tooltip,
+    prelude::*, ButtonStyle, ContextMenu, ContextMenuEntry, IconButton, IconButtonShape, IconName,
+    IconSize, PopoverMenu, PopoverMenuHandle, Tooltip,
 };
 use vim_mode_setting::VimModeSetting;
 use workspace::{
@@ -94,7 +94,8 @@ impl Render for QuickActionBar {
             git_blame_inline_enabled,
             show_git_blame_gutter,
             auto_signature_help_enabled,
-            inline_completions_enabled,
+            show_inline_completions,
+            inline_completion_enabled,
         ) = {
             let editor = editor.read(cx);
             let selection_menu_enabled = editor.selection_menu_enabled(cx);
@@ -103,7 +104,8 @@ impl Render for QuickActionBar {
             let git_blame_inline_enabled = editor.git_blame_inline_enabled();
             let show_git_blame_gutter = editor.show_git_blame_gutter();
             let auto_signature_help_enabled = editor.auto_signature_help_enabled(cx);
-            let inline_completions_enabled = editor.inline_completions_enabled(cx);
+            let show_inline_completions = editor.should_show_inline_completions(cx);
+            let inline_completion_enabled = editor.inline_completions_enabled(cx);
 
             (
                 selection_menu_enabled,
@@ -112,7 +114,8 @@ impl Render for QuickActionBar {
                 git_blame_inline_enabled,
                 show_git_blame_gutter,
                 auto_signature_help_enabled,
-                inline_completions_enabled,
+                show_inline_completions,
+                inline_completion_enabled,
             )
         };
 
@@ -294,12 +297,12 @@ impl Render for QuickActionBar {
                             },
                         );
 
-                        menu = menu.toggleable_entry(
-                            "Edit Predictions",
-                            inline_completions_enabled,
-                            IconPosition::Start,
-                            Some(editor::actions::ToggleInlineCompletions.boxed_clone()),
-                            {
+                        let mut inline_completion_entry = ContextMenuEntry::new("Edit Predictions")
+                            .toggleable(IconPosition::Start, inline_completion_enabled && show_inline_completions)
+                            .disabled(!inline_completion_enabled)
+                            .action(Some(
+                                editor::actions::ToggleInlineCompletions.boxed_clone(),
+                            )).handler({
                                 let editor = editor.clone();
                                 move |window, cx| {
                                     editor
@@ -312,8 +315,14 @@ impl Render for QuickActionBar {
                                         })
                                         .ok();
                                 }
-                            },
-                        );
+                            });
+                        if !inline_completion_enabled {
+                            inline_completion_entry = inline_completion_entry.documentation_aside(|_| {
+                                Label::new("You can't toggle edit predictions for this file as it is within the excluded files list.").into_any_element()
+                            });
+                        }
+
+                        menu = menu.item(inline_completion_entry);
 
                         menu = menu.separator();
 

crates/zeta/src/zeta.rs 🔗

@@ -25,8 +25,7 @@ use gpui::{
 };
 use http_client::{HttpClient, Method};
 use language::{
-    language_settings::all_language_settings, Anchor, Buffer, BufferSnapshot, EditPreview,
-    OffsetRangeExt, Point, ToOffset, ToPoint,
+    Anchor, Buffer, BufferSnapshot, EditPreview, OffsetRangeExt, Point, ToOffset, ToPoint,
 };
 use language_models::LlmApiToken;
 use postage::watch;
@@ -1469,15 +1468,11 @@ impl inline_completion::InlineCompletionProvider for ZetaInlineCompletionProvide
 
     fn is_enabled(
         &self,
-        buffer: &Entity<Buffer>,
-        cursor_position: language::Anchor,
-        cx: &App,
+        _buffer: &Entity<Buffer>,
+        _cursor_position: language::Anchor,
+        _cx: &App,
     ) -> bool {
-        let buffer = buffer.read(cx);
-        let file = buffer.file();
-        let language = buffer.language_at(cursor_position);
-        let settings = all_language_settings(file, cx);
-        settings.inline_completions_enabled(language.as_ref(), file.map(|f| f.path().as_ref()), cx)
+        true
     }
 
     fn needs_terms_acceptance(&self, cx: &App) -> bool {