Browse Source

Assembly tree — Base/Derived, NavigationText, filter cascade

Five threads share infrastructure here, so they land together:

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
4c24419322
  1. 120
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 58
      ILSpy.Tests/Navigation/NavigationTests.cs
  3. 17
      ILSpy/AssemblyTree/AssemblyTreeModel.cs
  4. 1
      ILSpy/Assets/Icons/SubTypes.svg
  5. 1
      ILSpy/Assets/Icons/SuperTypes.svg
  6. 4
      ILSpy/Images.cs
  7. 10
      ILSpy/LanguageSettings.cs
  8. 42
      ILSpy/Languages/Language.cs
  9. 6
      ILSpy/NavigationEntry.cs
  10. 2
      ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs
  11. 8
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  12. 80
      ILSpy/TreeNodes/BaseTypesEntryNode.cs
  13. 85
      ILSpy/TreeNodes/BaseTypesTreeNode.cs
  14. 97
      ILSpy/TreeNodes/DerivedTypesEntryNode.cs
  15. 94
      ILSpy/TreeNodes/DerivedTypesTreeNode.cs
  16. 8
      ILSpy/TreeNodes/EventTreeNode.cs
  17. 8
      ILSpy/TreeNodes/FieldTreeNode.cs
  18. 18
      ILSpy/TreeNodes/FilterResult.cs
  19. 1
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  20. 2
      ILSpy/TreeNodes/MemberReferenceTreeNode.cs
  21. 10
      ILSpy/TreeNodes/MethodTreeNode.cs
  22. 2
      ILSpy/TreeNodes/ModuleReferenceTreeNode.cs
  23. 7
      ILSpy/TreeNodes/NamespaceTreeNode.cs
  24. 8
      ILSpy/TreeNodes/PropertyTreeNode.cs
  25. 2
      ILSpy/TreeNodes/ReferenceFolderTreeNode.cs
  26. 14
      ILSpy/TreeNodes/ResourceListTreeNode.cs
  27. 2
      ILSpy/TreeNodes/TypeReferenceTreeNode.cs
  28. 42
      ILSpy/TreeNodes/TypeTreeNode.cs

120
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -1332,4 +1332,124 @@ public class AssemblyTreeTests @@ -1332,4 +1332,124 @@ public class AssemblyTreeTests
((MethodTreeNode)resolved!).MethodDefinition.MetadataToken
.Should().Be(getter.MetadataToken);
}
[AvaloniaTest]
public async Task Type_Tree_Node_Exposes_BaseTypes_Subtree_Listing_Object_For_System_Exception()
{
// Each type node should surface a "Base Types" sub-tree listing its base classes /
// interfaces, mirroring the WPF tree. System.Exception extends only System.Object so we
// expect a single BaseTypesEntryNode whose label ends in "Object".
// Arrange — boot, wait for assemblies, drill into System.Exception.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.Exception");
typeNode.IsExpanded = true;
// Assert — BaseTypes child exists, expanding it yields entries, Object is among them.
var baseTypes = typeNode.Children.OfType<BaseTypesTreeNode>().Single();
baseTypes.IsExpanded = true;
baseTypes.Children.OfType<BaseTypesEntryNode>().Should().NotBeEmpty();
baseTypes.Children.OfType<BaseTypesEntryNode>()
.Should().Contain(e => e.Text.ToString()!.EndsWith("Object"),
"System.Exception's base-type chain must include Object");
}
[AvaloniaTest]
public async Task BaseTypesEntryNode_Activate_Navigates_To_The_Base_Type()
{
// Activating a BaseTypesEntryNode jumps the assembly-tree selection to the underlying
// type node — same gesture the WPF tree uses to walk an inheritance chain.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.Exception");
typeNode.IsExpanded = true;
var baseTypes = typeNode.Children.OfType<BaseTypesTreeNode>().Single();
baseTypes.IsExpanded = true;
var objectEntry = baseTypes.Children.OfType<BaseTypesEntryNode>()
.First(e => e.Text.ToString()!.EndsWith("Object"));
// Act — activate via the stub routed-args (mirrors a double-click).
var args = new StubRoutedEventArgs();
objectEntry.ActivateItem(args);
// Assert — selection moved off System.Exception and onto a TypeTreeNode for Object.
// Cast through object to bypass SharpTreeNodeAssertions' restricted member set.
((object?)vm.AssemblyTreeModel.SelectedItem).Should().BeOfType<TypeTreeNode>();
((object?)vm.AssemblyTreeModel.SelectedItem).Should().NotBeSameAs(typeNode);
((TypeTreeNode)vm.AssemblyTreeModel.SelectedItem!).Text.ToString()
.Should().Be("Object");
}
[AvaloniaTest]
public async Task Type_Tree_Node_Exposes_DerivedTypes_Subtree_For_Non_Sealed_Class()
{
// Non-sealed types get a "Derived Types" sub-tree listing classes that extend them. We
// scan the assembly list synchronously — for the small fixture loaded in tests this
// yields a few hits per common base (e.g. SystemException is derived from Exception).
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.Exception");
typeNode.IsExpanded = true;
var derived = typeNode.Children.OfType<DerivedTypesTreeNode>().Single();
derived.IsExpanded = true;
derived.Children.OfType<DerivedTypesEntryNode>().Should().NotBeEmpty(
"the loaded assembly list contains several Exception subclasses (e.g. SystemException, ArgumentException)");
}
[AvaloniaTest]
public async Task Sealed_Class_Has_No_DerivedTypes_Node()
{
// Sealed types can't be derived from, so the DerivedTypes sub-tree must not appear.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var stringNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.String");
stringNode.IsExpanded = true;
stringNode.Children.OfType<DerivedTypesTreeNode>()
.Should().BeEmpty("System.String is sealed");
}
[AvaloniaTest]
public async Task Object_Has_No_BaseTypes_Node()
{
// System.Object has no base types, so the BaseTypes sub-tree must not appear.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var objectNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.Object");
objectNode.IsExpanded = true;
objectNode.Children.OfType<BaseTypesTreeNode>()
.Should().BeEmpty("System.Object has no base types");
}
}

