diff --git a/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs b/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs index 2d3007682..f813ae4b4 100644 --- a/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs +++ b/ILSpy.Tests/ContextMenus/DecompileInNewViewTests.cs @@ -485,6 +485,42 @@ public class DecompileInNewViewTests "the grid must visually highlight every restored node"); } + [AvaloniaTest] + public async Task DecompileInNewView_On_A_Metadata_Table_Opens_Its_Grid_Not_A_Code_Tab() + { + // "Decompile to new tab" on a node with custom content (a metadata table) must open that + // node's own page-type — the metadata grid — not force-decompile it into an empty code tab. + var window = AppComposition.Current.GetExport(); + window.Show(); + var vm = (MainWindowViewModel)window.DataContext!; + await vm.AssemblyTreeModel.WaitForAssembliesAsync(minimumCount: 1); + var pane = await window.WaitForComponent(); + var registry = AppComposition.Current.GetExport(); + + var tableNode = vm.AssemblyTreeModel.FindCoreLib() + .GetChild() + .GetChild() + .GetChild(); + + var documents = ((ILSpyDockFactory)vm.DockWorkspace.Factory).Documents!; + int before = documents.VisibleDockables?.OfType().Count() ?? 0; + + var menu = pane.BuildContextMenuForCurrentState(registry.Entries, rightClickedNode: tableNode); + menu.Should().NotBeNull(); + menu!.ClickItem(Resources.DecompileToNewPanel); + + await Waiters.WaitForAsync( + () => documents.VisibleDockables!.OfType() + .Any(t => t.Content is global::ILSpy.ViewModels.MetadataTablePageModel)); + + documents.VisibleDockables!.OfType().Count() + .Should().BeGreaterThan(before, "a new tab must open"); + var newTab = documents.VisibleDockables!.OfType() + .First(t => t.Content is global::ILSpy.ViewModels.MetadataTablePageModel); + newTab.Content.Should().BeOfType( + "the metadata-table node must open its grid page, not a decompiler tab"); + } + static bool RowNodeEquals(global::ILSpy.Controls.TreeView.SharpTreeViewItem row, SharpTreeNode node) => ReferenceEquals(row.DataContext, node); diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs index 5b1d62e2d..40eb7e466 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -61,13 +61,21 @@ namespace ILSpy.Commands public void Execute(TextViewContext context) { var nodes = SelectedNodes(context).ToArray(); - if (nodes.Length > 0) + if (nodes.Length == 1) { + // Route a single node through OpenNodeInNewTab so nodes with custom content + // (metadata tables, resource viewers) open their own page-type instead of being + // force-decompiled into an empty code tab. Plain nodes still get a decompiler tab, + // sourced from the node so the tab/tree stay in lockstep when flipping tabs. + dockWorkspace.OpenNodeInNewTab(nodes[0]); + return; + } + if (nodes.Length > 1) + { + // Multi-node selections leave SourceNode null — no single tree row represents the + // union — and always decompile (there is no custom-content union page). var content = new DecompilerTabPageModel { Language = languageService.CurrentLanguage }; - // Single-node selection carries a SourceNode so the tab/tree stay in lockstep - // when the user flips tabs. Multi-node selections leave SourceNode null — - // no single tree row represents the union. - dockWorkspace.OpenNewTab(content, sourceNode: nodes.Length == 1 ? nodes[0] : null); + dockWorkspace.OpenNewTab(content, sourceNode: null); content.CurrentNodes = nodes; return; }