From 0af048a7cfa94969076eca68718ca041b9a0802c Mon Sep 17 00:00:00 2001 From: Agus Zubiaga Date: Mon, 10 Feb 2025 17:57:25 -0300 Subject: [PATCH] edit predictions: Cache settings across renders (#24581) We were reading edit prediction settings too often, causing frames to be dropped. We'll now cache them and update them from `update_visible_inline_completion`. Release Notes: - N/A --- crates/assistant/src/inline_assistant.rs | 2 +- crates/assistant2/src/inline_assistant.rs | 2 +- crates/editor/src/editor.rs | 195 ++++++++++-------- crates/editor/src/element.rs | 9 +- crates/feedback/src/feedback_modal.rs | 2 +- .../src/inline_completion_button.rs | 2 +- crates/language_tools/src/lsp_log.rs | 4 +- crates/prompt_library/src/prompt_library.rs | 4 +- crates/zed/src/zed/quick_action_bar.rs | 4 +- crates/zeta/src/rate_completion_modal.rs | 2 +- 10 files changed, 124 insertions(+), 102 deletions(-) diff --git a/crates/assistant/src/inline_assistant.rs b/crates/assistant/src/inline_assistant.rs index 286440f989..370d6d1119 100644 --- a/crates/assistant/src/inline_assistant.rs +++ b/crates/assistant/src/inline_assistant.rs @@ -1255,7 +1255,7 @@ impl InlineAssistant { editor.scroll_manager.set_forbid_vertical_scroll(true); editor.set_show_scrollbars(false, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.highlight_rows::( Anchor::min()..Anchor::max(), cx.theme().status().deleted_background, diff --git a/crates/assistant2/src/inline_assistant.rs b/crates/assistant2/src/inline_assistant.rs index a595341481..2158f3279f 100644 --- a/crates/assistant2/src/inline_assistant.rs +++ b/crates/assistant2/src/inline_assistant.rs @@ -1345,7 +1345,7 @@ impl InlineAssistant { editor.scroll_manager.set_forbid_vertical_scroll(true); editor.set_show_scrollbars(false, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.highlight_rows::( Anchor::min()..Anchor::max(), cx.theme().status().deleted_background, diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index 6d1ed15015..3c004aad00 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -497,6 +497,23 @@ struct InlineCompletionState { invalidation_range: Range, } +enum EditPredictionSettings { + Disabled, + Enabled { + show_in_menu: bool, + preview_requires_modifier: bool, + }, +} + +impl EditPredictionSettings { + pub fn is_enabled(&self) -> bool { + match self { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { .. } => true, + } + } +} + enum InlineCompletionHighlight {} pub enum MenuInlineCompletionsPolicy { @@ -683,6 +700,7 @@ pub struct Editor { active_inline_completion: Option, /// Used to prevent flickering as the user types while the menu is open stale_inline_completion_in_menu: Option, + edit_prediction_settings: EditPredictionSettings, inline_completions_hidden_for_vim_mode: bool, show_inline_completions_override: Option, menu_inline_completions_policy: MenuInlineCompletionsPolicy, @@ -1395,6 +1413,7 @@ impl Editor { inline_completions_hidden_for_vim_mode: false, show_inline_completions_override: None, menu_inline_completions_policy: MenuInlineCompletionsPolicy::ByProvider, + edit_prediction_settings: EditPredictionSettings::Disabled, custom_context_menu: None, show_git_blame_gutter: false, show_git_blame_inline: false, @@ -1530,7 +1549,7 @@ impl Editor { key_context.add("copilot_suggestion"); key_context.add("edit_prediction"); - if showing_completions || self.edit_prediction_requires_modifier(cx) { + if showing_completions || self.edit_prediction_requires_modifier() { key_context.add(EDIT_PREDICTION_REQUIRES_MODIFIER_KEY_CONTEXT); } } @@ -1886,23 +1905,14 @@ impl Editor { cx: &mut Context, ) { if self.show_inline_completions_override.is_some() { - self.set_show_inline_completions(None, window, cx); + self.set_show_edit_predictions(None, window, cx); } else { - let cursor = self.selections.newest_anchor().head(); - 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_in_buffer( - &buffer, - cursor_buffer_position, - cx, - ); - self.set_show_inline_completions(Some(show_inline_completions), window, cx); - } + let show_edit_predictions = !self.edit_predictions_enabled(); + self.set_show_edit_predictions(Some(show_edit_predictions), window, cx); } } - pub fn set_show_inline_completions( + pub fn set_show_edit_predictions( &mut self, show_edit_predictions: Option, window: &mut Window, @@ -3019,7 +3029,7 @@ impl Editor { } let trigger_in_words = - this.show_edit_predictions_in_menu(cx) || !had_active_inline_completion; + this.show_edit_predictions_in_menu() || !had_active_inline_completion; this.trigger_completion_on_input(&text, trigger_in_words, window, cx); linked_editing_ranges::refresh_linked_ranges(this, window, cx); this.refresh_inline_completion(true, false, window, cx); @@ -3912,7 +3922,7 @@ impl Editor { *editor.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(menu)); - if editor.show_edit_predictions_in_menu(cx) { + if editor.show_edit_predictions_in_menu() { editor.update_visible_inline_completion(window, cx); } else { editor.discard_inline_completion(false, cx); @@ -3926,7 +3936,7 @@ impl Editor { // If it was already hidden and we don't show inline // completions in the menu, we should also show the // inline-completion when available. - if was_hidden && editor.show_edit_predictions_in_menu(cx) { + if was_hidden && editor.show_edit_predictions_in_menu() { editor.update_visible_inline_completion(window, cx); } } @@ -3976,7 +3986,7 @@ impl Editor { let entries = completions_menu.entries.borrow(); let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?; - if self.show_edit_predictions_in_menu(cx) { + if self.show_edit_predictions_in_menu() { self.discard_inline_completion(true, cx); } let candidate_id = mat.candidate_id; @@ -4668,7 +4678,7 @@ impl Editor { } if !user_requested - && (!self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx) + && (!self.should_show_edit_predictions() || !self.is_focused(window) || buffer.read(cx).is_empty()) { @@ -4687,58 +4697,79 @@ 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 show_edit_predictions_in_menu(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu, } } - fn edit_prediction_requires_modifier(&self, cx: &App) -> bool { - 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() - == InlineCompletionPreviewMode::WhenHoldingModifier - }) - .unwrap_or(false) + pub fn edit_predictions_enabled(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { .. } => true, + } } - fn should_show_inline_completions_in_buffer( + fn edit_prediction_requires_modifier(&self) -> bool { + match self.edit_prediction_settings { + EditPredictionSettings::Disabled => false, + EditPredictionSettings::Enabled { + preview_requires_modifier, + .. + } => preview_requires_modifier, + } + } + + fn edit_prediction_settings_at_position( &self, buffer: &Entity, buffer_position: language::Anchor, cx: &App, - ) -> bool { - if !self.snippet_stack.is_empty() { - return false; + ) -> EditPredictionSettings { + if self.mode != EditorMode::Full + || !self.show_inline_completions_override.unwrap_or(true) + || self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) + { + return EditPredictionSettings::Disabled; } - if self.inline_completions_disabled_in_scope(buffer, buffer_position, cx) { - return false; - } + let buffer = buffer.read(cx); - 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_edit_predictions + let file = buffer.file(); + + if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions { + return EditPredictionSettings::Disabled; + }; + + let by_provider = matches!( + self.menu_inline_completions_policy, + MenuInlineCompletionsPolicy::ByProvider + ); + + let show_in_menu = by_provider + && EditorSettings::get_global(cx).show_edit_predictions_in_menu + && self + .edit_prediction_provider + .as_ref() + .map_or(false, |provider| { + provider.provider.show_completions_in_menu() + }); + + let preview_requires_modifier = all_language_settings(file, cx) + .inline_completions_preview_mode() + == InlineCompletionPreviewMode::WhenHoldingModifier; + + EditPredictionSettings::Enabled { + show_in_menu, + preview_requires_modifier, } } + fn should_show_edit_predictions(&self) -> bool { + self.snippet_stack.is_empty() && self.edit_predictions_enabled() + } + pub fn inline_completions_enabled(&self, cx: &App) -> bool { let cursor = self.selections.newest_anchor().head(); if let Some((buffer, cursor_position)) = @@ -4781,9 +4812,7 @@ 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.inline_completions_hidden_for_vim_mode - || !self.should_show_inline_completions_in_buffer(&buffer, cursor_buffer_position, cx) - { + if self.inline_completions_hidden_for_vim_mode || !self.should_show_edit_predictions() { return None; } @@ -4889,7 +4918,7 @@ impl Editor { } } - if self.show_edit_predictions_in_menu(cx) { + if self.show_edit_predictions_in_menu() { self.hide_context_menu(window, cx); } @@ -5076,14 +5105,10 @@ impl Editor { /// 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 { + pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool { if self.previewing_inline_completion - || !self.show_edit_predictions_in_menu(cx) - || !self.should_show_inline_completions(cx) + || !self.show_edit_predictions_in_menu() + || !self.edit_predictions_enabled() { return false; } @@ -5092,7 +5117,7 @@ impl Editor { return true; } - has_completion && self.edit_prediction_requires_modifier(cx) + has_completion && self.edit_prediction_requires_modifier() } fn handle_modifiers_changed( @@ -5102,7 +5127,7 @@ impl Editor { window: &mut Window, cx: &mut Context, ) { - if self.show_edit_predictions_in_menu(cx) { + if self.show_edit_predictions_in_menu() { let accept_binding = AcceptEditPredictionBinding::resolve(self.focus_handle(cx), window); if let Some(accept_keystroke) = accept_binding.keystroke() { @@ -5140,10 +5165,11 @@ impl Editor { let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer)); let excerpt_id = cursor.excerpt_id; - let show_in_menu = self.show_edit_predictions_in_menu(cx); + let show_in_menu = self.show_edit_predictions_in_menu(); let completions_menu_has_precedence = !show_in_menu && (self.context_menu.borrow().is_some() || (!self.completion_tasks.is_empty() && !self.has_active_inline_completion())); + if completions_menu_has_precedence || !offset_selection.is_empty() || self @@ -5160,11 +5186,22 @@ impl Editor { } self.take_active_inline_completion(cx); - let provider = self.edit_prediction_provider()?; + let Some(provider) = self.edit_prediction_provider() else { + self.edit_prediction_settings = EditPredictionSettings::Disabled; + return None; + }; let (buffer, cursor_buffer_position) = self.buffer.read(cx).text_anchor_for_position(cursor, cx)?; + self.edit_prediction_settings = + self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx); + + if !self.edit_prediction_settings.is_enabled() { + self.discard_inline_completion(false, cx); + return None; + } + let inline_completion = provider.suggest(&buffer, cursor_buffer_position, cx)?; let edits = inline_completion .edits @@ -5223,8 +5260,7 @@ impl Editor { snapshot, } } else { - let show_completions_in_buffer = !self - .inline_completion_visible_in_cursor_popover(true, cx) + let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true) && !self.inline_completions_hidden_for_vim_mode; if show_completions_in_buffer { if edits @@ -5300,19 +5336,6 @@ impl Editor { Some(self.edit_prediction_provider.as_ref()?.provider.clone()) } - fn show_edit_predictions_in_menu(&self, cx: &App) -> bool { - let by_provider = matches!( - self.menu_inline_completions_policy, - MenuInlineCompletionsPolicy::ByProvider - ); - - by_provider - && EditorSettings::get_global(cx).show_edit_predictions_in_menu - && self - .edit_prediction_provider() - .map_or(false, |provider| provider.show_completions_in_menu()) - } - fn render_code_actions_indicator( &self, _style: &EditorStyle, diff --git a/crates/editor/src/element.rs b/crates/editor/src/element.rs index 784c1d5178..6fe274572e 100644 --- a/crates/editor/src/element.rs +++ b/crates/editor/src/element.rs @@ -3088,10 +3088,9 @@ impl EditorElement { { let editor = self.editor.read(cx); - if editor.inline_completion_visible_in_cursor_popover( - editor.has_active_inline_completion(), - cx, - ) { + if editor + .edit_prediction_visible_in_cursor_popover(editor.has_active_inline_completion()) + { height_above_menu += editor.edit_prediction_cursor_popover_height() + POPOVER_Y_PADDING; edit_prediction_popover_visible = true; @@ -3557,7 +3556,7 @@ impl EditorElement { 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) { + if editor.edit_prediction_visible_in_cursor_popover(true) { return None; } diff --git a/crates/feedback/src/feedback_modal.rs b/crates/feedback/src/feedback_modal.rs index 34f8e2fa49..f2b6b46447 100644 --- a/crates/feedback/src/feedback_modal.rs +++ b/crates/feedback/src/feedback_modal.rs @@ -191,7 +191,7 @@ impl FeedbackModal { ); editor.set_show_gutter(false, cx); editor.set_show_indent_guides(false, cx); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_vertical_scroll_margin(5, cx); editor.set_use_modal_editing(false); editor.set_soft_wrap(); diff --git a/crates/inline_completion_button/src/inline_completion_button.rs b/crates/inline_completion_button/src/inline_completion_button.rs index 1b9e730969..244fa8324b 100644 --- a/crates/inline_completion_button/src/inline_completion_button.rs +++ b/crates/inline_completion_button/src/inline_completion_button.rs @@ -611,7 +611,7 @@ impl InlineCompletionButton { .unwrap_or(true), ) }; - self.editor_show_predictions = editor.should_show_inline_completions(cx); + self.editor_show_predictions = editor.edit_predictions_enabled(); self.edit_prediction_provider = editor.edit_prediction_provider(); self.language = language.cloned(); self.file = file; diff --git a/crates/language_tools/src/lsp_log.rs b/crates/language_tools/src/lsp_log.rs index 9d4a236f5f..5241eb210d 100644 --- a/crates/language_tools/src/lsp_log.rs +++ b/crates/language_tools/src/lsp_log.rs @@ -708,7 +708,7 @@ impl LspLogView { editor.set_text(log_contents, window, cx); editor.move_to_end(&MoveToEnd, window, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor }); @@ -751,7 +751,7 @@ impl LspLogView { ); editor.set_text(server_info, window, cx); editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor }); diff --git a/crates/prompt_library/src/prompt_library.rs b/crates/prompt_library/src/prompt_library.rs index be5e31d6b5..a37957bfd6 100644 --- a/crates/prompt_library/src/prompt_library.rs +++ b/crates/prompt_library/src/prompt_library.rs @@ -563,7 +563,7 @@ impl PromptLibrary { editor.set_text(prompt_metadata.title.unwrap_or_default(), window, cx); if prompt_id.is_built_in() { editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); } editor }); @@ -578,7 +578,7 @@ impl PromptLibrary { let mut editor = Editor::for_buffer(buffer, None, window, cx); if prompt_id.is_built_in() { editor.set_read_only(true); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); } editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx); editor.set_show_gutter(false, cx); diff --git a/crates/zed/src/zed/quick_action_bar.rs b/crates/zed/src/zed/quick_action_bar.rs index 5f2b98d444..3e1b57125c 100644 --- a/crates/zed/src/zed/quick_action_bar.rs +++ b/crates/zed/src/zed/quick_action_bar.rs @@ -104,7 +104,7 @@ 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 show_inline_completions = editor.should_show_inline_completions(cx); + let show_edit_predictions = editor.edit_predictions_enabled(); let inline_completion_enabled = editor.inline_completions_enabled(cx); ( @@ -114,7 +114,7 @@ impl Render for QuickActionBar { git_blame_inline_enabled, show_git_blame_gutter, auto_signature_help_enabled, - show_inline_completions, + show_edit_predictions, inline_completion_enabled, ) }; diff --git a/crates/zeta/src/rate_completion_modal.rs b/crates/zeta/src/rate_completion_modal.rs index dda838c21b..ed79f41fe8 100644 --- a/crates/zeta/src/rate_completion_modal.rs +++ b/crates/zeta/src/rate_completion_modal.rs @@ -279,7 +279,7 @@ impl RateCompletionModal { editor.set_show_runnables(false, cx); editor.set_show_wrap_guides(false, cx); editor.set_show_indent_guides(false, cx); - editor.set_show_inline_completions(Some(false), window, cx); + editor.set_show_edit_predictions(Some(false), window, cx); editor.set_placeholder_text("Add your feedback…", cx); if focus { cx.focus_self(window);