58
ILSpy.Tests/Navigation/NavigationTests.cs

@ -135,10 +135,14 @@ public class NavigationTests @@ -135,10 +135,14 @@ public class NavigationTests
// Assert 1 — newest-first ordering: index 0 is the immediate previous selection
// (methodB), index 1 is the one before that (methodA). Each menu item carries a
// TreeNodeEntry wrapping the original tree node.
// TreeNodeEntry wrapping the original tree node, and the header reads the richer
// NavigationText (mirrors WPF — disambiguates "Empty" from other "Empty" methods by
// prefixing the declaring type).
var items = flyout.Items.OfType<MenuItem>().ToList();
((string)items[0].Header!).Should().Be((string)methodB.Text);
((string)items[1].Header!).Should().Be((string)methodA.Text);
((string)items[0].Header!).Should().Be((string)methodB.NavigationText);
((string)items[1].Header!).Should().Be((string)methodA.NavigationText);
((string)items[0].Header!).Should().Contain("Enumerable",
"NavigationText must include the declaring type");
items[1].CommandParameter.Should().BeOfType<global::ILSpy.Navigation.TreeNodeEntry>();
var entry = (global::ILSpy.Navigation.TreeNodeEntry)items[1].CommandParameter!;
ReferenceEquals(entry.Node, methodA).Should().BeTrue();
@ -151,4 +155,52 @@ public class NavigationTests @@ -151,4 +155,52 @@ public class NavigationTests
// so Forward becomes available.
vm.DockWorkspace.NavigateForwardCommand.CanExecute(null).Should().BeTrue();
}
[AvaloniaTest]
public async Task Back_History_Dropdown_Uses_NavigationText_For_Generic_Tree_Nodes()
{
// Bare Text on grouping nodes ("Base Types", "Derived Types", "References") reads
// ambiguously in the back-history dropdown — the user can't tell which type's bases
// they're looking at. ILSpyTreeNode.NavigationText carries the richer disambiguated
// form ("Base Types (System.Exception)"), and TreeNodeEntry.DisplayText must use it
// when the node implements ILSpyTreeNode.
// Arrange — boot, wait for assemblies, expand System.Exception so we can select its
// BaseTypesTreeNode child.
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
var coreLibName = typeof(object).Assembly.GetName().Name!;
var typeNode = vm.AssemblyTreeModel.FindNode<TypeTreeNode>(
coreLibName, "System", "System.Exception");
typeNode.IsExpanded = true;
var baseTypes = typeNode.Children.OfType<BaseTypesTreeNode>().Single();
// Act — select the grouping node, wait for decompile, navigate elsewhere so the
// grouping lands on the back stack.
vm.AssemblyTreeModel.SelectNode(baseTypes);
await vm.DockWorkspace.WaitForDecompiledTextAsync();
await Task.Delay(600);
vm.AssemblyTreeModel.SelectNode(typeNode);
await vm.DockWorkspace.WaitForDecompiledTextAsync();
// Open the back-history flyout and read the entry header.
var backSplit = window.GetVisualDescendants().OfType<SplitButton>()
.Single(sb => sb.Name == "BackSplitButton");
var flyout = (MenuFlyout)backSplit.Flyout!;
flyout.ShowAt(backSplit);
await Waiters.WaitForAsync(() => flyout.Items.OfType<MenuItem>().Any());
// Assert — the entry shows the richer "Base Types (System.Exception)" form, NOT the
// bare "Base Types" Text.
var items = flyout.Items.OfType<MenuItem>().ToList();
var baseTypesEntry = items.First(i =>
((string)i.Header!).StartsWith((string)baseTypes.Text));
((string)baseTypesEntry.Header!).Should().Contain("Exception",
"the dropdown header must disambiguate generic grouping nodes via NavigationText");
((string)baseTypesEntry.Header!).Should().NotBe((string)baseTypes.Text,
"falling back to bare Text would reproduce the WPF parity gap");
}
}

17
ILSpy/AssemblyTree/AssemblyTreeModel.cs

