diff --git a/assets/settings/default.json b/assets/settings/default.json index 732ee3e0ab..f67f2fa965 100644 --- a/assets/settings/default.json +++ b/assets/settings/default.json @@ -783,7 +783,14 @@ "**/*.cert", "**/*.crt", "**/secrets.yml" - ] + ], + // When to show edit predictions previews in buffer. + // This setting takes two possible values: + // 1. Display inline when there are no language server completions available. + // "inline_preview": "auto" + // 2. Display inline when holding modifier key (alt by default). + // "inline_preview": "when_holding_modifier" + "inline_preview": "auto" }, // Settings specific to journaling "journal": { diff --git a/crates/assistant_context_editor/src/context_editor.rs b/crates/assistant_context_editor/src/context_editor.rs index 86febdecc4..d1b9fbbda7 100644 --- a/crates/assistant_context_editor/src/context_editor.rs +++ b/crates/assistant_context_editor/src/context_editor.rs @@ -459,7 +459,7 @@ impl ContextEditor { window: &mut Window, cx: &mut Context, ) { - if self.editor.read(cx).has_active_completions_menu() { + if self.editor.read(cx).has_visible_completions_menu() { return; } diff --git a/crates/editor/src/code_context_menus.rs b/crates/editor/src/code_context_menus.rs index a6f0e5c0f0..f2752fb532 100644 --- a/crates/editor/src/code_context_menus.rs +++ b/crates/editor/src/code_context_menus.rs @@ -169,7 +169,6 @@ pub struct CompletionsMenu { resolve_completions: bool, show_completion_documentation: bool, last_rendered_range: Rc>>>, - pub previewing_inline_completion: bool, } impl CompletionsMenu { @@ -200,7 +199,6 @@ impl CompletionsMenu { scroll_handle: UniformListScrollHandle::new(), resolve_completions: true, last_rendered_range: RefCell::new(None).into(), - previewing_inline_completion: false, } } @@ -257,7 +255,6 @@ impl CompletionsMenu { resolve_completions: false, show_completion_documentation: false, last_rendered_range: RefCell::new(None).into(), - previewing_inline_completion: false, } } @@ -410,12 +407,8 @@ impl CompletionsMenu { .detach(); } - pub fn is_empty(&self) -> bool { - self.entries.borrow().is_empty() - } - pub fn visible(&self) -> bool { - !self.is_empty() && !self.previewing_inline_completion + !self.entries.borrow().is_empty() } fn origin(&self) -> ContextMenuOrigin { @@ -709,10 +702,6 @@ impl CompletionsMenu { // This keeps the display consistent when y_flipped. self.scroll_handle.scroll_to_item(0, ScrollStrategy::Top); } - - pub fn set_previewing_inline_completion(&mut self, value: bool) { - self.previewing_inline_completion = value; - } } #[derive(Clone)] diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 170371164f..9fa54c5f06 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -97,8 +97,8 @@ use language::{ language_settings::{self, all_language_settings, language_settings, InlayHintSettings}, markdown, point_from_lsp, AutoindentMode, BracketPair, Buffer, Capability, CharKind, CodeLabel, CompletionDocumentation, CursorShape, Diagnostic, EditPreview, HighlightedText, IndentKind, - IndentSize, Language, OffsetRangeExt, Point, Selection, SelectionGoal, TextObject, - TransactionId, TreeSitterOptions, + IndentSize, InlineCompletionPreviewMode, Language, OffsetRangeExt, Point, Selection, + SelectionGoal, TextObject, TransactionId, TreeSitterOptions, }; use language::{point_to_lsp, BufferRow, CharClassifier, Runnable, RunnableRange}; use linked_editing_ranges::refresh_linked_ranges; @@ -693,6 +693,7 @@ pub struct Editor { show_inline_completions: bool, show_inline_completions_override: Option, menu_inline_completions_policy: MenuInlineCompletionsPolicy, + previewing_inline_completion: bool, inlay_hint_cache: InlayHintCache, next_inlay_id: usize, _subscriptions: Vec, @@ -1384,6 +1385,7 @@ impl Editor { inline_completion_provider: None, active_inline_completion: None, stale_inline_completion_in_menu: None, + previewing_inline_completion: false, inlay_hint_cache: InlayHintCache::new(inlay_hint_settings), gutter_hovered: false, @@ -4662,6 +4664,18 @@ impl Editor { } } + fn inline_completion_preview_mode(&self, cx: &App) -> language::InlineCompletionPreviewMode { + let cursor = self.selections.newest_anchor().head(); + + self.buffer + .read(cx) + .text_anchor_for_position(cursor, cx) + .map(|(buffer, _)| { + all_language_settings(buffer.read(cx).file(), cx).inline_completions_preview_mode() + }) + .unwrap_or_default() + } + fn should_show_inline_completions_in_buffer( &self, buffer: &Entity, @@ -5009,11 +5023,28 @@ impl Editor { true } - pub fn is_previewing_inline_completion(&self) -> bool { - matches!( - self.context_menu.borrow().as_ref(), - Some(CodeContextMenu::Completions(menu)) if !menu.is_empty() && menu.previewing_inline_completion - ) + /// Returns true when we're displaying the inline completion popover below the cursor + /// like we are not previewing and the LSP autocomplete menu is visible + /// or we are in `when_holding_modifier` mode. + pub fn inline_completion_visible_in_cursor_popover( + &self, + has_completion: bool, + cx: &App, + ) -> bool { + if self.previewing_inline_completion + || !self.show_inline_completions_in_menu(cx) + || !self.should_show_inline_completions(cx) + { + return false; + } + + if self.has_visible_completions_menu() { + return true; + } + + has_completion + && self.inline_completion_preview_mode(cx) + == InlineCompletionPreviewMode::WhenHoldingModifier } fn update_inline_completion_preview( @@ -5022,13 +5053,13 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - // Moves jump directly with a preview step - + // Moves jump directly without a preview step if self .active_inline_completion .as_ref() .map_or(true, |c| c.is_move()) { + self.previewing_inline_completion = false; cx.notify(); return; } @@ -5037,20 +5068,7 @@ impl Editor { return; } - let mut menu_borrow = self.context_menu.borrow_mut(); - - let Some(CodeContextMenu::Completions(completions_menu)) = menu_borrow.as_mut() else { - return; - }; - - if completions_menu.is_empty() - || completions_menu.previewing_inline_completion == modifiers.alt - { - return; - } - - completions_menu.set_previewing_inline_completion(modifiers.alt); - drop(menu_borrow); + self.previewing_inline_completion = modifiers.alt; self.update_visible_inline_completion(window, cx); } @@ -5146,7 +5164,7 @@ impl Editor { snapshot, } } else { - if !show_in_menu || !self.has_active_completions_menu() { + if !self.inline_completion_visible_in_cursor_popover(true, cx) { if edits .iter() .all(|(range, _)| range.to_offset(&multibuffer).is_empty()) @@ -5180,7 +5198,7 @@ impl Editor { let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) { if provider.show_tab_accept_marker() { - EditDisplayMode::TabAccept(self.is_previewing_inline_completion()) + EditDisplayMode::TabAccept(self.previewing_inline_completion) } else { EditDisplayMode::Inline } @@ -5443,10 +5461,12 @@ impl Editor { } pub fn context_menu_visible(&self) -> bool { - self.context_menu - .borrow() - .as_ref() - .map_or(false, |menu| menu.visible()) + !self.previewing_inline_completion + && self + .context_menu + .borrow() + .as_ref() + .map_or(false, |menu| menu.visible()) } fn context_menu_origin(&self) -> Option { @@ -5848,9 +5868,7 @@ impl Editor { self.completion_tasks.clear(); let context_menu = self.context_menu.borrow_mut().take(); self.stale_inline_completion_in_menu.take(); - if context_menu.is_some() { - self.update_visible_inline_completion(window, cx); - } + self.update_visible_inline_completion(window, cx); context_menu } @@ -14438,10 +14456,11 @@ impl Editor { Some(gpui::Point::new(source_x, source_y)) } - pub fn has_active_completions_menu(&self) -> bool { - self.context_menu.borrow().as_ref().map_or(false, |menu| { - menu.visible() && matches!(menu, CodeContextMenu::Completions(_)) - }) + pub fn has_visible_completions_menu(&self) -> bool { + !self.previewing_inline_completion + && self.context_menu.borrow().as_ref().map_or(false, |menu| { + menu.visible() && matches!(menu, CodeContextMenu::Completions(_)) + }) } pub fn register_addon(&mut self, instance: T) { diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 32c2d4a5de..3fce0fa36c 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3109,7 +3109,10 @@ impl EditorElement { { let editor = self.editor.read(cx); - if editor.has_active_completions_menu() && editor.show_inline_completions_in_menu(cx) { + if editor.inline_completion_visible_in_cursor_popover( + editor.has_active_inline_completion(), + cx, + ) { height_above_menu += editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING; edit_prediction_popover_visible = true; @@ -3615,7 +3618,12 @@ impl EditorElement { const PADDING_X: Pixels = Pixels(24.); const PADDING_Y: Pixels = Pixels(2.); - let active_inline_completion = self.editor.read(cx).active_inline_completion.as_ref()?; + let editor = self.editor.read(cx); + let active_inline_completion = editor.active_inline_completion.as_ref()?; + + if editor.inline_completion_visible_in_cursor_popover(true, cx) { + return None; + } match &active_inline_completion.completion { InlineCompletion::Move { target, .. } => { @@ -3682,7 +3690,7 @@ impl EditorElement { display_mode, snapshot, } => { - if self.editor.read(cx).has_active_completions_menu() { + if self.editor.read(cx).has_visible_completions_menu() { return None; } diff --git a/crates/editor/src/signature_help.rs b/crates/editor/src/signature_help.rs index 7874df967c..f2e479f877 100644 --- a/crates/editor/src/signature_help.rs +++ b/crates/editor/src/signature_help.rs @@ -158,7 +158,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if self.pending_rename.is_some() || self.has_active_completions_menu() { + if self.pending_rename.is_some() || self.has_visible_completions_menu() { return; } diff --git a/crates/language/src/language.rs b/crates/language/src/language.rs index beef293558..f7aa838c7f 100644 --- a/crates/language/src/language.rs +++ b/crates/language/src/language.rs @@ -21,6 +21,7 @@ mod toolchain; pub mod buffer_tests; pub mod markdown; +pub use crate::language_settings::InlineCompletionPreviewMode; use crate::language_settings::SoftWrap; use anyhow::{anyhow, Context as _, Result}; use async_trait::async_trait; diff --git a/crates/language/src/language_settings.rs b/crates/language/src/language_settings.rs index ac57e566f4..3f5d5ef8a2 100644 --- a/crates/language/src/language_settings.rs +++ b/crates/language/src/language_settings.rs @@ -214,6 +214,19 @@ pub struct InlineCompletionSettings { pub provider: InlineCompletionProvider, /// A list of globs representing files that edit predictions should be disabled for. pub disabled_globs: Vec, + /// When to show edit predictions previews in buffer. + pub inline_preview: InlineCompletionPreviewMode, +} + +/// The mode in which edit predictions should be displayed. +#[derive(Copy, Clone, Debug, Default, Eq, PartialEq, Serialize, Deserialize, JsonSchema)] +#[serde(rename_all = "snake_case")] +pub enum InlineCompletionPreviewMode { + /// Display inline when there are no language server completions available. + #[default] + Auto, + /// Display inline when holding modifier key (alt by default). + WhenHoldingModifier, } /// The settings for all languages. @@ -406,6 +419,9 @@ pub struct InlineCompletionSettingsContent { /// A list of globs representing files that edit predictions should be disabled for. #[serde(default)] pub disabled_globs: Option>, + /// When to show edit predictions previews in buffer. + #[serde(default)] + pub inline_preview: InlineCompletionPreviewMode, } /// The settings for enabling/disabling features. @@ -890,6 +906,11 @@ impl AllLanguageSettings { self.language(None, language.map(|l| l.name()).as_ref(), cx) .show_inline_completions } + + /// Returns the edit predictions preview mode for the given language and path. + pub fn inline_completions_preview_mode(&self) -> InlineCompletionPreviewMode { + self.inline_completions.inline_preview + } } fn merge_with_editorconfig(settings: &mut LanguageSettings, cfg: &EditorconfigProperties) { @@ -987,6 +1008,12 @@ impl settings::Settings for AllLanguageSettings { .features .as_ref() .and_then(|f| f.inline_completion_provider); + let mut inline_completions_preview = default_value + .inline_completions + .as_ref() + .map(|inline_completions| inline_completions.inline_preview) + .ok_or_else(Self::missing_default)?; + let mut completion_globs: HashSet<&String> = default_value .inline_completions .as_ref() @@ -1017,12 +1044,13 @@ impl settings::Settings for AllLanguageSettings { { inline_completion_provider = Some(provider); } - if let Some(globs) = user_settings - .inline_completions - .as_ref() - .and_then(|f| f.disabled_globs.as_ref()) - { - completion_globs.extend(globs.iter()); + + if let Some(inline_completions) = user_settings.inline_completions.as_ref() { + inline_completions_preview = inline_completions.inline_preview; + + if let Some(disabled_globs) = inline_completions.disabled_globs.as_ref() { + completion_globs.extend(disabled_globs.iter()); + } } // A user's global settings override the default global settings and @@ -1075,6 +1103,7 @@ impl settings::Settings for AllLanguageSettings { .iter() .filter_map(|g| Some(globset::Glob::new(g).ok()?.compile_matcher())) .collect(), + inline_preview: inline_completions_preview, }, defaults, languages,