From 4d465bead341d227ad1693d0077efa61419fad12 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Sun, 4 Oct 2026 15:51:26 +0200 Subject: [PATCH] Keep package entry selections decompilable The stale-node guard compared selected nodes only against top-level assembly-list entries, so nodes owned by package entries looked removed even while their parent package was active. Walk the parent bundle chain before clearing the decompiler tab so restored or clicked package-entry selections still render. Assisted-by: OpenCode:openai/gpt-5.5:OpenCode --- .../AssemblyTree/AssemblyTreeModelTests.cs | 20 +++++++++++++++++++ ILSpy/Docking/DockWorkspace.cs | 13 +++++++++++- 2 files changed, 32 insertions(+), 1 deletion(-) diff --git a/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs b/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs index c7e9f879e..3e4e7f98b 100644 --- a/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs +++ b/ILSpy.Tests/AssemblyTree/AssemblyTreeModelTests.cs @@ -188,4 +188,24 @@ public class AssemblyTreeModelTests sibling.Children.Should().BeEmpty( "only the folders on the path down to the target get expanded."); } + + [AvaloniaTest] + public async Task Selecting_a_type_inside_a_package_entry_decompiles_it() + { + var (_, vm) = await TestHarness.BootAsync(); + await vm.OpenAssemblyAsync(CreatePackage()); + + var nested = (await vm.AssemblyTreeModel.AssemblyList!.GetAllAssemblies()) + .Single(a => a.FileName == "lib/net10.0/Nested.dll"); + var type = nested.GetTypeSystemOrNull()!.MainModule.TypeDefinitions + .Single(t => t.Name == FixtureAssembly.TypeName); + var node = vm.AssemblyTreeModel.FindTreeNode(type); + ((object?)node).Should().BeOfType(); + + vm.AssemblyTreeModel.SelectNode(node!); + var tab = await vm.DockWorkspace.WaitForDecompiledTextAsync(); + + tab.Text.Should().Contain(FixtureAssembly.TypeName, + "a live package-entry node must not be mistaken for a stale node from a removed assembly"); + } } diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 6587e91b9..d61695b39 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -803,7 +803,7 @@ namespace ICSharpCode.ILSpy.Docking assemblyTreeModel.AssemblyList?.GetAssemblies() ?? []); if (nodes.Any(n => n.AncestorsAndSelf() .OfType() - .Any(a => !activeAssemblies.Contains(a.LoadedAssembly)))) + .Any(a => !IsActiveAssemblyOrPackageEntry(a.LoadedAssembly, activeAssemblies)))) { lastShownNodes = null; ClearActiveDecompilerTab(); @@ -879,6 +879,17 @@ namespace ICSharpCode.ILSpy.Docking ActivateMainTabIfNeeded(main); } + static bool IsActiveAssemblyOrPackageEntry(ICSharpCode.ILSpyX.LoadedAssembly assembly, + HashSet activeAssemblies) + { + for (var current = assembly; current != null; current = current.ParentBundle) + { + if (activeAssemblies.Contains(current)) + return true; + } + return false; + } + // The "exactly one preview tab" rule: tree selections always route to the single preview // ("the One"), tracked by factory.MainTab. Reuse it wherever it sits -- even if a frozen // tab is currently active, ActivateMainTabIfNeeded brings it forward -- as long as it is