@ -492,6 +492,23 @@ namespace ILSpy.AssemblyTree @@ -492,6 +492,23 @@ namespace ILSpy.AssemblyTree
SelectedItem = node;
}
/// <summary>
/// Resolves <paramref name="type"/> to the matching <see cref="TypeTreeNode"/> in the
/// loaded assembly list and selects it. Returns false when the type's parent module is
/// not loaded (or the namespace / nested-type chain can't be walked) — used by the
/// Base/Derived Types entry nodes to jump along an inheritance chain.
/// </summary>
public bool JumpToType(ITypeDefinition? type)
{
if (type == null || assemblyListTreeNode == null)
return false;
var node = FindTypeNode(assemblyListTreeNode, type);
if (node == null)
return false;
SelectNode(node);
return true;
}
public void OpenFiles(string[] fileNames, bool focusNode = true)
{
ArgumentNullException.ThrowIfNull(fileNames);

1
ILSpy/Assets/Icons/SubTypes.svg

@ -0,0 +1 @@ @@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="canvas" x="0" y="0" width="16" height="16" style="fill:#f6f6f6;fill-opacity:0;fill-rule:nonzero;"/><path id="outline" d="M6.814,14.21l2.474,0l4.948,-4.948l0,-1.099l-2.199,-2.199l-1.924,0l0,-4.123l-4.124,0l0,4.123l-1.924,0l-2.199,2.199l0,1.099l4.948,4.948Z" style="fill:#f6f6f6;fill-rule:nonzero;"/><path id="iconBg" d="M3.241,8.713l1.374,-1.375l2.749,0l0,-4.123l1.374,0l0,4.123l2.749,0l1.374,1.375l-4.123,4.123l-1.374,0l-4.123,-4.123Zm4.947,2.748l2.749,-2.748l-5.772,0l2.749,2.748l0.274,0Z" style="fill:#424242;fill-rule:nonzero;"/><path id="iconFg" d="M5.165,8.713l2.749,2.748l0.274,0l2.749,-2.748l-5.772,0" style="fill:#f0eff1;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

1
ILSpy/Assets/Icons/SuperTypes.svg

@ -0,0 +1 @@ @@ -0,0 +1 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg width="100%" height="100%" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xml:space="preserve" xmlns:serif="http://www.serif.com/" style="fill-rule:evenodd;clip-rule:evenodd;stroke-linejoin:round;stroke-miterlimit:2;"><rect id="canvas" x="0" y="0" width="16" height="16" style="fill:#f6f6f6;fill-opacity:0;fill-rule:nonzero;"/><path id="outline" d="M6.814,1.841l2.474,0l4.948,4.948l0,1.099l-2.199,2.199l-1.924,0l0,4.123l-4.124,0l0,-4.123l-1.924,0l-2.199,-2.199l0,-1.099l4.948,-4.948Z" style="fill:#f6f6f6;fill-rule:nonzero;"/><path id="iconBg" d="M3.241,7.338l1.374,1.375l2.749,0l0,4.123l1.374,0l0,-4.123l2.749,0l1.374,-1.375l-4.123,-4.123l-1.374,0l-4.123,4.123Zm4.947,-2.748l2.749,2.748l-5.772,0l2.749,-2.748l0.274,0Z" style="fill:#424242;fill-rule:nonzero;"/><path id="iconFg" d="M5.165,7.338l2.749,-2.748l0.274,0l2.749,2.748l-5.772,0" style="fill:#f0eff1;fill-rule:nonzero;"/></svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

4
ILSpy/Images.cs

@ -78,6 +78,10 @@ namespace ILSpy.Images @@ -78,6 +78,10 @@ namespace ILSpy.Images
public static readonly IImage ShowPrivateInternal = LoadSvg(nameof(ShowPrivateInternal));
public static readonly IImage ShowAll = LoadSvg(nameof(ShowAll));
// Type-relation tree nodes (Base Types / Derived Types).
public static readonly IImage SuperTypes = LoadSvg(nameof(SuperTypes));
public static readonly IImage SubTypes = LoadSvg(nameof(SubTypes));
// Containers
public static readonly IImage Namespace = LoadSvg(nameof(Namespace));
public static readonly IImage ReferenceFolder = LoadSvg(nameof(ReferenceFolder));

10
ILSpy/LanguageSettings.cs

@ -85,5 +85,15 @@ namespace ILSpy @@ -85,5 +85,15 @@ namespace ILSpy
[ObservableProperty]
string? languageVersionId;
// SearchTerm is currently a placeholder mirroring WPF — the Avalonia search pane isn't
// ported yet, but several tree-node Filter overrides reference SearchTermMatches when
// porting the WPF logic verbatim. Returning string.Empty + always-true SearchTermMatches
// means the Filter cascade collapses to "match if visibility passes", same as no-term-set
// in WPF. When the search pane lands, swap these for a real backing field bound to the
// pane's text box.
public string SearchTerm => string.Empty;
public bool SearchTermMatches(string value) => true;
}
}

42
ILSpy/Languages/Language.cs

