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
This commit is contained in:
Agus Zubiaga 2025-02-10 17:57:25 -03:00 committed by GitHub
parent 973cb916f3
commit 0af048a7cf
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 124 additions and 102 deletions

View file

@ -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::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,

View file

@ -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::<DeletedLines>(
Anchor::min()..Anchor::max(),
cx.theme().status().deleted_background,

View file

@ -497,6 +497,23 @@ struct InlineCompletionState {
invalidation_range: Range<Anchor>,
}
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<InlineCompletionState>,
/// Used to prevent flickering as the user types while the menu is open
stale_inline_completion_in_menu: Option<InlineCompletionState>,
edit_prediction_settings: EditPredictionSettings,
inline_completions_hidden_for_vim_mode: bool,
show_inline_completions_override: Option<bool>,
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<Self>,
) {
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<bool>,
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>,
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<Self>,
) {
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,

View file

@ -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;
}

View file

@ -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();

View file

@ -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;

View file

@ -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
});

View file

@ -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);

View file

@ -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,
)
};

View file

@ -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);