From 86834887da048b504373acc184135011aa3390b2 Mon Sep 17 00:00:00 2001 From: Smit Barmase Date: Wed, 17 Sep 2025 18:13:57 +0530 Subject: [PATCH] editor: Fix completions menu flashes on every keystroke in TSX files with emmet (#38320) Closes https://github.com/zed-industries/zed/issues/37774 Bug in https://github.com/zed-industries/zed/pull/32927 Instead of using trigger characters to clear cached completions items, now we check if the query is empty to clear it. Turns out Emmet defines whole [alphanumeric as trigger characters](https://github.com/olrtg/emmet-language-server/blob/279be108725fb391c167690b697ce154fd32657b/index.ts#L116) which causes flickering. Clear on trigger characters was introduced to get rid of cached completions like in the case of "Parent.Foo.Bar", where "." is one of the trigger characters. This works still since "." is not part of `completion_query_characters` and hence we use it as a boundary while building the current query. i.e in this case, the query would be empty after typing ".", clearing cached completions. Release Notes: - Fixed issue where completions menu flashed on every keystroke in TSX files with emmet extension installed. --- crates/editor/src/editor.rs | 61 ++++++++++++++++++------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/crates/editor/src/editor.rs b/crates/editor/src/editor.rs index bdf1ae16474c647abd7ecb0593fb272b37ef9c54..999e66efdcf7a39774cb29a6dba28473cd84fa81 100644 --- a/crates/editor/src/editor.rs +++ b/crates/editor/src/editor.rs @@ -5488,6 +5488,18 @@ impl Editor { drop(multibuffer_snapshot); + // Hide the current completions menu when query is empty. Without this, cached + // completions from before the trigger char may be reused (#32774). + if query.is_none() { + let menu_is_open = matches!( + self.context_menu.borrow().as_ref(), + Some(CodeContextMenu::Completions(_)) + ); + if menu_is_open { + self.hide_context_menu(window, cx); + } + } + let mut ignore_word_threshold = false; let provider = match requested_source { Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(), @@ -5509,37 +5521,6 @@ impl Editor { .as_ref() .is_none_or(|provider| provider.filter_completions()); - let trigger_kind = match trigger { - Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => { - CompletionTriggerKind::TRIGGER_CHARACTER - } - _ => CompletionTriggerKind::INVOKED, - }; - let completion_context = CompletionContext { - trigger_character: trigger.and_then(|trigger| { - if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { - Some(String::from(trigger)) - } else { - None - } - }), - trigger_kind, - }; - - // Hide the current completions menu when a trigger char is typed. Without this, cached - // completions from before the trigger char may be reused (#32774). Snippet choices could - // involve trigger chars, so this is skipped in that case. - if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER && self.snippet_stack.is_empty() - { - let menu_is_open = matches!( - self.context_menu.borrow().as_ref(), - Some(CodeContextMenu::Completions(_)) - ); - if menu_is_open { - self.hide_context_menu(window, cx); - } - } - if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() { if filter_completions { menu.filter(query.clone(), provider.clone(), window, cx); @@ -5570,11 +5551,29 @@ impl Editor { } }; + let trigger_kind = match trigger { + Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => { + CompletionTriggerKind::TRIGGER_CHARACTER + } + _ => CompletionTriggerKind::INVOKED, + }; + let completion_context = CompletionContext { + trigger_character: trigger.and_then(|trigger| { + if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER { + Some(String::from(trigger)) + } else { + None + } + }), + trigger_kind, + }; + let Anchor { excerpt_id: buffer_excerpt_id, text_anchor: buffer_position, .. } = buffer_position; + let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) = buffer_snapshot.surrounding_word(buffer_position, false) {