@ -18,17 +18,20 @@ @@ -18,17 +18,20 @@
using System;
using System.Collections.Generic;
using System.Reflection.Metadata;
using System.Reflection.PortableExecutable;
using AvaloniaEdit.Highlighting;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Disassembler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.Solution;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Abstractions;
namespace ILSpy.Languages
{
@ -37,12 +40,49 @@ namespace ILSpy.Languages @@ -37,12 +40,49 @@ namespace ILSpy.Languages
/// Subclasses override formatting for their target syntax. Implementations must be
/// thread-safe.
/// </summary>
public abstract class Language
public abstract class Language : ILanguage
{
public abstract string Name { get; }
public abstract string FileExtension { get; }
/// <summary>
/// Versions selectable for this language (e.g. C# 1.0 → C# 14). Default is empty;
/// <see cref="HasLanguageVersions"/> reports whether any are available so toolbar UI
/// can hide the version picker for languages that don't differentiate.
/// </summary>
public virtual IReadOnlyList<LanguageVersion> LanguageVersions => System.Array.Empty<LanguageVersion>();
public bool HasLanguageVersions => LanguageVersions.Count > 0;
/// <summary>
/// Token-to-source-line mapping for navigation. Default returns a no-op CodeMappingInfo —
/// language subclasses with full decompilation override and produce a real one.
/// </summary>
public virtual CodeMappingInfo GetCodeMappingInfo(MetadataFile module, EntityHandle member)
=> new(module, (TypeDefinitionHandle)member);
/// <summary>
/// Stable name string for an entity reachable only by metadata token. Falls back to the
/// language's <see cref="EntityToString"/> path so subclasses don't have to repeat the
/// formatting rules — overrideable when a language wants a tokenless name (e.g. C# uses a
/// disassembler-friendly form).
/// </summary>
public virtual string GetEntityName(MetadataFile module, EntityHandle handle, bool fullName, bool omitGenerics)
{
ArgumentNullException.ThrowIfNull(module);
var typeSystem = new DecompilerTypeSystem(module, module.GetAssemblyResolver());
var entity = typeSystem.MainModule.ResolveEntity(handle);
if (entity == null)
return string.Empty;
var flags = ConversionFlags.ShowParameterList | ConversionFlags.ShowParameterModifiers;
if (fullName)
flags |= ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames;
if (!omitGenerics)
flags |= ConversionFlags.ShowTypeParameterList;
return entity is IType type ? TypeToString(type, flags) : EntityToString((IEntity)entity, flags);
}
public virtual string TypeToString(IType type, ConversionFlags conversionFlags = ConversionFlags.UseFullyQualifiedTypeNames | ConversionFlags.UseFullyQualifiedEntityNames)
{
ArgumentNullException.ThrowIfNull(type);

6
ILSpy/NavigationEntry.cs

@ -23,6 +23,7 @@ using Avalonia.Media; @@ -23,6 +23,7 @@ using Avalonia.Media;
using ICSharpCode.ILSpyX.TreeView;
using ILSpy.TreeNodes;
using ILSpy.ViewModels;
namespace ILSpy.Navigation
@ -68,7 +69,10 @@ namespace ILSpy.Navigation @@ -68,7 +69,10 @@ namespace ILSpy.Navigation
Node = node ?? throw new ArgumentNullException(nameof(node));
}
public override object DisplayText => Node.Text ?? string.Empty;
// ILSpyTreeNode exposes a richer NavigationText for nodes whose generic Text ("Base
// Types", "Derived Types", "References", …) reads ambiguously without context. Plain
// SharpTreeNode entries fall back to Text.
public override object DisplayText => (Node is ILSpyTreeNode ilspy ? ilspy.NavigationText : Node.Text) ?? string.Empty;
public override IImage? DisplayIcon => Node.Icon as IImage;

2
ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs

@ -60,6 +60,8 @@ namespace ILSpy.TreeNodes @@ -60,6 +60,8 @@ namespace ILSpy.TreeNodes
public override object Text => ILAmbience.EscapeName(reference.Name);
public override object? NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.References})";
public override object Icon {
get {
if (state == LoadState.Unloaded)

8
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -277,6 +277,14 @@ namespace ILSpy.TreeNodes @@ -277,6 +277,14 @@ namespace ILSpy.TreeNodes
Children.Add(new TypeTreeNode(t, module));
}
public override FilterResult Filter(LanguageSettings settings)
{
if (settings.SearchTermMatches(LoadedAssembly.ShortName))
return FilterResult.Match;
else
return FilterResult.Recurse;
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
void HandleException(Exception ex, string message)

80
ILSpy/TreeNodes/BaseTypesEntryNode.cs

@ -0,0 +1,80 @@ @@ -0,0 +1,80 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX.TreeView;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Single base-class / interface entry under a <see cref="BaseTypesTreeNode"/>. Activating
/// the entry navigates the assembly-tree selection to the corresponding <see cref="TypeTreeNode"/>.
/// </summary>
public sealed class BaseTypesEntryNode : ILSpyTreeNode, IMemberTreeNode
{
readonly ITypeDefinition type;
public BaseTypesEntryNode(ITypeDefinition type)
{
this.type = type;
}
public override object Text => Language.TypeToString(type, ConversionFlags.None);
public override object? NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.BaseTypes})";
public override object Icon => type.Kind == TypeKind.Interface
? Images.Images.Interface
: Images.Images.Class;
public override void ActivateItem(IPlatformRoutedEventArgs e)
{
e.Handled = ActivateItem(this, type);
}
/// <summary>
/// Shared activate helper used by <see cref="BaseTypesEntryNode"/> and
/// <see cref="DerivedTypesEntryNode"/>. Mirrors WPF's matching helper. The
/// <paramref name="node"/> parameter is unused on Avalonia (navigation routes through
/// the MEF-resolved <see cref="AssemblyTreeModel"/> instead of walking up the tree to
/// the assembly-list node), but the signature stays in lock-step with WPF so any future
/// caller that needs the originating node has it on hand.
/// </summary>
internal static bool ActivateItem(SharpTreeNode node, ITypeDefinition? def)
{
if (def == null)
return false;
var atm = AppComposition.Current.GetExport<AssemblyTreeModel>();
return atm.JumpToType(def);
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, ConversionFlags.None));
}
IEntity IMemberTreeNode.Member => type;
}
}

