diff --git a/ILSpy.Tests/Search/SearchPaneViewTests.cs b/ILSpy.Tests/Search/SearchPaneViewTests.cs index 2dce5905a..024c63164 100644 --- a/ILSpy.Tests/Search/SearchPaneViewTests.cs +++ b/ILSpy.Tests/Search/SearchPaneViewTests.cs @@ -73,4 +73,32 @@ public class SearchPaneViewTests vm.SearchTerm.Should().Be("Enumerable", "the TextBox is bound two-way to SearchPaneModel.SearchTerm"); } + + [AvaloniaTest] + public async Task Pressing_Escape_In_The_Search_Input_Clears_SearchTerm() + { + // The clear-X button's tooltip advertises "Clear (Esc)". Pressing Esc while the + // SearchInput TextBox has focus must wipe the term — same path as clicking the + // button. Goes through the bound view-model so the orchestrator's cancel-and- + // restart path fires too. + + var window = AppComposition.Current.GetExport(); + window.Show(); + var pane = await window.WaitForComponent(); + var vm = (SearchPaneModel)pane.DataContext!; + + var input = pane.FindControl("SearchInput"); + input!.Text = "Enumerable"; + vm.SearchTerm.Should().Be("Enumerable", + "baseline: the test setup must put a value in the box before testing the clear"); + + input.RaiseEvent(new global::Avalonia.Input.KeyEventArgs { + Key = global::Avalonia.Input.Key.Escape, + KeyModifiers = global::Avalonia.Input.KeyModifiers.None, + RoutedEvent = global::Avalonia.Input.InputElement.KeyDownEvent, + Source = input, + }); + + vm.SearchTerm.Should().BeEmpty("Escape in the search input must clear the term"); + } } diff --git a/ILSpy/Search/SearchPane.axaml b/ILSpy/Search/SearchPane.axaml index 2ebb4bca9..78900ebc0 100644 --- a/ILSpy/Search/SearchPane.axaml +++ b/ILSpy/Search/SearchPane.axaml @@ -52,8 +52,9 @@ + Height="2" Margin="0,0,0,1" + BorderThickness="0" + MinHeight="2" /> diff --git a/ILSpy/Search/SearchPane.axaml.cs b/ILSpy/Search/SearchPane.axaml.cs index 6b2dbba83..ca4562fa9 100644 --- a/ILSpy/Search/SearchPane.axaml.cs +++ b/ILSpy/Search/SearchPane.axaml.cs @@ -36,6 +36,21 @@ namespace ILSpy.Search InitializeComponent(); SearchResults.DoubleTapped += OnResultDoubleTapped; SearchResults.KeyDown += OnResultKeyDown; + SearchInput.KeyDown += OnSearchInputKeyDown; + } + + void OnSearchInputKeyDown(object? sender, KeyEventArgs e) + { + // Escape mirrors the clear-X button. Goes through the bound view-model so the + // orchestrator's cancel-and-restart path fires the same way it does when the + // user backspaces the box empty by hand. + if (e.Key != Key.Escape) + return; + if (DataContext is SearchPaneModel vm && vm.SearchTerm.Length > 0) + { + vm.SearchTerm = string.Empty; + e.Handled = true; + } } void OnClearSearchClicked(object? sender, RoutedEventArgs e)