From 9427f7661645d9f404e710406c80d6aad4424b66 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 28 May 2026 22:15:46 +0200 Subject: [PATCH] Re-group File menu so DEBUG items aren't mixed with assembly loading The File menu's MenuCategory metadata was inherited from the WPF tree and showed two friction points: the four DEBUG diagnostics (Decompile All / Disassemble All / Decompile 100x / Dump PDB) lived in the Open category, so they appeared right after Reload with no visual separator -- the menu read as if Decompile All was a variant of Open; and the assembly-list mutators (Remove with load errors, Clear assembly list, Manage assembly lists) were split across two categories (Remove and Open) even though they all operate on the same object. Reshape into five categories with non-overlapping MenuOrder ranges so category order is deterministic instead of falling out of MEF discovery order: Open (Open, GAC, Reload) MenuOrder 0 .. 9 AssemblyList (Manage, Remove, Clear) MenuOrder 10 .. 19 Save (Save Code, Generate PDB) MenuOrder 20 .. 29 Debug (DEBUG -- ...) MenuOrder 30 .. 39 Exit (Exit) MenuOrder 99999 The "AssemblyList" category replaces the previous "Remove" identifier (carrying Remove-with-load-errors + Clear) since both members and the newly-relocated Manage entry are list-of-assemblies operations rather than pure removal. The category value is a string literal because it is only ever used as a grouping key by ContextMenuProvider / MainMenu.AppendRegistryCommands -- never displayed. Stride-10 within each category gives room to insert later items without renumbering. The earlier 2.5 / 2.55 / 2.6 / 2.7 fractional scheme had no category-boundary contract: the existing 2.6 tie between Remove-with-load-errors and Clear meant their order between each other depended on whatever order reflection enumerated the fields in. Generate portable PDB previously had no MenuOrder at all so its position relative to Save Code was MEF-implementation defined. Assisted-by: Claude:claude-opus-4-7:Claude Code --- ILSpy/Commands/DecompileAllCommand.cs | 6 +++--- ILSpy/Commands/FileCommands.cs | 10 +++++----- ILSpy/Commands/Pdb2XmlCommand.cs | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index c20f01aaf..6a351bb60 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -43,7 +43,7 @@ namespace ILSpy.Commands /// timing in a results tab. Hard-coded output path matches WPF — the command's /// CanExecute gates on that directory existing so it's effectively opt-in. /// - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = "Debug", MenuOrder = 30)] [Shared] sealed class DecompileAllCommand : SimpleCommand { @@ -114,7 +114,7 @@ namespace ILSpy.Commands /// but routes through . /// CanExecute gates on the output directory existing. /// - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = nameof(Resources.Open), MenuOrder = 2.55)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDisassemble), MenuCategory = "Debug", MenuOrder = 31)] [Shared] sealed class DisassembleAllCommand : SimpleCommand { @@ -185,7 +185,7 @@ namespace ILSpy.Commands /// and report average wall-clock time. Used for tracking decompilation perf /// regressions across changes. /// - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile100x), MenuCategory = nameof(Resources.Open), MenuOrder = 2.6)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile100x), MenuCategory = "Debug", MenuOrder = 32)] [Shared] sealed class Decompile100TimesCommand : SimpleCommand { diff --git a/ILSpy/Commands/FileCommands.cs b/ILSpy/Commands/FileCommands.cs index 5d97ed07c..857d805b3 100644 --- a/ILSpy/Commands/FileCommands.cs +++ b/ILSpy/Commands/FileCommands.cs @@ -124,7 +124,7 @@ namespace ILSpy.Commands } } - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = nameof(Resources.Open), MenuOrder = 1.7)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ManageAssembly_Lists), MenuIcon = "Images/AssemblyList", MenuCategory = "AssemblyList", MenuOrder = 10)] [Shared] sealed class ManageAssemblyListsCommand : SimpleCommand { @@ -165,7 +165,7 @@ namespace ILSpy.Commands // DEBUG-only Pdb2XmlCommand moved to its own file with `#if DEBUG && WINDOWS` gating. // On non-Windows or non-Debug builds the entry simply isn't compiled. - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._RemoveAssembliesWithLoadErrors), MenuCategory = "AssemblyList", MenuOrder = 11)] [Shared] [method: ImportingConstructor] sealed class RemoveAssembliesWithLoadErrors(AssemblyTreeModel assemblyTreeModel) : SimpleCommand @@ -186,7 +186,7 @@ namespace ILSpy.Commands } } - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ClearAssemblyList), MenuCategory = nameof(Resources.Remove), MenuOrder = 2.6)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.ClearAssemblyList), MenuCategory = "AssemblyList", MenuOrder = 12)] [Shared] [method: ImportingConstructor] sealed class ClearAssemblyListCommand(AssemblyTreeModel assemblyTreeModel) : SimpleCommand @@ -197,7 +197,7 @@ namespace ILSpy.Commands public override void Execute(object? parameter) => assemblyTreeModel.AssemblyList?.Clear(); } - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._SaveCode), MenuIcon = "Images/Save", MenuCategory = nameof(Resources.Save), MenuOrder = 0, InputGestureText = "Ctrl+S")] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources._SaveCode), MenuIcon = "Images/Save", MenuCategory = nameof(Resources.Save), MenuOrder = 20, InputGestureText = "Ctrl+S")] [Shared] [method: ImportingConstructor] internal sealed class SaveCommand( @@ -275,7 +275,7 @@ namespace ILSpy.Commands } } - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save))] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.GeneratePortable), MenuCategory = nameof(Resources.Save), MenuOrder = 21)] [Shared] sealed class GeneratePdbCommand : SimpleCommand { diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index 090c2ae58..876cb1962 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -40,7 +40,7 @@ namespace ILSpy.Commands /// Microsoft.DiaSymReader uses native COM interop. WPF gates this command /// identically (#if DEBUG && WINDOWS). /// - [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = nameof(Resources.Open), MenuOrder = 2.7)] + [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDumpPDBAsXML), MenuCategory = "Debug", MenuOrder = 33)] [Shared] sealed class Pdb2XmlCommand : SimpleCommand {