85
ILSpy/TreeNodes/BaseTypesTreeNode.cs

@ -0,0 +1,85 @@ @@ -0,0 +1,85 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// 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.Reflection.Metadata;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.TreeView;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Lists the base types of a class — the inheritance chain plus implemented interfaces, in
/// type-itself-first → most-distant-ancestor order. Lazy: children are only resolved when
/// the user expands the node.
/// </summary>
public sealed class BaseTypesTreeNode : ILSpyTreeNode
{
readonly MetadataFile module;
readonly ITypeDefinition type;
public BaseTypesTreeNode(MetadataFile module, ITypeDefinition type)
{
this.module = module;
this.type = type;
LazyLoading = true;
}
public override object Text => ICSharpCode.ILSpy.Properties.Resources.BaseTypes;
public override object? NavigationText => $"{Text} ({Language.TypeToString(type)})";
public override object Icon => Images.Images.SuperTypes;
protected override void LoadChildren()
{
AddBaseTypes(Children, module, type);
}
internal static void AddBaseTypes(SharpTreeNodeCollection children, MetadataFile module, ITypeDefinition typeDefinition)
{
// Re-resolve the type with an Uncached type system so we get a fresh inheritance
// chain (some Decompiler-flag toggles affect how interfaces fold in). Mirrors WPF.
var handle = (TypeDefinitionHandle)typeDefinition.MetadataToken;
var typeSystem = new DecompilerTypeSystem(module, module.GetAssemblyResolver(),
TypeSystemOptions.Default | TypeSystemOptions.Uncached);
if (typeSystem.MainModule.ResolveEntity(handle) is not ITypeDefinition t)
return;
// GetAllBaseTypeDefinitions returns [furthest-ancestor, ..., direct-base, self]; we
// want everything except self, in walk-up order — so reverse and skip(1).
foreach (var td in t.GetAllBaseTypeDefinitions().Reverse().Skip(1))
{
if (t.Kind != TypeKind.Interface || t.Kind == td.Kind)
children.Add(new BaseTypesEntryNode(td));
}
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
foreach (var child in Children.OfType<ILSpyTreeNode>())
child.Decompile(language, output, options);
}
}
}

97
ILSpy/TreeNodes/DerivedTypesEntryNode.cs

@ -0,0 +1,97 @@ @@ -0,0 +1,97 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
using System.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Output;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Abstractions;
using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Single derived-type entry under a <see cref="DerivedTypesTreeNode"/>. Activating jumps
/// to the corresponding <see cref="TypeTreeNode"/>. Itself lazy-recursive — expanding a
/// derived-type entry walks one more level, surfacing types derived from <em>it</em>.
/// </summary>
public sealed class DerivedTypesEntryNode : ILSpyTreeNode, IMemberTreeNode
{
readonly AssemblyList list;
readonly ITypeDefinition type;
public DerivedTypesEntryNode(AssemblyList list, ITypeDefinition type)
{
this.list = list;
this.type = type;
LazyLoading = true;
}
public override bool ShowExpander => !type.IsSealed && base.ShowExpander;
public override object Text => Language.TypeToString(type, ConversionFlags.None);
public override object? NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.DerivedTypes})";
public override object Icon => type.Kind == TypeKind.Interface
? Images.Images.Interface
: Images.Images.Class;
protected override void LoadChildren()
{
foreach (var entry in DerivedTypesTreeNode.FindDerivedTypes(list, LanguageService.CurrentLanguage, type, CancellationToken.None))
Children.Add(entry);
}
public override bool IsPublicAPI => type.Accessibility switch {
Accessibility.Public or Accessibility.Internal or Accessibility.ProtectedOrInternal => true,
_ => false,
};
/// <summary>
/// Mirrors WPF's filter — drop non-public entries under PublicOnly visibility, otherwise
/// recurse so the user can drill into derived chains. The WPF overload also reads
/// <c>SearchTermMatches</c> (a <see cref="LanguageSettings"/> helper that's not yet in
/// the Avalonia port) to surface only entries whose name matches the active search term;
/// reinstate that branch when the search infrastructure lands.
/// </summary>
public override FilterResult Filter(LanguageSettings settings)
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
return FilterResult.Recurse;
}
public override void ActivateItem(IPlatformRoutedEventArgs e)
{
e.Handled = BaseTypesEntryNode.ActivateItem(this, type);
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
language.WriteCommentLine(output, language.TypeToString(type, ConversionFlags.None));
}
IEntity IMemberTreeNode.Member => type;
}
}

