Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion app/src/terminal/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11661,9 +11661,19 @@ impl Input {
// 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.
// selected item. This must happen before validating the original prefix so
// fuzzy matches that do not start with the original query can still cycle.
return false;
}

// Classic completions allow the buffer to diverge from the full original
// buffer while cycling, but the original replacement prefix must remain intact.
// Otherwise backspacing past the query causes an empty/short prefix search to
// restore the stale result set.
let original_word = &buffer_text_original[replacement_start..];
if !current_word.starts_with(original_word) {
return true;
}
}

// If the user continues to type with the tab suggestions open, we perform a
Expand Down
120 changes: 120 additions & 0 deletions app/src/terminal/input_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6775,6 +6775,126 @@ fn test_tab_completions_menu_for_classic_completions() {
})
}

#[test]
fn test_classic_completions_close_when_backspaced_past_original_query() {
let _flag = FeatureFlag::ClassicCompletions.override_enabled(true);
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let editor = input.read(&app, |input, _| input.editor().clone());

app.update(|ctx| {
InputSettings::handle(ctx).update(ctx, |setting, ctx| {
setting
.classic_completions_mode
.toggle_and_save_value(ctx)
.expect("Able to turn on classic completions");
})
});

input.update(&mut app, |input, ctx| {
input.clear_buffer_and_reset_undo_stack(ctx);
input.user_insert("cd Do", ctx);
input.input_tab(ctx);
input.handle_completion_suggestions_results(
build_suggestion_results(
vec![file_suggestion("Downloads"), file_suggestion("Documents")],
(3, 5),
MatchStrategy::CaseInsensitive,
),
CompletionsTrigger::Keybinding,
editor_model_snapshot(input, ctx),
ctx,
);
input.input_tab(ctx);
});

input.read(&app, |input, ctx| {
assert_eq!(input.buffer_text(ctx), "cd Downloads");
assert!(matches!(
input.suggestions_mode_model.as_ref(ctx).mode(),
InputSuggestionsMode::CompletionSuggestions { .. }
));
});

// Backspacing through the selected result until only `D` remains should dismiss the menu
// instead of filtering with `D` and resurrecting the original result set.
editor.update(&mut app, |editor, ctx| {
for _ in 0.."ownloads".len() {
editor.backspace(ctx);
}
});

input.read(&app, |input, ctx| {
assert_eq!(input.buffer_text(ctx), "cd D");
assert_eq!(
*input.suggestions_mode_model.as_ref(ctx).mode(),
InputSuggestionsMode::Closed
);
});
})
}

#[test]
fn test_classic_completions_keep_fuzzy_selection_open() {
let _flag = FeatureFlag::ClassicCompletions.override_enabled(true);
App::test((), |mut app| async move {
initialize_app(&mut app);
let terminal = add_window_with_bootstrapped_terminal(&mut app, None, None).await;
let input = terminal.read(&app, |terminal, _| terminal.input().clone());
let editor = input.read(&app, |input, _| input.editor().clone());

app.update(|ctx| {
InputSettings::handle(ctx).update(ctx, |setting, ctx| {
setting
.classic_completions_mode
.toggle_and_save_value(ctx)
.expect("Able to turn on classic completions");
})
});

input.update(&mut app, |input, ctx| {
input.clear_buffer_and_reset_undo_stack(ctx);
input.user_insert("cd Do", ctx);
input.input_tab(ctx);
input.handle_completion_suggestions_results(
build_suggestion_results(
vec![fuzzy_argument_suggestion("Desktop", vec![0, 5])],
(3, 5),
MatchStrategy::Fuzzy,
),
CompletionsTrigger::Keybinding,
editor_model_snapshot(input, ctx),
ctx,
);
input.input_tab(ctx);
});

input.read(&app, |input, ctx| {
assert_eq!(input.buffer_text(ctx), "cd Desktop");
assert!(matches!(
input.suggestions_mode_model.as_ref(ctx).mode(),
InputSuggestionsMode::CompletionSuggestions { .. }
));
});

editor.update(&mut app, |editor, ctx| {
for _ in 0.."esktop".len() {
editor.backspace(ctx);
}
});

input.read(&app, |input, ctx| {
assert_eq!(input.buffer_text(ctx), "cd D");
assert_eq!(
*input.suggestions_mode_model.as_ref(ctx).mode(),
InputSuggestionsMode::Closed
);
});
})
}

#[test]
fn test_tab_completions_menu_for_classic_completions_with_files() {
let _flag = FeatureFlag::ClassicCompletions.override_enabled(true);
Expand Down