diff --git a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs index 8de8d6dc5..593d2b9ae 100644 --- a/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs +++ b/ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs @@ -41,6 +41,7 @@ using ILSpy.Commands; using ILSpy.Docking; using ILSpy.Metadata; using ILSpy.Metadata.CorTables; +using ILSpy.Search; using ILSpy.TreeNodes; using ILSpy.ViewModels; using ILSpy.Views; @@ -1483,6 +1484,42 @@ public class AssemblyTreeTests "Execute under headless must be a no-op rather than crash"); } + [AvaloniaTest] + public async Task Active_Search_Term_Does_Not_Hide_Member_Tree_Nodes() + { + // Pre-existing port misstep: commit 45461ddde wired the search-pane's term into + // LanguageSettings.SearchTerm and made SearchTermMatches gate visibility on it. + // WPF intentionally makes SearchTermMatches a no-op (returns true) so the assembly + // tree stays independent of the search pane. After fixing parity, FieldTreeNode.Filter + // must NOT return Hidden purely because the field's name doesn't contain the active + // SearchTerm — only ShowApiLevel + ShowMember remain valid hiding criteria. + + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + + var settings = AppComposition.Current.GetExport() + .SessionSettings.LanguageSettings; + settings.SearchTerm = "ZZZ_DefinitelyNotAnEnumName"; + + var coreLibName = typeof(object).Assembly.GetName().Name!; + var dayOfWeek = vm.AssemblyTreeModel.FindNode( + coreLibName, "System", "System.DayOfWeek"); + Assert.That(dayOfWeek, Is.Not.Null, "System.DayOfWeek must be discoverable in CoreLib"); + + dayOfWeek!.EnsureLazyChildren(); + var monday = dayOfWeek.Children.OfType() + .FirstOrDefault(f => f.FieldDefinition.Name == "Monday"); + Assert.That(monday, Is.Not.Null, + "Monday must be present as a FieldTreeNode child of System.DayOfWeek"); + + // Direct Filter() invocation bypasses the IsVisible gate that the implicit cascade + // honours, so the assertion exercises the policy independent of view-tree state. + monday!.Filter(settings).Should().NotBe(FilterResult.Hidden, + "an active search term must not hide member tree nodes — WPF's SearchTermMatches is a no-op"); + } + [AvaloniaTest] public async Task Global_Namespace_Types_Appear_Under_Dash_Node() { diff --git a/ILSpy.Tests/Search/SearchTermFilterTests.cs b/ILSpy.Tests/Search/SearchTermFilterTests.cs index 2b3024541..d668087a1 100644 --- a/ILSpy.Tests/Search/SearchTermFilterTests.cs +++ b/ILSpy.Tests/Search/SearchTermFilterTests.cs @@ -16,7 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.Linq; using System.Threading.Tasks; using Avalonia.Headless.NUnit; @@ -35,28 +34,30 @@ namespace ICSharpCode.ILSpy.Tests.Search; public class SearchTermFilterTests { [AvaloniaTest] - public Task SearchTermMatches_Empty_Term_Matches_Anything() + public Task SearchTermMatches_Is_A_No_Op_That_Always_Returns_True() { - var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; - settings.SearchTerm = string.Empty; - settings.SearchTermMatches("Object").Should().BeTrue(); - settings.SearchTermMatches("IEnumerable").Should().BeTrue(); - settings.SearchTermMatches(string.Empty).Should().BeTrue(); - return Task.CompletedTask; - } + // Pins the WPF-parity contract: the search pane drives its own results via the + // ILSpyX search strategies; LanguageSettings.SearchTermMatches deliberately ignores + // SearchTerm so the assembly-tree filter cascade stays independent of the search + // pane. Without this, typing a term into the search box would hide tree rows whose + // names don't match it — including member rows under a type whose own name DOES + // match — because the cascade only resets the match bit one level deep. - [AvaloniaTest] - public Task SearchTermMatches_Is_Case_Insensitive_Substring() - { var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; - settings.SearchTerm = "enum"; try { - settings.SearchTermMatches("IEnumerable").Should().BeTrue( - "contains check is case-insensitive — IEnumerable contains 'enum'"); - settings.SearchTermMatches("Enumerator").Should().BeTrue(); - settings.SearchTermMatches("Object").Should().BeFalse( - "Object does not contain 'enum'"); + settings.SearchTerm = string.Empty; + settings.SearchTermMatches("Object").Should().BeTrue(); + settings.SearchTermMatches("IEnumerable").Should().BeTrue(); + settings.SearchTermMatches(string.Empty).Should().BeTrue(); + + settings.SearchTerm = "enum"; + settings.SearchTermMatches("IEnumerable").Should().BeTrue(); + settings.SearchTermMatches("Object").Should().BeTrue( + "WPF parity: SearchTermMatches must NOT honour the SearchTerm — it's a no-op shim"); + + settings.SearchTerm = "ZZZ_NoMatchAnywhere"; + settings.SearchTermMatches("Anything").Should().BeTrue(); } finally { @@ -66,8 +67,14 @@ public class SearchTermFilterTests } [AvaloniaTest] - public Task Typing_In_The_Search_Pane_Pushes_The_Term_Into_LanguageSettings() + public Task Typing_In_The_Search_Pane_Does_Not_Bleed_Into_LanguageSettings_SearchTerm() { + // The search pane is decoupled from the assembly-tree filter cascade. Earlier port + // commits pushed SearchPaneModel.SearchTerm into LanguageSettings.SearchTerm to drive + // a tree-filter cascade — that path hid member rows users expected to see (e.g. enum + // literals under their matched-by-name enum type). Reverted to WPF parity: typing in + // the search pane drives the orchestrator only. + var search = AppComposition.Current.GetExport(); var settings = AppComposition.Current.GetExport().SessionSettings.LanguageSettings; settings.SearchTerm.Should().Be(string.Empty, @@ -76,8 +83,8 @@ public class SearchTermFilterTests search.SearchTerm = "Enumerable"; try { - settings.SearchTerm.Should().Be("Enumerable", - "SearchPaneModel must push the term to LanguageSettings so the assembly-tree filter cascade activates"); + settings.SearchTerm.Should().Be(string.Empty, + "SearchPaneModel must not bleed its term into LanguageSettings; the cascade stays independent"); } finally { diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index 6e09a396e..a058d6e6d 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -96,16 +96,15 @@ namespace ILSpy public partial string SearchTerm { get; set; } = string.Empty; /// - /// Case-insensitive substring match against the active . - /// Empty term matches anything so the assembly tree shows its full contents when - /// no search is active. + /// Tree-filter no-op. The search pane runs against the loaded assemblies directly + /// (see + friends); + /// piping its term through the assembly-tree filter cascade would hide member rows + /// whose names don't contain the term — even when their parent type *does* match — + /// because the cascade only resets the "match" bit one level deep. The matcher + /// stays as a parameter-taking shim so existing FieldTreeNode.Filter / + /// MethodTreeNode.Filter / etc. overrides keep their structure (the + /// ShowApiLevel + ShowMember checks they wrap are still meaningful). /// - public bool SearchTermMatches(string value) - { - var term = SearchTerm; - if (string.IsNullOrEmpty(term)) - return true; - return value.Contains(term, System.StringComparison.OrdinalIgnoreCase); - } + public bool SearchTermMatches(string value) => true; } } diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index 8eed545f5..6326c5ddf 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -162,14 +162,6 @@ namespace ILSpy.Search IsSearching = false; var term = SearchTerm ?? string.Empty; - - // Push the term into LanguageSettings so the assembly-tree filter cascade - // (every Filter override calls SearchTermMatches) hides non-matching rows - // while a search is active. Clearing the term restores the full tree. - var settings = TryGetSettings()?.SessionSettings?.LanguageSettings; - if (settings != null) - settings.SearchTerm = term; - if (string.IsNullOrWhiteSpace(term)) return;