94
ILSpy/TreeNodes/DerivedTypesTreeNode.cs

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
// Copyright (c) 2026 AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System.Collections.Generic;
using System.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Analyzers;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Lists the derived types of a class — every loaded type whose direct base list contains
/// our target. Lazy: the assembly-list scan only runs when the user expands the node. The
/// scan is synchronous; small assembly lists complete in milliseconds. A future task can
/// switch to a background-loaded variant when this proves slow under real workloads.
/// </summary>
public sealed class DerivedTypesTreeNode : ILSpyTreeNode
{
readonly AssemblyList list;
readonly ITypeDefinition type;
public DerivedTypesTreeNode(AssemblyList list, ITypeDefinition type)
{
this.list = list;
this.type = type;
LazyLoading = true;
}
public override object Text => ICSharpCode.ILSpy.Properties.Resources.DerivedTypes;
public override object? NavigationText => $"{Text} ({Language.TypeToString(type)})";
public override object Icon => Images.Images.SubTypes;
protected override void LoadChildren()
{
foreach (var entry in FindDerivedTypes(list, LanguageService.CurrentLanguage, type, CancellationToken.None))
Children.Add(entry);
}
internal static IEnumerable<DerivedTypesEntryNode> FindDerivedTypes(AssemblyList list, Language language, ITypeDefinition type, CancellationToken cancellationToken)
{
var context = new AnalyzerContext {
CancellationToken = cancellationToken,
Language = language,
AssemblyList = list,
};
var scope = context.GetScopeOf(type);
foreach (var td in scope.GetTypesInScope(cancellationToken))
{
cancellationToken.ThrowIfCancellationRequested();
foreach (var baseType in td.DirectBaseTypes)
{
if (baseType.FullName == type.FullName && baseType.TypeParameterCount == type.TypeParameterCount)
{
yield return new DerivedTypesEntryNode(list, td);
break;
}
}
}
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
EnsureLazyChildren();
foreach (var child in Children)
{
if (child is ILSpyTreeNode node)
node.Decompile(language, output, options);
}
}
}
}

8
ILSpy/TreeNodes/EventTreeNode.cs

@ -46,6 +46,9 @@ namespace ILSpy.TreeNodes @@ -46,6 +46,9 @@ namespace ILSpy.TreeNodes
}
public override object Text => Language.EntityToString(EventDefinition, ConversionFlags.None);
public override object NavigationText => Language.EntityToString(EventDefinition, ConversionFlags.ShowDeclaringType);
public override object Icon => Images.Images.GetIcon(Images.Images.Event,
Images.Images.GetOverlay(EventDefinition.Accessibility), EventDefinition.IsStatic);
@ -61,9 +64,10 @@ namespace ILSpy.TreeNodes @@ -61,9 +64,10 @@ namespace ILSpy.TreeNodes
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(EventDefinition))
if (settings.SearchTermMatches(EventDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.CurrentLanguage.ShowMember(EventDefinition)))
return FilterResult.Match;
return FilterResult.Hidden;
else
return FilterResult.Hidden;
}
public override string ToString() => "Event " + EventDefinition.Name;

8
ILSpy/TreeNodes/FieldTreeNode.cs

@ -40,6 +40,9 @@ namespace ILSpy.TreeNodes @@ -40,6 +40,9 @@ namespace ILSpy.TreeNodes
}
public override object Text => Language.EntityToString(FieldDefinition, ConversionFlags.None);
public override object NavigationText => Language.EntityToString(FieldDefinition, ConversionFlags.ShowDeclaringType);
public override object Icon => Images.Images.GetIcon(Images.Images.Field,
Images.Images.GetOverlay(FieldDefinition.Accessibility), FieldDefinition.IsStatic);
public override bool ShowExpander => false;
@ -56,9 +59,10 @@ namespace ILSpy.TreeNodes @@ -56,9 +59,10 @@ namespace ILSpy.TreeNodes
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(FieldDefinition))
if (settings.SearchTermMatches(FieldDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.CurrentLanguage.ShowMember(FieldDefinition)))
return FilterResult.Match;
return FilterResult.Hidden;
else
return FilterResult.Hidden;
}
public override string ToString() => "Field " + FieldDefinition.Name;

18
ILSpy/TreeNodes/FilterResult.cs

