Browse Source

Resources folder under each assembly node

Assisted-by: Claude:claude-opus-4-7:Claude Code

Assisted-by: Claude:claude-opus-4-7:Claude Code
pull/3755/head
Siegfried Pammer 2 months ago
parent
commit
e154d79a6f
  1. 21
      ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs
  2. 1
      ILSpy/Assets/Icons/FolderClosed.svg
  3. 1
      ILSpy/Assets/Icons/FolderOpen.svg
  4. 1
      ILSpy/Assets/Icons/Resource.svg
  5. 3
      ILSpy/Images.cs
  6. 3
      ILSpy/TreeNodes/AssemblyTreeNode.cs
  7. 77
      ILSpy/TreeNodes/ResourceListTreeNode.cs
  8. 56
      ILSpy/TreeNodes/ResourceTreeNode.cs

21
ILSpy.Tests/AssemblyList/AssemblyTreeTests.cs

@ -44,6 +44,27 @@ namespace ICSharpCode.ILSpy.Tests;
[TestFixture] [TestFixture]
public class AssemblyTreeTests public class AssemblyTreeTests
{ {
[AvaloniaTest]
public async Task Assembly_Node_Has_Resources_Folder_When_Module_Has_Resources()
{
var window = AppComposition.Current.GetExport<MainWindow>();
window.Show();
var vm = (MainWindowViewModel)window.DataContext!;
await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 3);
// Pick a loaded assembly that ships embedded resources. CoreLib always has at least
// the localised error-message tables.
var coreLibName = typeof(object).Assembly.GetName().Name!;
var assemblyNode = vm.AssemblyTreeModel.FindNode<AssemblyTreeNode>(coreLibName);
assemblyNode.EnsureLazyChildren();
var resources = assemblyNode.Children.OfType<ResourceListTreeNode>().Single();
resources.Text.Should().Be(Resources._Resources);
resources.EnsureLazyChildren();
resources.Children.Should().NotBeEmpty();
resources.Children.Should().AllBeAssignableTo<ResourceTreeNode>();
}
[AvaloniaTest] [AvaloniaTest]
public async Task Assembly_Node_Has_References_Folder_Child() public async Task Assembly_Node_Has_References_Folder_Child()
{ {

1
ILSpy/Assets/Icons/FolderClosed.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-folder{fill:#dcb67a}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M0 0h16v16H0V0z" id="canvas"/><path class="icon-vs-out" d="M1.5 1h8.11l1 2h2.886c.827 0 1.5.673 1.5 1.5v8c0 .827-.673 1.5-1.5 1.5H1.5C.673 14 0 13.327 0 12.5v-10C0 1.673.673 1 1.5 1z" id="outline"/><path class="icon-folder" d="M13.496 4H9.992l-1-2H1.5a.5.5 0 0 0-.5.5v10a.5.5 0 0 0 .5.5h11.996a.5.5 0 0 0 .5-.5v-8a.5.5 0 0 0-.5-.5zM2 3h6.374l.5 1H2V3z" id="iconBg"/><path class="icon-vs-fg" d="M2 3h6.374l.5 1H2V3z" id="iconFg"/></svg>

After

Width:  |  Height:  |  Size: 676 B

1
ILSpy/Assets/Icons/FolderOpen.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-folder{fill:#dcb67a}</style><path class="icon-canvas-transparent" d="M0 0h16v16H0V0z" id="canvas"/><path class="icon-vs-out" d="M0 2.969v9c0 .617.227 1.102.57 1.435.57.543 1.43.565 1.43.565h11.677L16 8.165V6.969h-1v-2c0-1.299-1.03-2-2-2h-2.884l-1-2H2c-.995 0-2 .697-2 2z" id="outline"/><path class="icon-folder" d="M1 3v9c0 .97.94.984.997.984H2V3h6l1 2h4v3H4l-2 5h11l2-5h-1V5c0-1-1.236-1-1-1H9.5l-1-2H2S1 2 1 3z" id="iconBg"/></svg>

After

Width:  |  Height:  |  Size: 579 B

1
ILSpy/Assets/Icons/Resource.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" enable-background="new 0 0 16 16"><style type="text/css">.icon-canvas-transparent{opacity:0;fill:#F6F6F6;} .icon-vs-out{fill:#F6F6F6;} .icon-vs-bg{fill:#424242;} .icon-vs-fg_x0020_2{fill:#F0EFF1;}</style><path class="icon-canvas-transparent" d="M16 16h-16v-16h16v16z" id="canvas"/><path class="icon-vs-out" d="M15 16h-13v-16h8.621l4.379 4.379v11.621z" id="outline"/><path class="icon-vs-fg_x0020_2" d="M13 14h-9v-12h5v4h4v8zm-3-9v-2.793l2.793 2.793h-2.793z" id="iconFg"/><path class="icon-vs-bg" d="M3 1v14h11v-10.207l-3.793-3.793h-7.207zm10 13h-9v-12h5v4h4v8zm-3-9v-2.793l2.793 2.793h-2.793z" id="iconBg"/></svg>

After

Width:  |  Height:  |  Size: 673 B

3
ILSpy/Images.cs

@ -77,6 +77,9 @@ namespace ILSpy.Images
public static readonly IImage Namespace = LoadSvg(nameof(Namespace)); public static readonly IImage Namespace = LoadSvg(nameof(Namespace));
public static readonly IImage ReferenceFolder = LoadSvg(nameof(ReferenceFolder)); public static readonly IImage ReferenceFolder = LoadSvg(nameof(ReferenceFolder));
public static readonly IImage MetadataTable = LoadSvg(nameof(MetadataTable)); public static readonly IImage MetadataTable = LoadSvg(nameof(MetadataTable));
public static readonly IImage FolderClosed = LoadSvg(nameof(FolderClosed));
public static readonly IImage FolderOpen = LoadSvg(nameof(FolderOpen));
public static readonly IImage Resource = LoadSvg(nameof(Resource));
// Types // Types
public static readonly IImage Class = LoadSvg(nameof(Class)); public static readonly IImage Class = LoadSvg(nameof(Class));

3
ILSpy/TreeNodes/AssemblyTreeNode.cs

@ -205,6 +205,9 @@ namespace ILSpy.TreeNodes
Children.Add(new ReferenceFolderTreeNode(module, this)); Children.Add(new ReferenceFolderTreeNode(module, this));
if (module.Resources.Any())
Children.Add(new ResourceListTreeNode(module));
var metadata = module.Metadata; var metadata = module.Metadata;
var namespaces = metadata.TypeDefinitions var namespaces = metadata.TypeDefinitions
.Where(t => metadata.GetTypeDefinition(t).GetDeclaringType().IsNil) .Where(t => metadata.GetTypeDefinition(t).GetDeclaringType().IsNil)

77
ILSpy/TreeNodes/ResourceListTreeNode.cs

@ -0,0 +1,77 @@
// 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 Avalonia.Threading;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.Metadata;
using ICSharpCode.ILSpy.Properties;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Lists the embedded resources of an assembly. Children are <see cref="ResourceTreeNode"/>
/// instances created via the <see cref="ResourceTreeNode.Create"/> factory.
/// </summary>
sealed class ResourceListTreeNode : ILSpyTreeNode
{
readonly MetadataFile module;
public ResourceListTreeNode(MetadataFile module)
{
this.module = module;
LazyLoading = true;
}
public override object Text => Resources._Resources;
public override object Icon => IsExpanded ? Images.Images.FolderOpen : Images.Images.FolderClosed;
protected override void OnExpanding()
{
base.OnExpanding();
RaisePropertyChanged(nameof(Icon));
}
protected override void OnCollapsing()
{
base.OnCollapsing();
RaisePropertyChanged(nameof(Icon));
}
protected override void LoadChildren()
{
foreach (var r in module.Resources.OrderBy(m => m.Name, NaturalStringComparer.Instance))
Children.Add(ResourceTreeNode.Create(r));
}
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
Dispatcher.UIThread.Invoke(EnsureLazyChildren);
foreach (var child in Children.OfType<ILSpyTreeNode>())
{
child.Decompile(language, output, options);
output.WriteLine();
}
}
}
}

56
ILSpy/TreeNodes/ResourceTreeNode.cs

@ -0,0 +1,56 @@
// 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;
using ICSharpCode.Decompiler;
using ICSharpCode.Decompiler.IL;
using ICSharpCode.Decompiler.Metadata;
using ILSpy.Languages;
namespace ILSpy.TreeNodes
{
/// <summary>
/// Default resource entry node — used when no specialised handler (image, XAML, .resources
/// file, …) is registered for a given resource. The Avalonia port currently always falls
/// back to this plain node since the typed handlers are not yet ported.
/// </summary>
public class ResourceTreeNode : ILSpyTreeNode
{
public ResourceTreeNode(Resource r)
{
Resource = r ?? throw new ArgumentNullException(nameof(r));
}
public Resource Resource { get; }
public override object Text => ILAmbience.EscapeName(Resource.Name);
public override object Icon => Images.Images.Resource;
public override void Decompile(Language language, ITextOutput output, DecompilationOptions options)
{
var sizeInBytes = Resource.TryGetLength();
var sizeInBytesText = sizeInBytes == null ? "" : ", " + sizeInBytes + " bytes";
language.WriteCommentLine(output, $"{Resource.Name} ({Resource.ResourceType}, {Resource.Attributes}{sizeInBytesText})");
}
public static ILSpyTreeNode Create(Resource resource) => new ResourceTreeNode(resource);
}
}
Loading…
Cancel
Save