From 5bab130c87cbb8140c3e95750ccb121c06ce4ef6 Mon Sep 17 00:00:00 2001 From: Jason-Shen2 Date: Tue, 14 Jul 2026 06:28:03 +0800 Subject: [PATCH] fix(terminal): close stale classic completion menu after backspacing past original prefix Resolve the TODO in update_tab_completion_menu: when classic completions are enabled and the user backspaces past the original query prefix, the cached suggestions were being kept on screen even though they were based on a different query. Restructure the staleness check to run in both classic and non-classic modes, after first checking for the active cycling case (selected item matches current word). This preserves Tab cycling behavior while properly closing the menu when the user edits away from the original prefix. Fixes #13605 --- app/src/terminal/input.rs | 81 +++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 45 deletions(-) diff --git a/app/src/terminal/input.rs b/app/src/terminal/input.rs index b1c68676e1..ac3924b538 100644 --- a/app/src/terminal/input.rs +++ b/app/src/terminal/input.rs @@ -11636,56 +11636,47 @@ impl Input { return true; } + // The current word is everything from the start of the replacement to the + // cursor. + let current_word = &editor_text[replacement_start..cursor_position.as_usize()]; + + // In classic completion mode, check if the user is actively cycling through + // completions (selected item matches current word). If so, keep the menu open + // without filtering, since cycling inserts the completion text directly. + if self.is_classic_completions_enabled(ctx) { + let current_selected_item = + self.input_suggestions.as_ref(ctx).get_selected_item_text(); + if current_selected_item.is_some_and(|selected| selected == current_word) { + return false; + } + } + // If the buffer no longer starts with the original buffer text, // then we should close the completion menu because the result set - // was based on a different query. - // - // For classic completions, this is a poor heuristic: when you cycle - // through fuzzy matches, the text up to the cursor might not start - // with the original buffer text anymore. - // TODO: there's a bug here where if you hit tab and backspace, - // the result set won't go away (stale). - if !text_up_to_cursor.starts_with(buffer_text_original) - && !self.is_classic_completions_enabled(ctx) - { - // Close the input suggestions since the buffer was edited to no longer - // contain the text that triggered tab completion. - true - } else { - // The current word is everything from the start of the replacement to the - // cursor - let current_word = &editor_text[replacement_start..cursor_position.as_usize()]; - - if self.is_classic_completions_enabled(ctx) { - let current_selected_item = - self.input_suggestions.as_ref(ctx).get_selected_item_text(); - if current_selected_item.is_some_and(|selected| selected == current_word) { - // If we're in classic completion mode and the selected item is equal - // to the current word, then we should keep the menu open; the user is cycling. - // We early-return because we don't want to filter the menu based on the - // selected item. - return false; - } - } + // was based on a different query. This applies in both classic and + // non-classic modes: if the user has edited away from the original + // prefix, the cached suggestions are stale. + if !text_up_to_cursor.starts_with(buffer_text_original) { + return true; + } - // If the user continues to type with the tab suggestions open, we perform a - // prefix search on the original results to filter the suggestions. - let should_close = self.input_suggestions.update(ctx, |suggestions, ctx| { - suggestions.prefix_search_for_tab_completion( - current_word, - completion_results, - TabCompletionsPreselectOption::Unchanged, - ctx, - ); + // If the user continues to type with the tab suggestions open, we perform a + // prefix search on the original results to filter the suggestions. + let should_close = self.input_suggestions.update(ctx, |suggestions, ctx| { + suggestions.prefix_search_for_tab_completion( + current_word, + completion_results, + TabCompletionsPreselectOption::Unchanged, + ctx, + ); - // We should close the menu if there aren't any results - // after filtering. - suggestions.items().is_empty() - }); + // We should close the menu if there aren't any results + // after filtering. + suggestions.items().is_empty() + }); - ctx.notify(); - should_close - } + ctx.notify(); + should_close } fn clear_screen(&mut self, ctx: &mut ViewContext) {