@ -21,14 +21,26 @@ namespace ILSpy.TreeNodes @@ -21,14 +21,26 @@ namespace ILSpy.TreeNodes
/// <summary>
/// Outcome of an <see cref="ILSpyTreeNode.Filter"/> evaluation against the active
/// <see cref="LanguageSettings"/>. Drives whether the assembly tree shows the node, hides
/// it outright, or shows it only if any descendant matches.
/// it outright, or shows it only if any descendant matches. Layout mirrors WPF's
/// <c>FilterResult</c> for 1:1 portability of <see cref="ILSpyTreeNode.Filter"/> overrides.
/// </summary>
public enum FilterResult
{
/// <summary>The node passes the filter and should be displayed.</summary>
/// <summary>Shows the node (and resets the search term for child nodes).</summary>
Match,
/// <summary>The node itself should be hidden, but its descendants must still be evaluated.</summary>
/// <summary>
/// Hides the node only if all children are hidden (and resets the search term for
/// child nodes). Treated identically to <see cref="Recurse"/> by the cascade — the
/// "reset search term" distinction is documented for forward-compat with the WPF
/// behavior contract.
/// </summary>
MatchAndRecurse,
/// <summary>
/// Hides the node only if all children are hidden (doesn't reset the search term
/// for child nodes).
/// </summary>
Recurse,
/// <summary>The node and all its descendants are hidden.</summary>

1
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -162,6 +162,7 @@ namespace ILSpy.TreeNodes @@ -162,6 +162,7 @@ namespace ILSpy.TreeNodes
child.IsHidden = false;
break;
case FilterResult.Recurse:
case FilterResult.MatchAndRecurse:
child.EnsureChildrenFiltered();
child.IsHidden = child.Children.All(c => c.IsHidden);
break;

2
ILSpy/TreeNodes/MemberReferenceTreeNode.cs

@ -52,6 +52,8 @@ namespace ILSpy.TreeNodes @@ -52,6 +52,8 @@ namespace ILSpy.TreeNodes
public override object Text => Signature;
public override object NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.ReferencedTypes})";
public override object Icon => r.MemberReferenceKind switch {
MemberReferenceKind.Method => Images.Images.MethodReference,
MemberReferenceKind.Field => Images.Images.FieldReference,

10
ILSpy/TreeNodes/MethodTreeNode.cs

@ -41,6 +41,8 @@ namespace ILSpy.TreeNodes @@ -41,6 +41,8 @@ namespace ILSpy.TreeNodes
public override object Text => Language.EntityToString(MethodDefinition, ConversionFlags.None);
public override object NavigationText => Language.EntityToString(MethodDefinition, ConversionFlags.ShowDeclaringType);
public override object Icon {
get {
var baseImage = MethodDefinition.IsConstructor ? Images.Images.Constructor :
@ -67,9 +69,13 @@ namespace ILSpy.TreeNodes @@ -67,9 +69,13 @@ namespace ILSpy.TreeNodes
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(MethodDefinition))
// WPF additionally hides extension-block implementation methods when the
// ExtensionMembers decompiler setting is on; tracked as `methodtreenode-extension-gate`
// in the parity TODO and reinstated when the DecompilerSettings UI lands.
if (settings.SearchTermMatches(MethodDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.CurrentLanguage.ShowMember(MethodDefinition)))
return FilterResult.Match;
return FilterResult.Hidden;
else
return FilterResult.Hidden;
}
// Stable identity for SessionSettings.ActiveTreeViewPath; format must round-trip

2
ILSpy/TreeNodes/ModuleReferenceTreeNode.cs

@ -70,6 +70,8 @@ namespace ILSpy.TreeNodes @@ -70,6 +70,8 @@ namespace ILSpy.TreeNodes
public override object Text => moduleName;
public override object? NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.References})";
public override object Icon => Images.Images.Library;
public override void ActivateItem(IPlatformRoutedEventArgs e)

7
ILSpy/TreeNodes/NamespaceTreeNode.cs

@ -98,9 +98,10 @@ namespace ILSpy.TreeNodes @@ -98,9 +98,10 @@ namespace ILSpy.TreeNodes
public override FilterResult Filter(LanguageSettings settings)
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
return FilterResult.Match;
if (settings.SearchTermMatches(name))
return FilterResult.MatchAndRecurse;
else
return FilterResult.Recurse;
}
}
}

8
ILSpy/TreeNodes/PropertyTreeNode.cs

@ -44,6 +44,9 @@ namespace ILSpy.TreeNodes @@ -44,6 +44,9 @@ namespace ILSpy.TreeNodes
}
public override object Text => Language.EntityToString(PropertyDefinition, ConversionFlags.None);
public override object NavigationText => Language.EntityToString(PropertyDefinition, ConversionFlags.ShowDeclaringType);
public override object Icon => Images.Images.GetIcon(Images.Images.Property,
Images.Images.GetOverlay(PropertyDefinition.Accessibility), PropertyDefinition.IsStatic);
@ -59,9 +62,10 @@ namespace ILSpy.TreeNodes @@ -59,9 +62,10 @@ namespace ILSpy.TreeNodes
{
if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI)
return FilterResult.Hidden;
if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(PropertyDefinition))
if (settings.SearchTermMatches(PropertyDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.CurrentLanguage.ShowMember(PropertyDefinition)))
return FilterResult.Match;
return FilterResult.Hidden;
else
return FilterResult.Hidden;
}
public override string ToString()

2
ILSpy/TreeNodes/ReferenceFolderTreeNode.cs

@ -46,6 +46,8 @@ namespace ILSpy.TreeNodes @@ -46,6 +46,8 @@ namespace ILSpy.TreeNodes
public override object Text => Resources.References;
public override object? NavigationText => $"{Text} ({module.Name})";
public override object Icon => Images.Images.ReferenceFolder;
protected override void LoadChildren()

14
ILSpy/TreeNodes/ResourceListTreeNode.cs

@ -44,6 +44,8 @@ namespace ILSpy.TreeNodes @@ -44,6 +44,8 @@ namespace ILSpy.TreeNodes
public override object Text => Resources._Resources;
public override object? NavigationText => $"{Text} ({module.Name})";
public override object Icon => IsExpanded ? Images.Images.FolderOpen : Images.Images.FolderClosed;
protected override void OnExpanding()
@ -64,6 +66,18 @@ namespace ILSpy.TreeNodes @@ -64,6 +66,18 @@ namespace ILSpy.TreeNodes
Children.Add(ResourceTreeNode.Create(r));
}
public override FilterResult Filter(LanguageSettings settings)
{
// WPF's variant short-circuits on string.IsNullOrEmpty(SearchTerm) — when no term
// is set the folder reads as a plain match, otherwise the tree walks into resources
// to surface only matching ones. We use the same shape so a future search pane
// behaves identically.
if (string.IsNullOrEmpty(settings.SearchTerm))
return FilterResult.MatchAndRecurse;
else
return FilterResult.Recurse;
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
Dispatcher.UIThread.Invoke(EnsureLazyChildren);

2
ILSpy/TreeNodes/TypeReferenceTreeNode.cs

@ -47,6 +47,8 @@ namespace ILSpy.TreeNodes @@ -47,6 +47,8 @@ namespace ILSpy.TreeNodes
public override object Text => Language.TypeToString(resolvedType, ConversionFlags.None);
public override object NavigationText => $"{Text} ({ICSharpCode.ILSpy.Properties.Resources.ReferencedTypes})";
public override object Icon => Images.Images.TypeReference;
protected override void LoadChildren()

42
ILSpy/TreeNodes/TypeTreeNode.cs

@ -27,6 +27,8 @@ using ICSharpCode.Decompiler.TypeSystem; @@ -27,6 +27,8 @@ using ICSharpCode.Decompiler.TypeSystem;
using ICSharpCode.ILSpyX;
using ILSpy;
using ILSpy.AppEnv;
using ILSpy.AssemblyTree;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
@ -99,9 +101,17 @@ namespace ILSpy.TreeNodes @@ -99,9 +101,17 @@ namespace ILSpy.TreeNodes
var typeDef = ResolveTypeDefinition();
if (typeDef == null)
return FilterResult.Match;
if (settings.ShowApiLevel == ApiVisibility.All || Language.ShowMember(typeDef))
return FilterResult.Match;
return FilterResult.Hidden;
if (settings.SearchTermMatches(typeDef.Name))
{
if (settings.ShowApiLevel == ApiVisibility.All || LanguageService.CurrentLanguage.ShowMember(typeDef))
return FilterResult.Match;
else
return FilterResult.Hidden;
}
else
{
return FilterResult.Recurse;
}
}
// Stable identity for SessionSettings.ActiveTreeViewPath. ReflectionName is
@ -112,6 +122,18 @@ namespace ILSpy.TreeNodes @@ -112,6 +122,18 @@ namespace ILSpy.TreeNodes
return typeDef?.ReflectionName ?? module.Metadata.GetString(module.Metadata.GetTypeDefinition(handle).Name);
}
// Sealed / static / value-type / enum / delegate cannot be the base of another class,
// so a DerivedTypes child would always show up empty. Suppress it for those kinds.
static bool CanHaveDerivedTypes(ITypeDefinition typeDef)
{
if (typeDef.IsSealed)
return false;
return typeDef.Kind switch {
TypeKind.Class or TypeKind.Interface => true,
_ => false,
};
}
ITypeDefinition? ResolveTypeDefinition()
{
var typeSystem = module.GetTypeSystemOrNull();
@ -126,6 +148,20 @@ namespace ILSpy.TreeNodes @@ -126,6 +148,20 @@ namespace ILSpy.TreeNodes
if (typeDef == null)
return;
// Inheritance-relation siblings come first so they sit above the type's own members.
// BaseTypes is skipped for System.Object (no upstream chain) and for value types'
// implicit System.ValueType base when there's nothing else to show — the AddBaseTypes
// pass produces an empty set, which collapses the node.
if (typeDef.DirectBaseTypes.Any())
Children.Add(new BaseTypesTreeNode(module, typeDef));
// DerivedTypes is meaningful only for non-sealed reference types (and abstract
// classes / interfaces). Sealed classes can't be derived from; static classes are
// implicitly sealed.
var assemblyList = AppComposition.Current.GetExport<AssemblyTreeModel>().AssemblyList;
if (assemblyList != null && CanHaveDerivedTypes(typeDef))
Children.Add(new DerivedTypesTreeNode(assemblyList, typeDef));
foreach (var nestedType in typeDef.NestedTypes
.OrderBy(t => t.Name, NaturalStringComparer.Instance))
{

Loading…
Cancel
Save