From 0c1c5cecd7164d067ceb5884a2107700a126c3ef Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sun, 1 Sep 2024 08:07:58 +0200 Subject: [PATCH 01/14] Decouple AssemblyListPane from MainWindow --- ICSharpCode.ILSpyX/AssemblyList.cs | 5 +- ILSpy/Analyzers/AnalyzeCommand.cs | 4 +- ILSpy/Analyzers/AnalyzerEntityTreeNode.cs | 3 +- ILSpy/Analyzers/AnalyzerRootNode.cs | 1 - ILSpy/Analyzers/AnalyzerSearchTreeNode.cs | 2 +- ILSpy/Analyzers/AnalyzerTreeNode.cs | 1 - .../TreeNodes/AnalyzedModuleTreeNode.cs | 2 +- ILSpy/AssemblyTree/AssemblyListPane.xaml | 54 ++ ILSpy/AssemblyTree/AssemblyListPane.xaml.cs | 44 + ILSpy/AssemblyTree/AssemblyListPaneModel.cs | 896 ++++++++++++++++++ ILSpy/Commands/DecompileAllCommand.cs | 5 +- ILSpy/Commands/DecompileCommand.cs | 2 +- ILSpy/Commands/DecompileInNewViewCommand.cs | 17 +- ILSpy/Commands/DisassembleAllCommand.cs | 2 +- ILSpy/Commands/GeneratePdbContextMenuEntry.cs | 6 +- ILSpy/Commands/Pdb2XmlCommand.cs | 4 +- .../RemoveAssembliesWithLoadErrors.cs | 10 +- ILSpy/Commands/SaveCodeContextMenuEntry.cs | 1 - ILSpy/Commands/SelectPdbContextMenuEntry.cs | 6 +- ILSpy/Commands/SetThemeCommand.cs | 3 +- ILSpy/Commands/ShowCFGContextMenuEntry.cs | 1 - ILSpy/Commands/SortAssemblyListCommand.cs | 23 +- ILSpy/ContextMenuEntry.cs | 33 +- ILSpy/Controls/TreeView/SharpTreeView.cs | 1 + ILSpy/Docking/DockWorkspace.cs | 6 +- ILSpy/ExtensionMethods.cs | 1 - ILSpy/GlobalUsings.cs | 1 + ILSpy/LanguageSettings.cs | 1 - ILSpy/Languages/CSharpILMixedLanguage.cs | 1 - ILSpy/Languages/CSharpLanguage.cs | 7 +- ILSpy/Languages/ILLanguage.cs | 1 - ILSpy/MainWindow.xaml | 46 +- ILSpy/MainWindow.xaml.cs | 820 +--------------- .../CorTables/ClassLayoutTableTreeNode.cs | 2 +- .../CorTables/ConstantTableTreeNode.cs | 2 +- .../CorTables/CustomAttributeTableTreeNode.cs | 4 +- .../CorTables/DeclSecurityTableTreeNode.cs | 2 +- .../CorTables/EventMapTableTreeNode.cs | 4 +- .../Metadata/CorTables/EventTableTreeNode.cs | 2 +- .../CorTables/ExportedTypeTableTreeNode.cs | 2 +- .../CorTables/FieldLayoutTableTreeNode.cs | 2 +- .../CorTables/FieldMarshalTableTreeNode.cs | 2 +- .../CorTables/FieldRVATableTreeNode.cs | 2 +- .../GenericParamConstraintTableTreeNode.cs | 4 +- .../CorTables/GenericParamTableTreeNode.cs | 2 +- .../CorTables/ImplMapTableTreeNode.cs | 4 +- .../CorTables/InterfaceImplTableTreeNode.cs | 4 +- .../ManifestResourceTableTreeNode.cs | 2 +- .../CorTables/MemberRefTableTreeNode.cs | 2 +- .../CorTables/MethodImplTableTreeNode.cs | 6 +- .../CorTables/MethodSemanticsTableTreeNode.cs | 4 +- .../CorTables/MethodSpecTableTreeNode.cs | 2 +- .../Metadata/CorTables/MethodTableTreeNode.cs | 2 +- .../CorTables/NestedClassTableTreeNode.cs | 4 +- .../CorTables/PropertyMapTableTreeNode.cs | 4 +- .../CorTables/TypeDefTableTreeNode.cs | 6 +- .../CorTables/TypeRefTableTreeNode.cs | 2 +- ILSpy/Metadata/DebugMetadataTablesTreeNode.cs | 1 - .../CustomDebugInformationTableTreeNode.cs | 2 +- .../DebugTables/ImportScopeTableTreeNode.cs | 2 +- .../DebugTables/LocalScopeTableTreeNode.cs | 8 +- .../MethodDebugInformationTableTreeNode.cs | 4 +- .../StateMachineMethodTableTreeNode.cs | 4 +- ILSpy/Metadata/GoToTokenCommand.cs | 2 +- ILSpy/Metadata/MetadataProtocolHandler.cs | 2 +- ILSpy/Metadata/MetadataTablesTreeNode.cs | 1 - ILSpy/Options/DecompilerSettingsPanel.xaml.cs | 1 - ILSpy/Options/DisplaySettingsPanel.xaml.cs | 1 - ILSpy/Search/SearchPane.xaml.cs | 7 +- ILSpy/Search/SearchPaneModel.cs | 1 - ILSpy/SessionSettings.cs | 1 - ILSpy/TaskHelper.cs | 2 +- ILSpy/TextView/DecompilerTextView.cs | 8 +- ILSpy/TextView/DocumentationUIBuilder.cs | 5 +- ILSpy/Themes/WindowStyleManagerBehavior.cs | 1 - ILSpy/TreeNodes/AssemblyListTreeNode.cs | 18 +- ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs | 1 - ILSpy/TreeNodes/AssemblyTreeNode.cs | 12 +- ILSpy/TreeNodes/ILSpyTreeNode.cs | 5 +- ILSpy/Util/MessageBus.cs | 9 + ILSpy/Util/NavigationHistoryService.cs | 48 + ILSpy/ViewModels/AssemblyListPaneModel.cs | 41 - .../ManageAssemblyListsViewModel.cs | 1 - ILSpy/ViewModels/PaneModel.cs | 14 +- ILSpy/ViewModels/TabPageModel.cs | 2 - ILSpy/Views/DebugSteps.xaml.cs | 9 +- TestPlugin/MainMenuCommand.cs | 2 +- 87 files changed, 1248 insertions(+), 1049 deletions(-) create mode 100644 ILSpy/AssemblyTree/AssemblyListPane.xaml create mode 100644 ILSpy/AssemblyTree/AssemblyListPane.xaml.cs create mode 100644 ILSpy/AssemblyTree/AssemblyListPaneModel.cs create mode 100644 ILSpy/GlobalUsings.cs create mode 100644 ILSpy/Util/NavigationHistoryService.cs delete mode 100644 ILSpy/ViewModels/AssemblyListPaneModel.cs diff --git a/ICSharpCode.ILSpyX/AssemblyList.cs b/ICSharpCode.ILSpyX/AssemblyList.cs index b52e791173..ec10a4e92e 100644 --- a/ICSharpCode.ILSpyX/AssemblyList.cs +++ b/ICSharpCode.ILSpyX/AssemblyList.cs @@ -281,8 +281,9 @@ public LoadedAssembly OpenAssembly(string file, bool isAutoLoaded = false) { file = Path.GetFullPath(file); return OpenAssembly(file, () => { - var newAsm = new LoadedAssembly(this, file, fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols); - newAsm.IsAutoLoaded = isAutoLoaded; + var newAsm = new LoadedAssembly(this, file, fileLoaders: manager?.LoaderRegistry, applyWinRTProjections: ApplyWinRTProjections, useDebugSymbols: UseDebugSymbols) { + IsAutoLoaded = isAutoLoaded + }; return newAsm; }); } diff --git a/ILSpy/Analyzers/AnalyzeCommand.cs b/ILSpy/Analyzers/AnalyzeCommand.cs index 889e6afa0b..9a24f29b24 100644 --- a/ILSpy/Analyzers/AnalyzeCommand.cs +++ b/ILSpy/Analyzers/AnalyzeCommand.cs @@ -78,12 +78,12 @@ internal sealed class AnalyzeCommand : SimpleCommand public override bool CanExecute(object parameter) { - return MainWindow.Instance.SelectedNodes.All(n => n is IMemberTreeNode); + return MainWindow.Instance.AssemblyTreeModel.SelectedNodes.All(n => n is IMemberTreeNode); } public override void Execute(object parameter) { - foreach (var node in MainWindow.Instance.SelectedNodes.OfType()) + foreach (var node in MainWindow.Instance.AssemblyTreeModel.SelectedNodes.OfType()) { AnalyzerTreeView.Analyze(node.Member); } diff --git a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs index 213624ee1a..4217e411ca 100644 --- a/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerEntityTreeNode.cs @@ -42,7 +42,8 @@ public override void ActivateItem(IPlatformRoutedEventArgs e) MessageBox.Show(Properties.Resources.CannotAnalyzeMissingRef, "ILSpy"); return; } - MainWindow.Instance.JumpToReference(new EntityReference(this.Member.ParentModule.MetadataFile, this.Member.MetadataToken)); + + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(this.Member.ParentModule?.MetadataFile, this.Member.MetadataToken))); } public override bool HandleAssemblyListChanged(ICollection removedAssemblies, ICollection addedAssemblies) diff --git a/ILSpy/Analyzers/AnalyzerRootNode.cs b/ILSpy/Analyzers/AnalyzerRootNode.cs index 17709687f9..e1ce034389 100644 --- a/ILSpy/Analyzers/AnalyzerRootNode.cs +++ b/ILSpy/Analyzers/AnalyzerRootNode.cs @@ -2,7 +2,6 @@ using System.Collections.Specialized; using System.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; diff --git a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs index 0a7bbb95cb..e0ade86662 100644 --- a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs @@ -61,7 +61,7 @@ protected IEnumerable FetchChildren(CancellationToken ct) var context = new AnalyzerContext() { CancellationToken = ct, Language = Language, - AssemblyList = MainWindow.Instance.CurrentAssemblyList + AssemblyList = MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList }; var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory); if (context.SortResults) diff --git a/ILSpy/Analyzers/AnalyzerTreeNode.cs b/ILSpy/Analyzers/AnalyzerTreeNode.cs index 8972926a73..fb052303ee 100644 --- a/ILSpy/Analyzers/AnalyzerTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerTreeNode.cs @@ -18,7 +18,6 @@ using System.Collections.Generic; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; diff --git a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs index e7f83f6385..7f343fb785 100644 --- a/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs +++ b/ILSpy/Analyzers/TreeNodes/AnalyzedModuleTreeNode.cs @@ -61,7 +61,7 @@ public override void ActivateItem(IPlatformRoutedEventArgs e) MessageBox.Show(Properties.Resources.CannotAnalyzeMissingRef, "ILSpy"); return; } - MainWindow.Instance.JumpToReference(analyzedModule.MetadataFile); + MessageBus.Send(this, new NavigateToReferenceEventArgs(analyzedModule.MetadataFile)); } public override IEntity Member => null; diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml b/ILSpy/AssemblyTree/AssemblyListPane.xaml new file mode 100644 index 0000000000..8739412a89 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml @@ -0,0 +1,54 @@ + + + + + diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs new file mode 100644 index 0000000000..4e4ad88885 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs @@ -0,0 +1,44 @@ +using System.ComponentModel.Composition; +using System.Windows; + +using ICSharpCode.ILSpyX.TreeView; + +using TomsToolbox.Wpf.Composition.Mef; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + /// + /// Interaction logic for AssemblyListPane.xaml + /// + [DataTemplate(typeof(AssemblyListPaneModel))] + [PartCreationPolicy(CreationPolicy.NonShared)] + public partial class AssemblyListPane + { + public AssemblyListPane() + { + InitializeComponent(); + + ContextMenuProvider.Add(this); + } + + protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) + { + base.OnPropertyChanged(e); + + if (e.Property == DataContextProperty) + { + if (e.NewValue is not AssemblyListPaneModel model) + return; + + model.SetActiveView(this); + } + else if (e.Property == SelectedItemProperty) + { + if (e.NewValue is not SharpTreeNode treeNode) + return; + + FocusNode(treeNode); + } + } + } +} diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs new file mode 100644 index 0000000000..cda948a4cd --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs @@ -0,0 +1,896 @@ +// Copyright (c) 2019 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; +using System.Collections.ObjectModel; +using System.ComponentModel.Composition; +using System.IO; +using System.Threading.Tasks; +using System.Windows.Input; +using System.Windows.Threading; + +using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; +using ICSharpCode.ILSpy.TreeNodes; +using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX; +using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpyX.TreeView; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +using ICSharpCode.Decompiler.Metadata; + +using System.Reflection.Metadata.Ecma335; +using System.Windows; +using ICSharpCode.Decompiler.Documentation; +using ICSharpCode.Decompiler.TypeSystem.Implementation; +using System.Reflection.Metadata; + +using ICSharpCode.ILSpyX.Extensions; +using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.Search; +using ICSharpCode.Decompiler; +using System.Text; + +using TomsToolbox.Wpf; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + [ExportToolPane] + [PartCreationPolicy(CreationPolicy.Shared)] + [Export] + public class AssemblyListPaneModel : ToolPaneModel + { + public const string PaneContentId = "assemblyListPane"; + + AssemblyListPane activeView; + AssemblyList assemblyList; + AssemblyListTreeNode assemblyListTreeNode; + + bool refreshInProgress; + bool changingActiveTab; + + readonly NavigationHistoryService history = NavigationHistoryService.Instance; + + public AssemblyListPaneModel() + { + Title = Resources.Assemblies; + ContentId = PaneContentId; + IsCloseable = false; + ShortcutKey = new KeyGesture(Key.F6); + + MessageBus.Subscribers += JumpToReference; + MessageBus.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); + MessageBus.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); + + var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Background, TreeView_SelectionChanged); + SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); + } + + private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + var sessionSettings = SettingsService.Instance.SessionSettings; + + switch (e.PropertyName) + { + case nameof(SessionSettings.ActiveAssemblyList): + ShowAssemblyList(sessionSettings.ActiveAssemblyList); + break; + case nameof(SessionSettings.Theme): + // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) + DecompilerTextView.RegisterHighlighting(); + DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); + break; + case nameof(SessionSettings.CurrentCulture): + MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); + break; + } + } + + public AssemblyList CurrentAssemblyList { + get { return assemblyList; } + } + + private SharpTreeNode root; + public SharpTreeNode Root { + get => root; + set => SetProperty(ref root, value); + } + + private SharpTreeNode selectedItem; + public SharpTreeNode SelectedItem { + get => selectedItem; + set => SetProperty(ref selectedItem, value); + } + + public ObservableCollection SelectedItems { get; } = []; + + public string[] SelectedPath => GetPathForNode(SelectedItem); + + readonly List commandLineLoadedAssemblies = []; + + public bool HandleCommandLineArguments(CommandLineArguments args) + { + LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); + if (args.Language != null) + SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); + return true; + } + + /// + /// Called on startup or when passed arguments via WndProc from a second instance. + /// In the format case, spySettings is non-null; in the latter it is null. + /// + public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings spySettings = null) + { + var sessionSettings = SettingsService.Instance.SessionSettings; + + var relevantAssemblies = commandLineLoadedAssemblies.ToList(); + commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore + NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); + if (args.Search != null) + { + var searchPane = App.ExportProvider.GetExportedValue(); + + searchPane.SearchTerm = args.Search; + searchPane.Show(); + } + } + + public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings spySettings, List relevantAssemblies) + { + var initialSelection = SelectedItem; + if (navigateTo != null) + { + bool found = false; + if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) + { + string namespaceName = navigateTo.Substring(2); + foreach (LoadedAssembly asm in relevantAssemblies) + { + AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(asm); + if (asmNode != null) + { + // FindNamespaceNode() blocks the UI if the assembly is not yet loaded, + // so use an async wait instead. + await asm.GetMetadataFileAsync().Catch(ex => { }); + NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); + if (nsNode != null) + { + found = true; + if (SelectedItem == initialSelection) + { + SelectNode(nsNode); + } + break; + } + } + } + } + else if (navigateTo == "none") + { + // Don't navigate anywhere; start empty. + // Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC. + found = true; + } + else + { + IEntity mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); + // Make sure we wait for assemblies being loaded... + // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal + await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); + if (mr != null && mr.ParentModule?.MetadataFile != null) + { + found = true; + if (SelectedItem == initialSelection) + { + await JumpToReferenceAsync(mr); + } + } + } + if (!found && SelectedItem == initialSelection) + { + AvalonEditTextOutput output = new AvalonEditTextOutput(); + output.Write($"Cannot find '{navigateTo}' in command line specified assemblies."); + DockWorkspace.Instance.ShowText(output); + } + } + else if (relevantAssemblies.Count == 1) + { + // NavigateTo == null and an assembly was given on the command-line: + // Select the newly loaded assembly + AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(relevantAssemblies[0]); + if (asmNode != null && SelectedItem == initialSelection) + { + SelectNode(asmNode); + } + } + else if (spySettings != null) + { + SharpTreeNode node = null; + if (activeTreeViewPath?.Length > 0) + { + foreach (var asm in CurrentAssemblyList.GetAssemblies()) + { + if (asm.FileName == activeTreeViewPath[0]) + { + // FindNodeByPath() blocks the UI if the assembly is not yet loaded, + // so use an async wait instead. + await asm.GetMetadataFileAsync().Catch(ex => { }); + } + } + node = FindNodeByPath(activeTreeViewPath, true); + } + if (SelectedItem == initialSelection) + { + if (node != null) + { + SelectNode(node); + + // only if not showing the about page, perform the update check: + await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(spySettings); + } + else + { + DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); + } + } + } + } + + public static IEntity FindEntityInRelevantAssemblies(string navigateTo, IEnumerable relevantAssemblies) + { + ITypeReference typeRef; + IMemberReference memberRef = null; + if (navigateTo.StartsWith("T:", StringComparison.Ordinal)) + { + typeRef = IdStringProvider.ParseTypeName(navigateTo); + } + else + { + memberRef = IdStringProvider.ParseMemberIdString(navigateTo); + typeRef = memberRef.DeclaringTypeReference; + } + foreach (LoadedAssembly asm in relevantAssemblies.ToList()) + { + var module = asm.GetMetadataFileOrNull(); + if (CanResolveTypeInPEFile(module, typeRef, out var typeHandle)) + { + ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType + ? new DecompilerTypeSystem(module, module.GetAssemblyResolver()) + : new SimpleCompilation((PEFile)module, MinimalCorlib.Instance); + return memberRef == null + ? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition + : memberRef.Resolve(new SimpleTypeResolveContext(compilation)); + } + } + return null; + } + + static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle) + { + // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. + if (module.IsReferenceAssembly()) + { + typeHandle = default; + return false; + } + + switch (typeRef) + { + case GetPotentiallyNestedClassTypeReference topLevelType: + typeHandle = topLevelType.ResolveInPEFile(module); + return !typeHandle.IsNil; + case NestedTypeReference nestedType: + if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle)) + return false; + if (typeHandle.Kind == HandleKind.ExportedType) + return true; + var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle); + typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => { + var td = module.Metadata.GetTypeDefinition(t); + var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount); + return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName; + }); + return !typeHandle.IsNil; + default: + typeHandle = default; + return false; + } + } + + public void Initialize() + { + var loadPreviousAssemblies = Options.MiscSettingsPanel.CurrentMiscSettings.LoadPreviousAssemblies; + + var sessionSettings = SettingsService.Instance.SessionSettings; + + if (loadPreviousAssemblies) + { + this.assemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); + } + else + { + SettingsService.Instance.AssemblyListManager.ClearAll(); + this.assemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); + } + + HandleCommandLineArguments(App.CommandLineArguments); + + if (assemblyList.GetAssemblies().Length == 0 + && assemblyList.ListName == AssemblyListManager.DefaultListName + && loadPreviousAssemblies) + { + LoadInitialAssemblies(); + } + + ShowAssemblyList(this.assemblyList); + + if (sessionSettings.ActiveAutoLoadedAssembly != null + && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) + { + this.assemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); + } + + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => OpenAssemblies(SettingsService.Instance.SpySettings))); + } + + void OpenAssemblies(ILSpySettings spySettings) + { + HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, spySettings); + + AvalonEditTextOutput output = new(); + if (FormatExceptions(App.StartupExceptions.ToArray(), output)) + DockWorkspace.Instance.ShowText(output); + } + + static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) + { + var stringBuilder = new StringBuilder(); + var result = exceptions.FormatExceptions(stringBuilder); + if (result) + { + output.Write(stringBuilder.ToString()); + } + return result; + } + + public void ShowAssemblyList(string name) + { + AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); + //Only load a new list when it is a different one + if (list.ListName != CurrentAssemblyList.ListName) + { + ShowAssemblyList(list); + SelectNode(Root); + } + } + + void ShowAssemblyList(AssemblyList assemblyList) + { + history.Clear(); + if (this.assemblyList != null) + { + this.assemblyList.CollectionChanged -= assemblyList_CollectionChanged; + } + + this.assemblyList = assemblyList; + + assemblyList.CollectionChanged += assemblyList_CollectionChanged; + + assemblyListTreeNode = new(assemblyList) { + Select = x => SelectNode(x) + }; + + Root = assemblyListTreeNode; + + if (assemblyList.ListName == AssemblyListManager.DefaultListName) +#if DEBUG + this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; +#else + this.Title = "ILSpy"; +#endif + else +#if DEBUG + this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; +#else + this.Title = "ILSpy - " + assemblyList.ListName; +#endif + } + + void assemblyList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + if (e.Action == NotifyCollectionChangedAction.Reset) + { + history.RemoveAll(_ => true); + } + if (e.OldItems != null) + { + var oldAssemblies = new HashSet(e.OldItems.Cast()); + history.RemoveAll(n => n.TreeNodes.Any( + nd => nd.AncestorsAndSelf().OfType().Any( + a => oldAssemblies.Contains(a.LoadedAssembly)))); + } + + MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); + } + + void LoadInitialAssemblies() + { + // Called when loading an empty assembly list; so that + // the user can see something initially. + System.Reflection.Assembly[] initialAssemblies = { + typeof(object).Assembly, + typeof(Uri).Assembly, + typeof(System.Linq.Enumerable).Assembly, + typeof(System.Xml.XmlDocument).Assembly, + typeof(System.Windows.Markup.MarkupExtension).Assembly, + typeof(System.Windows.Rect).Assembly, + typeof(System.Windows.UIElement).Assembly, + typeof(System.Windows.FrameworkElement).Assembly + }; + foreach (System.Reflection.Assembly asm in initialAssemblies) + assemblyList.OpenAssembly(asm.Location); + } + + void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") + { + DecompileSelectedNodes(recordHistory: false); + } + } + + public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) + { + return assemblyListTreeNode.FindAssemblyNode(asm); + } + + #region Node Selection + + public void SelectNode(SharpTreeNode node, bool inNewTabPage = false) + { + if (node == null) + return; + + if (node.AncestorsAndSelf().Any(item => item.IsHidden)) + { + MessageBox.Show(Properties.Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); + return; + } + + if (inNewTabPage) + { + DockWorkspace.Instance.TabPages.Add(); + SelectedItem = null; + } + + if (SelectedItem == node) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + } + else + { + activeView?.ScrollIntoView(node); + SelectedItem = node; + } + } + + internal void SelectNodes(IEnumerable nodes, bool inNewTabPage = false, bool setFocus = true, bool changingTab = false, bool ignoreCompilationRequests = false) + { + this.ignoreDecompilationRequests = ignoreCompilationRequests; + + try + { + + if (inNewTabPage) + { + DockWorkspace.Instance.TabPages.Add(); + } + + // Ensure nodes exist + var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) + .Where(n => n != null) + .ToArray(); + + if (!nodesList.Any() || nodesList.Any(n => n.AncestorsAndSelf().Any(a => a.IsHidden))) + { + return; + } + + this.changingActiveTab = changingTab || inNewTabPage; + + var currentFocused = Keyboard.FocusedElement; + + try + { + if (SelectedItems.SequenceEqual(nodesList)) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + return; + } + + this.SelectedItems.Clear(); + this.SelectedItems.AddRange(nodesList); + } + finally + { + if (!setFocus) + currentFocused.Focus(); + + this.changingActiveTab = false; + } + } + finally + { + this.ignoreDecompilationRequests = false; + } + + } + + /// + /// Retrieves a node using the .ToString() representations of its ancestors. + /// + public SharpTreeNode FindNodeByPath(string[] path, bool returnBestMatch) + { + if (path == null) + return null; + SharpTreeNode node = Root; + SharpTreeNode bestMatch = node; + foreach (var element in path) + { + if (node == null) + break; + bestMatch = node; + node.EnsureLazyChildren(); + if (node is ILSpyTreeNode ilSpyTreeNode) + ilSpyTreeNode.EnsureChildrenFiltered(); + node = node.Children.FirstOrDefault(c => c.ToString() == element); + } + if (returnBestMatch) + return node ?? bestMatch; + else + return node; + } + + /// + /// Gets the .ToString() representation of the node's ancestors. + /// + public static string[] GetPathForNode(SharpTreeNode node) + { + if (node == null) + return null; + List path = new List(); + while (node.Parent != null) + { + path.Add(node.ToString()); + node = node.Parent; + } + path.Reverse(); + return path.ToArray(); + } + + public ILSpyTreeNode FindTreeNode(object reference) + { + switch (reference) + { + case LoadedAssembly lasm: + return assemblyListTreeNode.FindAssemblyNode(lasm); + case MetadataFile asm: + return assemblyListTreeNode.FindAssemblyNode(asm); + case Resource res: + return assemblyListTreeNode.FindResourceNode(res); + case ValueTuple resName: + return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2); + case ITypeDefinition type: + return assemblyListTreeNode.FindTypeNode(type); + case IField fd: + return assemblyListTreeNode.FindFieldNode(fd); + case IMethod md: + return assemblyListTreeNode.FindMethodNode(md); + case IProperty pd: + return assemblyListTreeNode.FindPropertyNode(pd); + case IEvent ed: + return assemblyListTreeNode.FindEventNode(ed); + case INamespace nd: + return assemblyListTreeNode.FindNamespaceNode(nd); + default: + return null; + } + } + + private void JumpToReference(object sender, NavigateToReferenceEventArgs e) + { + JumpToReferenceAsync(e.Reference, e.InNewTabPage).HandleExceptions(); + } + + /// + /// Jumps to the specified reference. + /// + /// + /// Returns a task that will signal completion when the decompilation of the jump target has finished. + /// The task will be marked as canceled if the decompilation is canceled. + /// + private Task JumpToReferenceAsync(object reference, bool inNewTabPage = false) + { + var decompilationTask = Task.CompletedTask; + + switch (reference) + { + case Decompiler.Disassembler.OpCodeInfo opCode: + MainWindow.OpenLink(opCode.Link); + break; + case EntityReference unresolvedEntity: + string protocol = unresolvedEntity.Protocol; + var file = unresolvedEntity.ResolveAssembly(assemblyList); + if (file == null) + { + break; + } + if (protocol != "decompile") + { + var protocolHandlers = App.ExportProvider.GetExportedValues(); + foreach (var handler in protocolHandlers) + { + var node = handler.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); + if (node != null) + { + SelectNode(node, newTabPage || inNewTabPage); + return decompilationTask; + } + } + } + var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)); + if (possibleToken != null) + { + var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); + reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value); + goto default; + } + break; + default: + ILSpyTreeNode treeNode = FindTreeNode(reference); + if (treeNode != null) + SelectNode(treeNode, inNewTabPage); + break; + } + return decompilationTask; + } + + #endregion + + public void LoadAssemblies(IEnumerable fileNames, List loadedAssemblies = null, bool focusNode = true) + { + var currentFocus = Keyboard.FocusedElement; + AssemblyTreeNode lastNode = null; + + foreach (string file in fileNames) + { + var assembly = assemblyList.OpenAssembly(file); + + if (loadedAssemblies != null) + { + loadedAssemblies.Add(assembly); + } + else + { + var node = assemblyListTreeNode.FindAssemblyNode(assembly); + if (node != null && focusNode) + { + lastNode = node; + SelectedItems.Add(node); + } + } + } + + if (!focusNode) + { + currentFocus?.Focus(); + } + else if (lastNode != null) + { + activeView?.FocusNode(lastNode); + } + } + + #region Decompile (TreeView_SelectionChanged) + + void TreeView_SelectionChanged() + { + DecompilerTextViewState state = null; + + // These are probably no longer needed and can be removed! + Debug.Assert(!refreshInProgress); + Debug.Assert(!changingActiveTab); + + if (refreshInProgress || changingActiveTab) + { + state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; + } + + var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; + + if (!changingActiveTab && !delayDecompilationRequestDueToContextMenu) + { + DecompileSelectedNodes(state); + } + else + { + ContextMenuProvider.ContextMenuClosed += ContextMenuClosed; + } + + MessageBus.Send(this, new AssemblyTreeSelectionChangedEventArgs()); + + return; + + void ContextMenuClosed(object sender, EventArgs e) + { + ContextMenuProvider.ContextMenuClosed -= ContextMenuClosed; + + Dispatcher.BeginInvoke(DispatcherPriority.Background, () => { + if (Mouse.RightButton != MouseButtonState.Pressed) + { + DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); + } + }); + } + } + + private bool ignoreDecompilationRequests; + + public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool recordHistory = true) + { + if (ignoreDecompilationRequests) + return; + + if (SelectedItems.Count == 0 && refreshInProgress) + return; + + if (recordHistory) + { + var tabPage = DockWorkspace.Instance.ActiveTabPage; + var currentState = tabPage.GetState(); + if (currentState != null) + history.UpdateCurrent(new NavigationState(tabPage, currentState)); + history.Record(new NavigationState(tabPage, SelectedItems)); + } + + DockWorkspace.Instance.ActiveTabPage.SupportsLanguageSwitching = true; + + if (SelectedItems.Count == 1) + { + if (SelectedItem is ILSpyTreeNode node && node.View(DockWorkspace.Instance.ActiveTabPage)) + return; + } + if (newState?.ViewedUri != null) + { + MainWindow.Instance.NavigateTo(new(newState.ViewedUri, null), recordHistory: false); + return; + } + var options = MainWindow.Instance.CreateDecompilationOptions(); + options.TextViewState = newState; + DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); + } + + public void RefreshDecompiledView() + { + try + { + refreshInProgress = true; + DecompileSelectedNodes(); + } + finally + { + refreshInProgress = false; + } + } + + public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; + + public LanguageVersion CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; + + public IEnumerable SelectedNodes { + get { + return GetTopLevelSelection().OfType(); + } + } + + #endregion + + + public void Refresh() + { + var currentFocus = Keyboard.FocusedElement; + + try + { + refreshInProgress = true; + var path = GetPathForNode(SelectedItem); + ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(assemblyList.ListName)); + SelectNode(FindNodeByPath(path, true), inNewTabPage: false); + } + finally + { + refreshInProgress = false; + currentFocus?.Focus(); + } + } + + public void UnselectAll(bool ignoreCompilationRequests = false) + { + this.ignoreDecompilationRequests = ignoreCompilationRequests; + SelectedItems.Clear(); + this.ignoreDecompilationRequests = false; + } + + public IEnumerable GetTopLevelSelection() + { + var selection = this.SelectedItems; + var selectionHash = new HashSet(selection); + + return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); + } + + public void SetActiveView(AssemblyListPane activeView) + { + this.activeView = activeView; + } + + public void SortAssemblyList() + { + using (activeView?.LockUpdates()) + { + CurrentAssemblyList.Sort(AssemblyComparer.Instance); + } + } + + private class AssemblyComparer : IComparer + { + public static readonly AssemblyComparer Instance = new(); + int IComparer.Compare(LoadedAssembly x, LoadedAssembly y) + { + return string.Compare(x?.ShortName, y?.ShortName, StringComparison.CurrentCulture); + } + } + + public void CollapseAll() + { + using (activeView.LockUpdates()) + { + CollapseChildren(Root); + } + } + + static void CollapseChildren(SharpTreeNode node) + { + foreach (var child in node.Children) + { + if (!child.IsExpanded) + continue; + + CollapseChildren(child); + child.IsExpanded = false; + } + } + } +} diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index c905c9a87b..3a41480582 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -28,7 +28,6 @@ using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using TomsToolbox.Essentials; @@ -49,7 +48,7 @@ public override void Execute(object parameter) Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies(), loadBalance: true), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate (LoadedAssembly asm) { if (!asm.HasLoadError) @@ -96,7 +95,7 @@ public override void Execute(object parameter) { const int numRuns = 100; var language = SettingsService.Instance.SessionSettings.LanguageSettings.Language; - var nodes = MainWindow.Instance.SelectedNodes.ToArray(); + var nodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes.ToArray(); var options = MainWindow.Instance.CreateDecompilationOptions(); Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { options.CancellationToken = ct; diff --git a/ILSpy/Commands/DecompileCommand.cs b/ILSpy/Commands/DecompileCommand.cs index 0a7d1a3857..ec35a0cbee 100644 --- a/ILSpy/Commands/DecompileCommand.cs +++ b/ILSpy/Commands/DecompileCommand.cs @@ -66,7 +66,7 @@ public void Execute(TextViewContext context) selection = entity; } if (selection != null) - MainWindow.Instance.JumpToReference(selection); + MessageBus.Send(this, new NavigateToReferenceEventArgs(selection)); } } } diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs index 07e9bebcbe..0ecc50bf83 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -23,11 +23,11 @@ using System.Windows.Threading; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Commands { [ExportContextMenuEntry(Header = nameof(Resources.DecompileToNewPanel), InputGestureText = "MMB", Icon = "images/Search", Category = nameof(Resources.Analyze), Order = 90)] @@ -53,18 +53,18 @@ IEnumerable GetNodes(TextViewContext context) { if (context.SelectedTreeNodes != null) { - if (context.TreeView != MainWindow.Instance.AssemblyTreeView) + if (context.TreeView.DataContext != MainWindow.Instance.AssemblyTreeModel) { - return context.SelectedTreeNodes.OfType().Select(FindTreeNode).Where(n => n != null); + return context.SelectedTreeNodes.OfType().Select(FindTreeNode).ExceptNullItems(); } else { - return context.SelectedTreeNodes.OfType().Where(n => n != null); + return context.SelectedTreeNodes.OfType(); } } else if (context.Reference?.Reference is IEntity entity) { - if (MainWindow.Instance.FindTreeNode(entity) is ILSpyTreeNode node) + if (MainWindow.Instance.AssemblyTreeModel.FindTreeNode(entity) is { } node) { return new[] { node }; } @@ -75,7 +75,7 @@ ILSpyTreeNode FindTreeNode(IMemberTreeNode node) { if (node is ILSpyTreeNode ilspyNode) return ilspyNode; - return MainWindow.Instance.FindTreeNode(node.Member); + return MainWindow.Instance.AssemblyTreeModel.FindTreeNode(node.Member); } } @@ -84,8 +84,7 @@ static void DecompileNodes(ILSpyTreeNode[] nodes) if (nodes.Length == 0) return; - MainWindow.Instance.SelectNodes(nodes, inNewTabPage: true); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes, inNewTabPage: true); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index fc44d405a3..aa81bd855e 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -44,7 +44,7 @@ public override void Execute(object parameter) Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies(), loadBalance: true), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate (LoadedAssembly asm) { if (!asm.HasLoadError) diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index b7c3997d06..fff3a9fb32 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -110,14 +110,14 @@ class GeneratePdbMainMenuEntry : SimpleCommand { public override bool CanExecute(object parameter) { - return MainWindow.Instance.SelectedNodes?.Count() == 1 - && MainWindow.Instance.SelectedNodes?.FirstOrDefault() is AssemblyTreeNode tn + return MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.Count() == 1 + && MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.FirstOrDefault() is AssemblyTreeNode tn && !tn.LoadedAssembly.HasLoadError; } public override void Execute(object parameter) { - var assembly = (MainWindow.Instance.SelectedNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly; + var assembly = (MainWindow.Instance.AssemblyTreeModel.SelectedNodes?.FirstOrDefault() as AssemblyTreeNode)?.LoadedAssembly; if (assembly == null) return; GeneratePdbContextMenuEntry.GeneratePdbForAssembly(assembly); diff --git a/ILSpy/Commands/Pdb2XmlCommand.cs b/ILSpy/Commands/Pdb2XmlCommand.cs index c697773704..b624124cbb 100644 --- a/ILSpy/Commands/Pdb2XmlCommand.cs +++ b/ILSpy/Commands/Pdb2XmlCommand.cs @@ -40,14 +40,14 @@ sealed class Pdb2XmlCommand : SimpleCommand { public override bool CanExecute(object parameter) { - var selectedNodes = MainWindow.Instance.SelectedNodes; + var selectedNodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes; return selectedNodes?.Any() == true && selectedNodes.All(n => n is AssemblyTreeNode asm && !asm.LoadedAssembly.HasLoadError); } public override void Execute(object parameter) { - Execute(MainWindow.Instance.SelectedNodes.OfType()); + Execute(MainWindow.Instance.AssemblyTreeModel.SelectedNodes.OfType()); } internal static void Execute(IEnumerable nodes) diff --git a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs index c8696104bd..73d9769a75 100644 --- a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs +++ b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs @@ -29,16 +29,16 @@ class RemoveAssembliesWithLoadErrors : SimpleCommand { public override bool CanExecute(object parameter) { - return MainWindow.Instance.CurrentAssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; + return MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; } public override void Execute(object parameter) { - foreach (var asm in MainWindow.Instance.CurrentAssemblyList.GetAssemblies()) + foreach (var asm in MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()) { if (!asm.HasLoadError) continue; - var node = MainWindow.Instance.AssemblyListTreeNode.FindAssemblyNode(asm); + var node = MainWindow.Instance.AssemblyTreeModel.FindAssemblyNode(asm); if (node != null && node.CanDelete()) node.Delete(); } @@ -51,12 +51,12 @@ class ClearAssemblyList : SimpleCommand { public override bool CanExecute(object parameter) { - return MainWindow.Instance.CurrentAssemblyList?.Count > 0; + return MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.Count > 0; } public override void Execute(object parameter) { - MainWindow.Instance.CurrentAssemblyList?.Clear(); + MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.Clear(); } } } diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index d7e38ada3d..9b013daad3 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -32,7 +32,6 @@ using ICSharpCode.ILSpyX.TreeView; using System.ComponentModel.Composition; -using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.TextView { diff --git a/ILSpy/Commands/SelectPdbContextMenuEntry.cs b/ILSpy/Commands/SelectPdbContextMenuEntry.cs index 061ff7e6b0..db4fa73733 100644 --- a/ILSpy/Commands/SelectPdbContextMenuEntry.cs +++ b/ILSpy/Commands/SelectPdbContextMenuEntry.cs @@ -48,10 +48,10 @@ public async void Execute(TextViewContext context) await assembly.LoadDebugInfo(dlg.FileName); } - var node = (AssemblyTreeNode)MainWindow.Instance.FindNodeByPath(new[] { assembly.FileName }, true); + var node = (AssemblyTreeNode)MainWindow.Instance.AssemblyTreeModel.FindNodeByPath(new[] { assembly.FileName }, true); node.UpdateToolTip(); - MainWindow.Instance.SelectNode(node); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.SelectNode(node); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } public bool IsEnabled(TextViewContext context) => true; diff --git a/ILSpy/Commands/SetThemeCommand.cs b/ILSpy/Commands/SetThemeCommand.cs index 2d1a0b32ac..7f10c21f76 100644 --- a/ILSpy/Commands/SetThemeCommand.cs +++ b/ILSpy/Commands/SetThemeCommand.cs @@ -1,5 +1,4 @@ -using ICSharpCode.ILSpy.Util; - + namespace ICSharpCode.ILSpy.Commands { public class SetThemeCommand : SimpleCommand diff --git a/ILSpy/Commands/ShowCFGContextMenuEntry.cs b/ILSpy/Commands/ShowCFGContextMenuEntry.cs index b4e8be003e..2067752cc0 100644 --- a/ILSpy/Commands/ShowCFGContextMenuEntry.cs +++ b/ILSpy/Commands/ShowCFGContextMenuEntry.cs @@ -6,7 +6,6 @@ using ICSharpCode.Decompiler.FlowAnalysis; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.ControlFlow; -using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.Commands { diff --git a/ILSpy/Commands/SortAssemblyListCommand.cs b/ILSpy/Commands/SortAssemblyListCommand.cs index 7621778a77..5b7dc67e1a 100644 --- a/ILSpy/Commands/SortAssemblyListCommand.cs +++ b/ILSpy/Commands/SortAssemblyListCommand.cs @@ -29,17 +29,11 @@ namespace ICSharpCode.ILSpy [ExportMainMenuCommand(ParentMenuID = nameof(Resources._View), Header = nameof(Resources.SortAssembly_listName), MenuIcon = "Images/Sort", MenuCategory = nameof(Resources.View))] [ExportToolbarCommand(ToolTip = nameof(Resources.SortAssemblyListName), ToolbarIcon = "Images/Sort", ToolbarCategory = nameof(Resources.View))] [PartCreationPolicy(CreationPolicy.Shared)] - sealed class SortAssemblyListCommand : SimpleCommand, IComparer + sealed class SortAssemblyListCommand : SimpleCommand { public override void Execute(object parameter) { - using (MainWindow.Instance.AssemblyTreeView.LockUpdates()) - MainWindow.Instance.CurrentAssemblyList.Sort(this); - } - - int IComparer.Compare(LoadedAssembly x, LoadedAssembly y) - { - return string.Compare(x.ShortName, y.ShortName, StringComparison.CurrentCulture); + MainWindow.Instance.AssemblyTreeModel.SortAssemblyList(); } } @@ -50,19 +44,8 @@ sealed class CollapseAllCommand : SimpleCommand { public override void Execute(object parameter) { - using (MainWindow.Instance.AssemblyTreeView.LockUpdates()) - CollapseChildren(MainWindow.Instance.AssemblyTreeView.Root); + MainWindow.Instance.AssemblyTreeModel.CollapseAll(); - void CollapseChildren(SharpTreeNode node) - { - foreach (var child in node.Children) - { - if (!child.IsExpanded) - continue; - CollapseChildren(child); - child.IsExpanded = false; - } - } } } } diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index fcbdb1bae9..9a50cb6498 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; +using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; @@ -32,6 +33,7 @@ using ICSharpCode.ILSpyX.TreeView; using TomsToolbox.Composition; +using TomsToolbox.Essentials; namespace ICSharpCode.ILSpy { @@ -168,6 +170,13 @@ public ExportContextMenuEntryAttribute() internal class ContextMenuProvider { + private static readonly WeakEventSource ContextMenuClosedEventSource = new(); + + public static event EventHandler ContextMenuClosed { + add => ContextMenuClosedEventSource.Subscribe(value); + remove => ContextMenuClosedEventSource.Unsubscribe(value); + } + /// /// Enables extensible context menu support for the specified tree view. /// @@ -203,37 +212,40 @@ public static void Add(DataGrid dataGrid) dataGrid.ContextMenu = new ContextMenu(); } + readonly Control control; readonly SharpTreeView treeView; readonly DecompilerTextView textView; readonly ListBox listBox; readonly DataGrid dataGrid; readonly IExport[] entries; - private ContextMenuProvider() + private ContextMenuProvider(Control control) { entries = App.ExportProvider.GetExports().ToArray(); + + this.control = control; } ContextMenuProvider(DecompilerTextView textView) - : this() + : this((Control)textView) { this.textView = textView ?? throw new ArgumentNullException(nameof(textView)); } ContextMenuProvider(SharpTreeView treeView) - : this() + : this((Control)treeView) { this.treeView = treeView ?? throw new ArgumentNullException(nameof(treeView)); } ContextMenuProvider(ListBox listBox) - : this() + : this((Control)listBox) { this.listBox = listBox ?? throw new ArgumentNullException(nameof(listBox)); } ContextMenuProvider(DataGrid dataGrid) - : this() + : this((Control)dataGrid) { this.dataGrid = dataGrid ?? throw new ArgumentNullException(nameof(dataGrid)); } @@ -289,7 +301,18 @@ void dataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e) bool ShowContextMenu(TextViewContext context, out ContextMenu menu) { + // Closing event is raised on the control where mouse is clicked, not on the control that opened the menu, so we hook on the global window event. + var window = Window.GetWindow(control)!; + window.ContextMenuClosing += ContextMenu_Closing; + + void ContextMenu_Closing(object sender, EventArgs e) + { + window.ContextMenuClosing -= ContextMenu_Closing; + ContextMenuClosedEventSource.Raise(this, EventArgs.Empty); + } + menu = new ContextMenu(); + var menuGroups = new Dictionary[]>(); IExport[] topLevelGroup = null; foreach (var group in entries.OrderBy(c => c.Metadata.Order).GroupBy(c => c.Metadata.ParentMenuID)) diff --git a/ILSpy/Controls/TreeView/SharpTreeView.cs b/ILSpy/Controls/TreeView/SharpTreeView.cs index faa7cb2f05..c60cd5ad76 100644 --- a/ILSpy/Controls/TreeView/SharpTreeView.cs +++ b/ILSpy/Controls/TreeView/SharpTreeView.cs @@ -158,6 +158,7 @@ void Reload() if (flattener != null) { flattener.Stop(); + flattener.CollectionChanged -= flattener_CollectionChanged; } if (Root != null) { diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index a7b4c71022..c0a2b40816 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -33,7 +33,6 @@ using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Wpf; @@ -136,8 +135,7 @@ public TabPageModel ActiveTabPage { { if (state.DecompiledNodes != null) { - MainWindow.Instance.SelectNodes(state.DecompiledNodes, - inNewTabPage: false, setFocus: true, changingActiveTab: true); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(state.DecompiledNodes, inNewTabPage: false, setFocus: true, changingTab: true); } else { @@ -216,7 +214,7 @@ internal void ResetLayout() CloseAllTabs(); SessionSettings.DockLayout.Reset(); InitializeLayout(MainWindow.Instance.dockManager); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView); } static readonly PropertyInfo previousContainerProperty = typeof(LayoutContent).GetProperty("PreviousContainer", BindingFlags.NonPublic | BindingFlags.Instance); diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 374ad026e2..39595224fb 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -27,7 +27,6 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy diff --git a/ILSpy/GlobalUsings.cs b/ILSpy/GlobalUsings.cs new file mode 100644 index 0000000000..12310c6125 --- /dev/null +++ b/ILSpy/GlobalUsings.cs @@ -0,0 +1 @@ +global using ICSharpCode.ILSpy.Util; \ No newline at end of file diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index ef7613ccfc..82245963c3 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -23,7 +23,6 @@ using System.Runtime.CompilerServices; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy diff --git a/ILSpy/Languages/CSharpILMixedLanguage.cs b/ILSpy/Languages/CSharpILMixedLanguage.cs index 4f7ca57cc3..4d3436d23d 100644 --- a/ILSpy/Languages/CSharpILMixedLanguage.cs +++ b/ILSpy/Languages/CSharpILMixedLanguage.cs @@ -34,7 +34,6 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Extensions; diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index df801511c8..b570ba12eb 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -41,7 +41,6 @@ using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using LanguageVersion = ICSharpCode.ILSpyX.LanguageVersion; @@ -359,15 +358,15 @@ public override void DecompileType(ITypeDefinition type, ITextOutput output, Dec void AddReferenceWarningMessage(MetadataFile module, ITextOutput output) { - var loadedAssembly = MainWindow.Instance.CurrentAssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); + var loadedAssembly = MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); if (loadedAssembly == null || !loadedAssembly.LoadedAssemblyReferencesInfo.HasErrors) return; string line1 = Properties.Resources.WarningSomeAssemblyReference; string line2 = Properties.Resources.PropertyManuallyMissingReferencesListLoadedAssemblies; AddWarningMessage(module, output, line1, line2, Properties.Resources.ShowAssemblyLoad, Images.ViewCode, delegate { - ILSpyTreeNode assemblyNode = MainWindow.Instance.FindTreeNode(module); + ILSpyTreeNode assemblyNode = MainWindow.Instance.AssemblyTreeModel.FindTreeNode(module); assemblyNode.EnsureLazyChildren(); - MainWindow.Instance.SelectNode(assemblyNode.Children.OfType().Single()); + MainWindow.Instance.AssemblyTreeModel.SelectNode(assemblyNode.Children.OfType().Single()); }); } diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs index 22d060b373..f2638bcabb 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -30,7 +30,6 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 39407e92e1..62c51508ce 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -21,52 +21,8 @@ xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" xmlns:toms="urn:TomsToolbox" xmlns:viewModels="clr-namespace:ICSharpCode.ILSpy.ViewModels" - xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes" - xmlns:util="clr-namespace:ICSharpCode.ILSpy.Util" d:DataContext="{d:DesignInstance local:MainWindowViewModel}"> - - - - - - - - - @@ -223,7 +179,7 @@ - + diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 235594f7a9..5a4d6ab6c4 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -21,11 +21,10 @@ using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Globalization; using System.IO; using System.Linq; -using System.Reflection.Metadata; -using System.Reflection.Metadata.Ecma335; -using System.Text; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -33,30 +32,22 @@ using System.Windows.Input; using System.Windows.Media; using System.Windows.Navigation; -using System.Windows.Threading; using AvalonDock.Layout.Serialization; using ICSharpCode.Decompiler; -using ICSharpCode.Decompiler.Documentation; -using ICSharpCode.Decompiler.Metadata; -using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.TypeSystem.Implementation; using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Docking; -using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; -using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.FileLoaders; using ICSharpCode.ILSpyX.Settings; -using ICSharpCode.ILSpy.Controls.TreeView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Extensions; using Microsoft.Win32; @@ -71,11 +62,7 @@ namespace ICSharpCode.ILSpy /// partial class MainWindow : Window { - bool refreshInProgress, changingActiveTab; - readonly NavigationHistory history = new NavigationHistory(); - - AssemblyList assemblyList; - AssemblyListTreeNode assemblyListTreeNode; + readonly NavigationHistoryService history = NavigationHistoryService.Instance; static MainWindow instance; @@ -83,16 +70,17 @@ public static MainWindow Instance { get { return instance; } } - public SharpTreeView AssemblyTreeView { + public AssemblyListPaneModel AssemblyTreeModel { get { - return FindResource("AssemblyTreeView") as SharpTreeView; + return App.ExportProvider.GetExportedValue(); } } public DecompilationOptions CreateDecompilationOptions() { var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress; - return new DecompilationOptions(CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { Progress = decompilerView }; + + return new(AssemblyTreeModel.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { Progress = decompilerView }; } public MainWindow() @@ -113,21 +101,18 @@ public MainWindow() SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) { - System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.CultureInfo(sessionSettings.CurrentCulture); + Thread.CurrentThread.CurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture); } InitializeComponent(); InitToolPanes(); DockWorkspace.Instance.InitializeLayout(dockManager); - MessageBus.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); - MessageBus.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); MessageBus.Subscribers += DockWorkspace_ActiveTabPageChanged; InitMainMenu(); InitWindowMenu(); InitToolbar(); InitFileLoaders(); - ContextMenuProvider.Add(AssemblyTreeView); this.Loaded += MainWindow_Loaded; } @@ -146,26 +131,6 @@ private void DockWorkspace_ActiveTabPageChanged(object sender, EventArgs e) } } - private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - var sessionSettings = SettingsService.Instance.SessionSettings; - - switch (e.PropertyName) - { - case nameof(SessionSettings.ActiveAssemblyList): - ShowAssemblyList(sessionSettings.ActiveAssemblyList); - break; - case nameof(SessionSettings.Theme): - // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) - DecompilerTextView.RegisterHighlighting(); - DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); - break; - case nameof(SessionSettings.CurrentCulture): - MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); - break; - } - } - void SetWindowBounds(Rect bounds) { this.Left = bounds.Left; @@ -217,7 +182,7 @@ Button MakeToolbarItem(IExport command) Command = CommandWrapper.Unwrap(command.Value), ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), Tag = command.Metadata.Tag, - Content = new Image { + Content = new System.Windows.Controls.Image { Width = 16, Height = 16, Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) @@ -242,7 +207,7 @@ void InitMainMenu() { if (parentMenuItem.Items.Count > 0) { - parentMenuItem.Items.Add(new Separator() { Tag = category.Key }); + parentMenuItem.Items.Add(new Separator { Tag = category.Key }); } foreach (var entry in category) { @@ -414,7 +379,7 @@ MenuItem CreateMenuItem(ToolPaneModel pane) if (shortcutKey != null) { InputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(System.Globalization.CultureInfo.CurrentUICulture); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); } if (!string.IsNullOrEmpty(pane.Icon)) { @@ -599,275 +564,26 @@ protected override void OnKeyDown(KeyEventArgs e) } } - public AssemblyList CurrentAssemblyList { - get { return assemblyList; } - } - - List commandLineLoadedAssemblies = new List(); - internal async Task HandleSingleInstanceCommandLineArguments(string[] args) { var cmdArgs = CommandLineArguments.Create(args); await Dispatcher.InvokeAsync(() => { - if (HandleCommandLineArguments(cmdArgs)) + if (AssemblyTreeModel.HandleCommandLineArguments(cmdArgs)) { if (!cmdArgs.NoActivate && WindowState == WindowState.Minimized) WindowState = WindowState.Normal; - HandleCommandLineArgumentsAfterShowList(cmdArgs); + AssemblyTreeModel.HandleCommandLineArgumentsAfterShowList(cmdArgs); } }); } - bool HandleCommandLineArguments(CommandLineArguments args) - { - LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); - if (args.Language != null) - SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); - return true; - } - - /// - /// Called on startup or when passed arguments via WndProc from a second instance. - /// In the format case, spySettings is non-null; in the latter it is null. - /// - void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings spySettings = null) - { - var sessionSettings = SettingsService.Instance.SessionSettings; - - var relevantAssemblies = commandLineLoadedAssemblies.ToList(); - commandLineLoadedAssemblies.Clear(); // clear references once we don't need them anymore - NavigateOnLaunch(args.NavigateTo, sessionSettings.ActiveTreeViewPath, spySettings, relevantAssemblies); - if (args.Search != null) - { - var searchPane = App.ExportProvider.GetExportedValue(); - - searchPane.SearchTerm = args.Search; - searchPane.Show(); - } - } - - async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings spySettings, List relevantAssemblies) - { - var initialSelection = AssemblyTreeView.SelectedItem; - if (navigateTo != null) - { - bool found = false; - if (navigateTo.StartsWith("N:", StringComparison.Ordinal)) - { - string namespaceName = navigateTo.Substring(2); - foreach (LoadedAssembly asm in relevantAssemblies) - { - AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(asm); - if (asmNode != null) - { - // FindNamespaceNode() blocks the UI if the assembly is not yet loaded, - // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch(ex => { }); - NamespaceTreeNode nsNode = asmNode.FindNamespaceNode(namespaceName); - if (nsNode != null) - { - found = true; - if (AssemblyTreeView.SelectedItem == initialSelection) - { - SelectNode(nsNode); - } - break; - } - } - } - } - else if (navigateTo == "none") - { - // Don't navigate anywhere; start empty. - // Used by ILSpy VS addin, it'll send us the real location to navigate to via IPC. - found = true; - } - else - { - IEntity mr = await Task.Run(() => FindEntityInRelevantAssemblies(navigateTo, relevantAssemblies)); - // Make sure we wait for assemblies being loaded... - // BeginInvoke in LoadedAssembly.LookupReferencedAssemblyInternal - await Dispatcher.InvokeAsync(delegate { }, DispatcherPriority.Normal); - if (mr != null && mr.ParentModule.MetadataFile != null) - { - found = true; - if (AssemblyTreeView.SelectedItem == initialSelection) - { - JumpToReference(mr); - } - } - } - if (!found && AssemblyTreeView.SelectedItem == initialSelection) - { - AvalonEditTextOutput output = new AvalonEditTextOutput(); - output.Write(string.Format("Cannot find '{0}' in command line specified assemblies.", navigateTo)); - DockWorkspace.Instance.ShowText(output); - } - } - else if (relevantAssemblies.Count == 1) - { - // NavigateTo == null and an assembly was given on the command-line: - // Select the newly loaded assembly - AssemblyTreeNode asmNode = assemblyListTreeNode.FindAssemblyNode(relevantAssemblies[0]); - if (asmNode != null && AssemblyTreeView.SelectedItem == initialSelection) - { - SelectNode(asmNode); - } - } - else if (spySettings != null) - { - SharpTreeNode node = null; - if (activeTreeViewPath?.Length > 0) - { - foreach (var asm in CurrentAssemblyList.GetAssemblies()) - { - if (asm.FileName == activeTreeViewPath[0]) - { - // FindNodeByPath() blocks the UI if the assembly is not yet loaded, - // so use an async wait instead. - await asm.GetMetadataFileAsync().Catch(ex => { }); - } - } - node = FindNodeByPath(activeTreeViewPath, true); - } - if (AssemblyTreeView.SelectedItem == initialSelection) - { - if (node != null) - { - SelectNode(node); - - // only if not showing the about page, perform the update check: - await ShowMessageIfUpdatesAvailableAsync(spySettings); - } - else - { - DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); - } - } - } - } - - internal static IEntity FindEntityInRelevantAssemblies(string navigateTo, IEnumerable relevantAssemblies) - { - ITypeReference typeRef = null; - IMemberReference memberRef = null; - if (navigateTo.StartsWith("T:", StringComparison.Ordinal)) - { - typeRef = IdStringProvider.ParseTypeName(navigateTo); - } - else - { - memberRef = IdStringProvider.ParseMemberIdString(navigateTo); - typeRef = memberRef.DeclaringTypeReference; - } - foreach (LoadedAssembly asm in relevantAssemblies.ToList()) - { - var module = asm.GetMetadataFileOrNull(); - if (CanResolveTypeInPEFile(module, typeRef, out var typeHandle)) - { - ICompilation compilation = typeHandle.Kind == HandleKind.ExportedType - ? new DecompilerTypeSystem(module, module.GetAssemblyResolver()) - : new SimpleCompilation((PEFile)module, MinimalCorlib.Instance); - return memberRef == null - ? typeRef.Resolve(new SimpleTypeResolveContext(compilation)) as ITypeDefinition - : (IEntity)memberRef.Resolve(new SimpleTypeResolveContext(compilation)); - } - } - return null; - } - - static bool CanResolveTypeInPEFile(MetadataFile module, ITypeReference typeRef, out EntityHandle typeHandle) - { - // We intentionally ignore reference assemblies, so that the loop continues looking for another assembly that might have a usable definition. - if (module.IsReferenceAssembly()) - { - typeHandle = default; - return false; - } - - switch (typeRef) - { - case GetPotentiallyNestedClassTypeReference topLevelType: - typeHandle = topLevelType.ResolveInPEFile(module); - return !typeHandle.IsNil; - case NestedTypeReference nestedType: - if (!CanResolveTypeInPEFile(module, nestedType.DeclaringTypeReference, out typeHandle)) - return false; - if (typeHandle.Kind == HandleKind.ExportedType) - return true; - var typeDef = module.Metadata.GetTypeDefinition((TypeDefinitionHandle)typeHandle); - typeHandle = typeDef.GetNestedTypes().FirstOrDefault(t => { - var td = module.Metadata.GetTypeDefinition(t); - var typeName = ReflectionHelper.SplitTypeParameterCountFromReflectionName(module.Metadata.GetString(td.Name), out int typeParameterCount); - return nestedType.AdditionalTypeParameterCount == typeParameterCount && nestedType.Name == typeName; - }); - return !typeHandle.IsNil; - default: - typeHandle = default; - return false; - } - } - void MainWindow_Loaded(object sender, RoutedEventArgs e) { DockWorkspace.Instance.TabPages.Add(); - var loadPreviousAssemblies = Options.MiscSettingsPanel.CurrentMiscSettings.LoadPreviousAssemblies; - - var sessionSettings = SettingsService.Instance.SessionSettings; - - if (loadPreviousAssemblies) - { - // Load AssemblyList only in Loaded event so that WPF is initialized before we start the CPU-heavy stuff. - // This makes the UI come up a bit faster. - this.assemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); - } - else - { - SettingsService.Instance.AssemblyListManager.ClearAll(); - this.assemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); - } - - HandleCommandLineArguments(App.CommandLineArguments); - - if (assemblyList.GetAssemblies().Length == 0 - && assemblyList.ListName == AssemblyListManager.DefaultListName - && loadPreviousAssemblies) - { - LoadInitialAssemblies(); - } - - ShowAssemblyList(this.assemblyList); - - if (sessionSettings.ActiveAutoLoadedAssembly != null - && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) - { - this.assemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); - } - - Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => OpenAssemblies(SettingsService.Instance.SpySettings))); - } - - void OpenAssemblies(ILSpySettings spySettings) - { - HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, spySettings); - - AvalonEditTextOutput output = new AvalonEditTextOutput(); - if (FormatExceptions(App.StartupExceptions.ToArray(), output)) - DockWorkspace.Instance.ShowText(output); - } - - static bool FormatExceptions(App.ExceptionData[] exceptions, ITextOutput output) - { - var stringBuilder = new StringBuilder(); - var result = exceptions.FormatExceptions(stringBuilder); - if (result) - { - output.Write(stringBuilder.ToString()); - } - return result; + AssemblyTreeModel.Initialize(); } #region Update Check @@ -889,7 +605,7 @@ public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, AdjustUpdateUIAfterCheck(downloadUrl, forceCheck); } - void updatePanelCloseButtonClick(object sender, RoutedEventArgs e) + void UpdatePanelCloseButtonClick(object sender, RoutedEventArgs e) { updatePanel.Visibility = Visibility.Collapsed; } @@ -898,7 +614,7 @@ async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) { if (updateAvailableDownloadUrl != null) { - MainWindow.OpenLink(updateAvailableDownloadUrl); + OpenLink(updateAvailableDownloadUrl); } else { @@ -925,326 +641,6 @@ void AdjustUpdateUIAfterCheck(string downloadUrl, bool displayMessage) } #endregion - public void ShowAssemblyList(string name) - { - AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); - //Only load a new list when it is a different one - if (list.ListName != CurrentAssemblyList.ListName) - { - ShowAssemblyList(list); - SelectNode(AssemblyTreeView.Root); - } - } - - void ShowAssemblyList(AssemblyList assemblyList) - { - history.Clear(); - if (this.assemblyList != null) - { - this.assemblyList.CollectionChanged -= assemblyList_Assemblies_CollectionChanged; - } - - this.assemblyList = assemblyList; - assemblyList.CollectionChanged += assemblyList_Assemblies_CollectionChanged; - - assemblyListTreeNode = new AssemblyListTreeNode(assemblyList); - assemblyListTreeNode.Select = x => SelectNode(x, inNewTabPage: false); - AssemblyTreeView.Root = assemblyListTreeNode; - - if (assemblyList.ListName == AssemblyListManager.DefaultListName) -#if DEBUG - this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion}"; -#else - this.Title = "ILSpy"; -#endif - else -#if DEBUG - this.Title = $"ILSpy {DecompilerVersionInfo.FullVersion} - " + assemblyList.ListName; -#else - this.Title = "ILSpy - " + assemblyList.ListName; -#endif - } - - void assemblyList_Assemblies_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) - { - if (e.Action == NotifyCollectionChangedAction.Reset) - { - history.RemoveAll(_ => true); - } - if (e.OldItems != null) - { - var oldAssemblies = new HashSet(e.OldItems.Cast()); - history.RemoveAll(n => n.TreeNodes.Any( - nd => nd.AncestorsAndSelf().OfType().Any( - a => oldAssemblies.Contains(a.LoadedAssembly)))); - } - - MessageBus.Send(this, new CurrentAssemblyListChangedEventArgs(e)); - } - - void LoadInitialAssemblies() - { - // Called when loading an empty assembly list; so that - // the user can see something initially. - System.Reflection.Assembly[] initialAssemblies = { - typeof(object).Assembly, - typeof(Uri).Assembly, - typeof(System.Linq.Enumerable).Assembly, - typeof(System.Xml.XmlDocument).Assembly, - typeof(System.Windows.Markup.MarkupExtension).Assembly, - typeof(System.Windows.Rect).Assembly, - typeof(System.Windows.UIElement).Assembly, - typeof(System.Windows.FrameworkElement).Assembly - }; - foreach (System.Reflection.Assembly asm in initialAssemblies) - assemblyList.OpenAssembly(asm.Location); - } - - void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) - { - if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") - { - DecompileSelectedNodes(recordHistory: false); - } - } - - internal AssemblyListTreeNode AssemblyListTreeNode { - get { return assemblyListTreeNode; } - } - - #region Node Selection - - public void SelectNode(SharpTreeNode obj) - { - SelectNode(obj, false); - } - - public void SelectNode(SharpTreeNode obj, bool inNewTabPage) - { - SelectNode(obj, inNewTabPage, true); - } - - public void SelectNode(SharpTreeNode obj, bool inNewTabPage, bool setFocus) - { - if (obj != null) - { - if (!obj.AncestorsAndSelf().Any(node => node.IsHidden)) - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - AssemblyTreeView.SelectedItem = null; - } - - // Set both the selection and focus to ensure that keyboard navigation works as expected. - if (setFocus) - { - AssemblyTreeView.FocusNode(obj); - } - else - { - AssemblyTreeView.ScrollIntoView(obj); - } - AssemblyTreeView.SelectedItem = obj; - } - else - { - MessageBox.Show(Properties.Resources.NavigationFailed, "ILSpy", MessageBoxButton.OK, MessageBoxImage.Exclamation); - } - } - } - - public void SelectNodes(IEnumerable nodes) - { - SelectNodes(nodes, false); - } - - public void SelectNodes(IEnumerable nodes, bool inNewTabPage) - { - SelectNodes(nodes, inNewTabPage, true); - } - - public void SelectNodes(IEnumerable nodes, bool inNewTabPage, bool setFocus) - { - SelectNodes(nodes, inNewTabPage, setFocus, false); - } - - internal void SelectNodes(IEnumerable nodes, bool inNewTabPage, - bool setFocus, bool changingActiveTab) - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - } - - // Ensure nodes exist - var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) - .Where(n => n != null).ToArray(); - - if (!nodesList.Any() || !nodesList.All(n => !n.AncestorsAndSelf().Any(a => a.IsHidden))) - { - return; - } - - this.changingActiveTab = changingActiveTab || inNewTabPage; - try - { - if (setFocus) - { - AssemblyTreeView.FocusNode(nodesList[0]); - } - else - { - AssemblyTreeView.ScrollIntoView(nodesList[0]); - } - - AssemblyTreeView.SetSelectedNodes(nodesList); - } - finally - { - this.changingActiveTab = false; - } - } - - /// - /// Retrieves a node using the .ToString() representations of its ancestors. - /// - public SharpTreeNode FindNodeByPath(string[] path, bool returnBestMatch) - { - if (path == null) - return null; - SharpTreeNode node = AssemblyTreeView.Root; - SharpTreeNode bestMatch = node; - foreach (var element in path) - { - if (node == null) - break; - bestMatch = node; - node.EnsureLazyChildren(); - var ilSpyTreeNode = node as ILSpyTreeNode; - if (ilSpyTreeNode != null) - ilSpyTreeNode.EnsureChildrenFiltered(); - node = node.Children.FirstOrDefault(c => c.ToString() == element); - } - if (returnBestMatch) - return node ?? bestMatch; - else - return node; - } - - /// - /// Gets the .ToString() representation of the node's ancestors. - /// - public static string[] GetPathForNode(SharpTreeNode node) - { - if (node == null) - return null; - List path = new List(); - while (node.Parent != null) - { - path.Add(node.ToString()); - node = node.Parent; - } - path.Reverse(); - return path.ToArray(); - } - - public ILSpyTreeNode FindTreeNode(object reference) - { - switch (reference) - { - case LoadedAssembly lasm: - return assemblyListTreeNode.FindAssemblyNode(lasm); - case MetadataFile asm: - return assemblyListTreeNode.FindAssemblyNode(asm); - case Resource res: - return assemblyListTreeNode.FindResourceNode(res); - case ValueTuple resName: - return assemblyListTreeNode.FindResourceNode(resName.Item1, resName.Item2); - case ITypeDefinition type: - return assemblyListTreeNode.FindTypeNode(type); - case IField fd: - return assemblyListTreeNode.FindFieldNode(fd); - case IMethod md: - return assemblyListTreeNode.FindMethodNode(md); - case IProperty pd: - return assemblyListTreeNode.FindPropertyNode(pd); - case IEvent ed: - return assemblyListTreeNode.FindEventNode(ed); - case INamespace nd: - return AssemblyListTreeNode.FindNamespaceNode(nd); - default: - return null; - } - } - - public void JumpToReference(object reference) - { - JumpToReference(reference, inNewTabPage: false); - } - - public void JumpToReference(object reference, bool inNewTabPage) - { - JumpToReferenceAsync(reference, inNewTabPage).HandleExceptions(); - } - - /// - /// Jumps to the specified reference. - /// - /// - /// Returns a task that will signal completion when the decompilation of the jump target has finished. - /// The task will be marked as canceled if the decompilation is canceled. - /// - public Task JumpToReferenceAsync(object reference) - { - return JumpToReferenceAsync(reference, inNewTabPage: false); - } - - public Task JumpToReferenceAsync(object reference, bool inNewTabPage) - { - decompilationTask = TaskHelper.CompletedTask; - switch (reference) - { - case Decompiler.Disassembler.OpCodeInfo opCode: - OpenLink(opCode.Link); - break; - case EntityReference unresolvedEntity: - string protocol = unresolvedEntity.Protocol ?? "decompile"; - var file = unresolvedEntity.ResolveAssembly(assemblyList); - if (file == null) - { - break; - } - if (protocol != "decompile") - { - var protocolHandlers = App.ExportProvider.GetExports(); - foreach (var handler in protocolHandlers) - { - var node = handler.Value.Resolve(protocol, file, unresolvedEntity.Handle, out bool newTabPage); - if (node != null) - { - SelectNode(node, newTabPage || inNewTabPage); - return decompilationTask; - } - } - } - var possibleToken = MetadataTokenHelpers.TryAsEntityHandle(MetadataTokens.GetToken(unresolvedEntity.Handle)); - if (possibleToken != null) - { - var typeSystem = new DecompilerTypeSystem(file, file.GetAssemblyResolver(), TypeSystemOptions.Default | TypeSystemOptions.Uncached); - reference = typeSystem.MainModule.ResolveEntity(possibleToken.Value); - goto default; - } - break; - default: - ILSpyTreeNode treeNode = FindTreeNode(reference); - if (treeNode != null) - SelectNode(treeNode, inNewTabPage); - break; - } - return decompilationTask; - } - public static void OpenLink(string link) { try @@ -1274,7 +670,6 @@ public static void ExecuteCommand(string fileName, string arguments) // just ignore all of them. } } - #endregion #region Open/Refresh void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e) @@ -1296,52 +691,14 @@ public void OpenFiles(string[] fileNames, bool focusNode = true) throw new ArgumentNullException(nameof(fileNames)); if (focusNode) - AssemblyTreeView.UnselectAll(); + AssemblyTreeModel.UnselectAll(); - LoadAssemblies(fileNames, focusNode: focusNode); - } - - void LoadAssemblies(IEnumerable fileNames, List loadedAssemblies = null, bool focusNode = true) - { - SharpTreeNode lastNode = null; - foreach (string file in fileNames) - { - var asm = assemblyList.OpenAssembly(file); - if (asm != null) - { - if (loadedAssemblies != null) - { - loadedAssemblies.Add(asm); - } - else - { - var node = assemblyListTreeNode.FindAssemblyNode(asm); - if (node != null && focusNode) - { - AssemblyTreeView.SelectedItems.Add(node); - lastNode = node; - } - } - } - - if (lastNode != null && focusNode) - AssemblyTreeView.FocusNode(lastNode); - } + AssemblyTreeModel.LoadAssemblies(fileNames, focusNode: focusNode); } void RefreshCommandExecuted(object sender, ExecutedRoutedEventArgs e) { - try - { - refreshInProgress = true; - var path = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); - ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(assemblyList.ListName)); - SelectNode(FindNodeByPath(path, true), inNewTabPage: false, AssemblyTreeView.IsFocused); - } - finally - { - refreshInProgress = false; - } + AssemblyTreeModel.Refresh(); } void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) @@ -1350,114 +707,16 @@ void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) } #endregion - #region Decompile (TreeView_SelectionChanged) - bool delayDecompilationRequestDueToContextMenu; - - protected override void OnContextMenuClosing(ContextMenuEventArgs e) - { - base.OnContextMenuClosing(e); - - if (delayDecompilationRequestDueToContextMenu) - { - delayDecompilationRequestDueToContextMenu = false; - var state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; - DecompileSelectedNodes(state); - } - } - - void TreeView_SelectionChanged(object sender, SelectionChangedEventArgs e) - { - DecompilerTextViewState state = null; - if (refreshInProgress || changingActiveTab) - { - state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; - } - - this.delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; - if (!changingActiveTab && !delayDecompilationRequestDueToContextMenu) - { - DecompileSelectedNodes(state); - } - - SelectionChanged?.Invoke(sender, e); - } - - Task decompilationTask; - bool ignoreDecompilationRequests; - - void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool recordHistory = true) - { - if (ignoreDecompilationRequests) - return; - - if (AssemblyTreeView.SelectedItems.Count == 0 && refreshInProgress) - return; - - if (recordHistory) - { - var tabPage = DockWorkspace.Instance.ActiveTabPage; - var currentState = tabPage.GetState(); - if (currentState != null) - history.UpdateCurrent(new NavigationState(tabPage, currentState)); - history.Record(new NavigationState(tabPage, AssemblyTreeView.SelectedItems.OfType())); - } - - DockWorkspace.Instance.ActiveTabPage.SupportsLanguageSwitching = true; - - if (AssemblyTreeView.SelectedItems.Count == 1) - { - ILSpyTreeNode node = AssemblyTreeView.SelectedItem as ILSpyTreeNode; - if (node != null && node.View(DockWorkspace.Instance.ActiveTabPage)) - return; - } - if (newState?.ViewedUri != null) - { - NavigateTo(new RequestNavigateEventArgs(newState.ViewedUri, null), recordHistory: false); - return; - } - var options = MainWindow.Instance.CreateDecompilationOptions(); - options.TextViewState = newState; - decompilationTask = DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync( - textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options) - ); - } - void SaveCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) { e.Handled = true; - e.CanExecute = SaveCodeContextMenuEntry.CanExecute(SelectedNodes.ToList()); + e.CanExecute = SaveCodeContextMenuEntry.CanExecute(AssemblyTreeModel.SelectedNodes.ToList()); } void SaveCommandExecuted(object sender, ExecutedRoutedEventArgs e) { - SaveCodeContextMenuEntry.Execute(SelectedNodes.ToList()); - } - - public void RefreshDecompiledView() - { - try - { - refreshInProgress = true; - DecompileSelectedNodes(); - } - finally - { - refreshInProgress = false; - } - } - - public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; - - public LanguageVersion CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; - - public event SelectionChangedEventHandler SelectionChanged; - - public IEnumerable SelectedNodes { - get { - return AssemblyTreeView.GetTopLevelSelection().OfType(); - } + SaveCodeContextMenuEntry.Execute(AssemblyTreeModel.SelectedNodes.ToList()); } - #endregion #region Back/Forward navigation void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) @@ -1498,17 +757,10 @@ void NavigateHistory(bool forward) history.UpdateCurrent(new NavigationState(tabPage, state)); var newState = forward ? history.GoForward() : history.GoBack(); - ignoreDecompilationRequests = true; - AssemblyTreeView.SelectedItems.Clear(); DockWorkspace.Instance.ActiveTabPage = newState.TabPage; - foreach (var node in newState.TreeNodes) - { - AssemblyTreeView.SelectedItems.Add(node); - } - if (newState.TreeNodes.Any()) - AssemblyTreeView.FocusNode(newState.TreeNodes.First()); - ignoreDecompilationRequests = false; - DecompileSelectedNodes(newState.ViewState as DecompilerTextViewState, false); + + AssemblyTreeModel.SelectNodes(newState.TreeNodes, ignoreCompilationRequests: true); + AssemblyTreeModel.DecompileSelectedNodes(newState.ViewState as DecompilerTextViewState, false); } #endregion @@ -1558,9 +810,8 @@ void RecordHistory() var currentState = tabPage.GetState(); if (currentState != null) history.UpdateCurrent(new NavigationState(tabPage, currentState)); - ignoreDecompilationRequests = true; - UnselectAll(); - ignoreDecompilationRequests = false; + + AssemblyTreeModel.UnselectAll(ignoreCompilationRequests: true); history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); } } @@ -1569,7 +820,7 @@ protected override void OnStateChanged(EventArgs e) { base.OnStateChanged(e); // store window state in settings only if it's not minimized - if (this.WindowState != System.Windows.WindowState.Minimized) + if (this.WindowState != WindowState.Minimized) SettingsService.Instance.SessionSettings.WindowState = this.WindowState; } @@ -1578,9 +829,9 @@ protected override void OnClosing(CancelEventArgs e) base.OnClosing(e); var sessionSettings = SettingsService.Instance.SessionSettings; - sessionSettings.ActiveAssemblyList = assemblyList.ListName; - sessionSettings.ActiveTreeViewPath = GetPathForNode(AssemblyTreeView.SelectedItem as SharpTreeNode); - sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeView.SelectedItem as SharpTreeNode); + sessionSettings.ActiveAssemblyList = AssemblyTreeModel.CurrentAssemblyList.ListName; + sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; + sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); sessionSettings.Save(); @@ -1590,12 +841,12 @@ private string GetAutoLoadedAssemblyNode(SharpTreeNode node) { if (node == null) return null; - while (!(node is TreeNodes.AssemblyTreeNode) && node.Parent != null) + while (!(node is AssemblyTreeNode) && node.Parent != null) { node = node.Parent; } //this should be an assembly node - var assyNode = node as TreeNodes.AssemblyTreeNode; + var assyNode = node as AssemblyTreeNode; var loadedAssy = assyNode.LoadedAssembly; if (!(loadedAssy.IsLoaded && loadedAssy.IsAutoLoaded)) return null; @@ -1603,11 +854,6 @@ private string GetAutoLoadedAssemblyNode(SharpTreeNode node) return loadedAssy.FileName; } - public void UnselectAll() - { - AssemblyTreeView.UnselectAll(); - } - public void SetStatus(string status, Brush foreground) { if (this.statusBar.Visibility == Visibility.Collapsed) diff --git a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs index f36164b16e..4d654da914 100644 --- a/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ClassLayoutTableTreeNode.cs @@ -97,7 +97,7 @@ struct ClassLayoutEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference("metadata", classLayout.Parent)); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference("metadata", classLayout.Parent))); } string parentTooltip; diff --git a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs index e6c41e5aae..2b2ea571f2 100644 --- a/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ConstantTableTreeNode.cs @@ -88,7 +88,7 @@ struct ConstantEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, constant.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, constant.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs index bae1ceffdd..370532d141 100644 --- a/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/CustomAttributeTableTreeNode.cs @@ -83,7 +83,7 @@ struct CustomAttributeEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, customAttr.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, customAttr.Parent, protocol: "metadata"))); } string parentTooltip; @@ -94,7 +94,7 @@ public void OnParentClick() public void OnConstructorClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, customAttr.Constructor, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, customAttr.Constructor, protocol: "metadata"))); } string constructorTooltip; diff --git a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs index b719f678c5..f37bdb87d9 100644 --- a/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/DeclSecurityTableTreeNode.cs @@ -84,7 +84,7 @@ struct DeclSecurityEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, declSecAttr.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, declSecAttr.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs index 44ea708f1f..3018d89707 100644 --- a/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventMapTableTreeNode.cs @@ -95,7 +95,7 @@ struct EventMapEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventMap.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventMap.Parent, protocol: "metadata"))); } string parentTooltip; @@ -106,7 +106,7 @@ public void OnParentClick() public void OnEventListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventMap.EventList, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventMap.EventList, protocol: "metadata"))); } string eventListTooltip; diff --git a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs index 7e3c95ca58..14dabb3e04 100644 --- a/ILSpy/Metadata/CorTables/EventTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/EventTableTreeNode.cs @@ -103,7 +103,7 @@ IEntity IMemberTreeNode.Member { public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, eventDef.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, eventDef.Type, protocol: "metadata"))); } string typeTooltip; diff --git a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs index dc88622c64..f1075c16bc 100644 --- a/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ExportedTypeTableTreeNode.cs @@ -107,7 +107,7 @@ struct ExportedTypeEntry public void OnImplementationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, type.Implementation, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, type.Implementation, protocol: "metadata"))); } string implementationTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs index ba014ccc09..6fe92c5972 100644 --- a/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldLayoutTableTreeNode.cs @@ -96,7 +96,7 @@ struct FieldLayoutEntry public void OnFieldClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldLayout.Field, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldLayout.Field, protocol: "metadata"))); } string fieldTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs index 7bb2c1da21..befd90a8c5 100644 --- a/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldMarshalTableTreeNode.cs @@ -95,7 +95,7 @@ struct FieldMarshalEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldMarshal.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldMarshal.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs index f95f21a296..959225bdba 100644 --- a/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/FieldRVATableTreeNode.cs @@ -99,7 +99,7 @@ struct FieldRVAEntry public void OnFieldClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, fieldRVA.Field, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, fieldRVA.Field, protocol: "metadata"))); } string fieldTooltip; diff --git a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs index d7af52a198..0e21802108 100644 --- a/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamConstraintTableTreeNode.cs @@ -84,7 +84,7 @@ struct GenericParamConstraintEntry public void OnOwnerClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParamConstraint.Parameter, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParamConstraint.Parameter, protocol: "metadata"))); } string ownerTooltip; @@ -108,7 +108,7 @@ public string OwnerTooltip { public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParamConstraint.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParamConstraint.Type, protocol: "metadata"))); } string typeTooltip; diff --git a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs index 1ebc8f1065..29509426b2 100644 --- a/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/GenericParamTableTreeNode.cs @@ -92,7 +92,7 @@ struct GenericParamEntry public void OnOwnerClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, genericParam.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, genericParam.Parent, protocol: "metadata"))); } string ownerTooltip; diff --git a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs index e47456fb58..efe6e6fa13 100644 --- a/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ImplMapTableTreeNode.cs @@ -113,7 +113,7 @@ struct ImplMapEntry public void OnMemberForwardedClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, implMap.MemberForwarded, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, implMap.MemberForwarded, protocol: "metadata"))); } string memberForwardedTooltip; @@ -124,7 +124,7 @@ public void OnMemberForwardedClick() public void OnImportScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, implMap.ImportScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, implMap.ImportScope, protocol: "metadata"))); } string importScopeTooltip; diff --git a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs index 72e2a6d8b0..04b87015df 100644 --- a/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/InterfaceImplTableTreeNode.cs @@ -96,7 +96,7 @@ struct InterfaceImplEntry public void OnClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, interfaceImpl.Class, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, interfaceImpl.Class, protocol: "metadata"))); } string classTooltip; @@ -107,7 +107,7 @@ public void OnClassClick() public void OnInterfaceClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, interfaceImpl.Interface, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, interfaceImpl.Interface, protocol: "metadata"))); } string interfaceTooltip; diff --git a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs index fca47ac3dd..5bd75eb913 100644 --- a/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/ManifestResourceTableTreeNode.cs @@ -93,7 +93,7 @@ struct ManifestResourceEntry public void OnImplementationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, manifestResource.Implementation, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, manifestResource.Implementation, protocol: "metadata"))); } string implementationTooltip; diff --git a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs index fc2d018655..9f858434d9 100644 --- a/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MemberRefTableTreeNode.cs @@ -83,7 +83,7 @@ struct MemberRefEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, memberRef.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, memberRef.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs index 2edd46feb1..2344f6ff10 100644 --- a/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodImplTableTreeNode.cs @@ -82,7 +82,7 @@ struct MethodImplEntry public void OnMethodDeclarationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.MethodDeclaration, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.MethodDeclaration, protocol: "metadata"))); } string methodDeclarationTooltip; @@ -93,7 +93,7 @@ public void OnMethodDeclarationClick() public void OnMethodBodyClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.MethodBody, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.MethodBody, protocol: "metadata"))); } string methodBodyTooltip; @@ -104,7 +104,7 @@ public void OnMethodBodyClick() public void OnTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodImpl.Type, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodImpl.Type, protocol: "metadata"))); } string typeTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs index 62f22c8a9c..49cb863f01 100644 --- a/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSemanticsTableTreeNode.cs @@ -90,7 +90,7 @@ struct MethodSemanticsEntry public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, method, protocol: "metadata"))); } string methodTooltip; @@ -101,7 +101,7 @@ public void OnMethodClick() public void OnAssociationClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, association, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, association, protocol: "metadata"))); } string associationTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs index 7e8acf6f55..d25a71dd3f 100644 --- a/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodSpecTableTreeNode.cs @@ -85,7 +85,7 @@ struct MethodSpecEntry public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodSpec.Method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodSpec.Method, protocol: "metadata"))); } string methodTooltip; diff --git a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs index b6f9ac6b47..cfea5f2aeb 100644 --- a/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/MethodTableTreeNode.cs @@ -118,7 +118,7 @@ struct MethodDefEntry : IMemberTreeNode public void OnParamListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, methodDef.GetParameters().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, methodDef.GetParameters().FirstOrDefault(), protocol: "metadata"))); } string paramListTooltip; diff --git a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs index 02fcd52335..e95619be73 100644 --- a/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/NestedClassTableTreeNode.cs @@ -95,7 +95,7 @@ struct NestedClassEntry public void OnNestedClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, nestedClass.Nested, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, nestedClass.Nested, protocol: "metadata"))); } string nestedClassTooltip; @@ -106,7 +106,7 @@ public void OnNestedClassClick() public void OnEnclosingClassClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, nestedClass.Enclosing, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, nestedClass.Enclosing, protocol: "metadata"))); } string enclosingClassTooltip; diff --git a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs index 7bbfcf09b9..6071961f3b 100644 --- a/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PropertyMapTableTreeNode.cs @@ -95,7 +95,7 @@ struct PropertyMapEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, propertyMap.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, propertyMap.Parent, protocol: "metadata"))); } string parentTooltip; @@ -106,7 +106,7 @@ public void OnParentClick() public void OnPropertyListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, propertyMap.PropertyList, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, propertyMap.PropertyList, protocol: "metadata"))); } string propertyListTooltip; diff --git a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs index 8f9ac73fb9..129f769079 100644 --- a/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeDefTableTreeNode.cs @@ -111,7 +111,7 @@ struct TypeDefEntry : IMemberTreeNode public void OnBaseTypeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.BaseType, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.BaseType, protocol: "metadata"))); } public string BaseTypeTooltip { @@ -142,7 +142,7 @@ public string BaseTypeTooltip { public void OnFieldListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.GetFields().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.GetFields().FirstOrDefault(), protocol: "metadata"))); } string fieldListTooltip; @@ -160,7 +160,7 @@ public string FieldListTooltip { public void OnMethodListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeDef.GetMethods().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeDef.GetMethods().FirstOrDefault(), protocol: "metadata"))); } string methodListTooltip; diff --git a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs index aeaead6087..6b086fea16 100644 --- a/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/TypeRefTableTreeNode.cs @@ -83,7 +83,7 @@ struct TypeRefEntry public void OnResolutionScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, typeRef.ResolutionScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, typeRef.ResolutionScope, protocol: "metadata"))); } string resolutionScopeTooltip; diff --git a/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs b/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs index 9ec2117518..d3067d713c 100644 --- a/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs +++ b/ILSpy/Metadata/DebugMetadataTablesTreeNode.cs @@ -21,7 +21,6 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Metadata diff --git a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs index f9ef9f3692..b020f5a97f 100644 --- a/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/CustomDebugInformationTableTreeNode.cs @@ -188,7 +188,7 @@ static CustomDebugInformationKind GetKind(MetadataReader metadata, GuidHandle h) public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.Parent, protocol: "metadata"))); } string parentTooltip; diff --git a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs index 750a18ae71..65639ad1f6 100644 --- a/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/ImportScopeTableTreeNode.cs @@ -80,7 +80,7 @@ readonly struct ImportScopeEntry public void OnParentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.Parent, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.Parent, protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.HeapOffset)] diff --git a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs index 8b0f1e888b..f25412f989 100644 --- a/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/LocalScopeTableTreeNode.cs @@ -81,7 +81,7 @@ struct LocalScopeEntry public void OnMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.Method, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.Method, protocol: "metadata"))); } string methodTooltip; @@ -92,7 +92,7 @@ public void OnMethodClick() public void OnImportScopeClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.ImportScope, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.ImportScope, protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.Token)] @@ -100,7 +100,7 @@ public void OnImportScopeClick() public void OnVariableListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.GetLocalVariables().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.GetLocalVariables().FirstOrDefault(), protocol: "metadata"))); } [ColumnInfo("X8", Kind = ColumnKind.Token)] @@ -108,7 +108,7 @@ public void OnVariableListClick() public void OnConstantListClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, localScope.GetLocalConstants().FirstOrDefault(), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, localScope.GetLocalConstants().FirstOrDefault(), protocol: "metadata"))); } public int StartOffset => localScope.StartOffset; diff --git a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs index 80aede7fc0..5453ce6f79 100644 --- a/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/MethodDebugInformationTableTreeNode.cs @@ -83,7 +83,7 @@ struct MethodDebugInformationEntry public void OnDocumentClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.Document, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.Document, protocol: "metadata"))); } public string DocumentTooltip { @@ -116,7 +116,7 @@ public string SequencePointsTooltip { public void OnLocalSignatureClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, debugInfo.LocalSignature, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, debugInfo.LocalSignature, protocol: "metadata"))); } public string LocalSignatureTooltip { diff --git a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs index 9341cad78f..d4f778ed2c 100644 --- a/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs +++ b/ILSpy/Metadata/DebugTables/StateMachineMethodTableTreeNode.cs @@ -83,7 +83,7 @@ struct StateMachineMethodEntry public void OnMoveNextMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, moveNextMethod, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, moveNextMethod, protocol: "metadata"))); } string moveNextMethodTooltip; @@ -94,7 +94,7 @@ public void OnMoveNextMethodClick() public void OnKickofMethodClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, kickoffMethod, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, kickoffMethod, protocol: "metadata"))); } string kickoffMethodTooltip; diff --git a/ILSpy/Metadata/GoToTokenCommand.cs b/ILSpy/Metadata/GoToTokenCommand.cs index 1c0caca2b7..d451f27aba 100644 --- a/ILSpy/Metadata/GoToTokenCommand.cs +++ b/ILSpy/Metadata/GoToTokenCommand.cs @@ -38,7 +38,7 @@ class GoToTokenCommand : IContextMenuEntry public void Execute(TextViewContext context) { int token = GetSelectedToken(context.DataGrid, out MetadataFile module).Value; - MainWindow.Instance.JumpToReference(new EntityReference(module, MetadataTokens.Handle(token), protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(module, MetadataTokens.Handle(token), protocol: "metadata"))); } public bool IsEnabled(TextViewContext context) diff --git a/ILSpy/Metadata/MetadataProtocolHandler.cs b/ILSpy/Metadata/MetadataProtocolHandler.cs index c49e300d76..8a50cac363 100644 --- a/ILSpy/Metadata/MetadataProtocolHandler.cs +++ b/ILSpy/Metadata/MetadataProtocolHandler.cs @@ -34,7 +34,7 @@ public ILSpyTreeNode Resolve(string protocol, MetadataFile module, Handle handle newTabPage = true; if (protocol != "metadata") return null; - var assemblyTreeNode = MainWindow.Instance.FindTreeNode(module) as AssemblyTreeNode; + var assemblyTreeNode = MainWindow.Instance.AssemblyTreeModel.FindTreeNode(module) as AssemblyTreeNode; if (assemblyTreeNode == null) return null; var mxNode = assemblyTreeNode.Children.OfType().FirstOrDefault(); diff --git a/ILSpy/Metadata/MetadataTablesTreeNode.cs b/ILSpy/Metadata/MetadataTablesTreeNode.cs index 3724ba590a..1fb55be811 100644 --- a/ILSpy/Metadata/MetadataTablesTreeNode.cs +++ b/ILSpy/Metadata/MetadataTablesTreeNode.cs @@ -23,7 +23,6 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; namespace ICSharpCode.ILSpy.Metadata diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index d530079dcf..892c8ea4f6 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -19,7 +19,6 @@ using System.ComponentModel.Composition; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy.Options diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml.cs b/ILSpy/Options/DisplaySettingsPanel.xaml.cs index 9645dacc7d..95b56b4eeb 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml.cs +++ b/ILSpy/Options/DisplaySettingsPanel.xaml.cs @@ -27,7 +27,6 @@ using System.Windows.Threading; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy.Options diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs index a3a7584a2a..2a5a104d6c 100644 --- a/ILSpy/Search/SearchPane.xaml.cs +++ b/ILSpy/Search/SearchPane.xaml.cs @@ -32,7 +32,6 @@ using System.Windows.Threading; using ICSharpCode.ILSpy.AppEnv; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Extensions; @@ -250,8 +249,8 @@ async void StartSearch(string searchTerm) { searchProgressBar.IsIndeterminate = true; - startedSearch = new(await mainWindow.CurrentAssemblyList.GetAllAssemblies(), searchTerm, - (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.CurrentLanguage, + startedSearch = new(await mainWindow.AssemblyTreeModel.CurrentAssemblyList.GetAllAssemblies(), searchTerm, + (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.AssemblyTreeModel.CurrentLanguage, SettingsService.Instance.SessionSettings.LanguageSettings.ShowApiLevel); currentSearch = startedSearch; @@ -269,7 +268,7 @@ void JumpToSelectedItem() { if (listBox.SelectedItem is SearchResult result) { - MainWindow.Instance.JumpToReference(result.Reference); + MessageBus.Send(this, new NavigateToReferenceEventArgs(result.Reference)); } } diff --git a/ILSpy/Search/SearchPaneModel.cs b/ILSpy/Search/SearchPaneModel.cs index 6ddcd9f048..b3bc30c405 100644 --- a/ILSpy/Search/SearchPaneModel.cs +++ b/ILSpy/Search/SearchPaneModel.cs @@ -20,7 +20,6 @@ using System.Windows.Input; using System.Windows.Media; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX.Search; diff --git a/ILSpy/SessionSettings.cs b/ILSpy/SessionSettings.cs index ad58d121f9..414d644cb9 100644 --- a/ILSpy/SessionSettings.cs +++ b/ILSpy/SessionSettings.cs @@ -28,7 +28,6 @@ using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Themes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Search; using ICSharpCode.ILSpyX.Settings; diff --git a/ILSpy/TaskHelper.cs b/ILSpy/TaskHelper.cs index e26eaf0627..5b33bff3c8 100644 --- a/ILSpy/TaskHelper.cs +++ b/ILSpy/TaskHelper.cs @@ -198,7 +198,7 @@ public static void IgnoreExceptions(this Task task) public static void HandleExceptions(this Task task) { task.Catch(exception => MainWindow.Instance.Dispatcher.BeginInvoke(new Action(delegate { - AvalonEditTextOutput output = new AvalonEditTextOutput(); + AvalonEditTextOutput output = new(); output.Write(exception.ToString()); Docking.DockWorkspace.Instance.ShowText(output); }))).IgnoreExceptions(); diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 1124a73e5e..842fc860f8 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -52,11 +52,11 @@ using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Documentation; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.AvalonEdit; using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; @@ -414,7 +414,7 @@ void ToolTipClosed(object? sender, EventArgs e) } else if (segment.Reference is EntityReference unresolvedEntity) { - var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.CurrentAssemblyList); + var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList); if (module == null) return null; var typeSystem = new DecompilerTypeSystem(module, @@ -474,7 +474,7 @@ void ToolTipClosed(object? sender, EventArgs e) IEntity? ResolveReference(string idString) { - return MainWindow.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.CurrentAssemblyList.GetAssemblies()); + return AssemblyListPaneModel.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()); } } @@ -1001,7 +1001,7 @@ internal void JumpToReference(ReferenceSegment referenceSegment, bool openInNewT return; } } - MainWindow.Instance.JumpToReference(reference, openInNewTab); + MessageBus.Send(this, new NavigateToReferenceEventArgs(reference, openInNewTab)); } Point? mouseDownPos; diff --git a/ILSpy/TextView/DocumentationUIBuilder.cs b/ILSpy/TextView/DocumentationUIBuilder.cs index bc7a9a4092..4d0c5eb789 100644 --- a/ILSpy/TextView/DocumentationUIBuilder.cs +++ b/ILSpy/TextView/DocumentationUIBuilder.cs @@ -37,7 +37,6 @@ using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpy.Util; namespace ICSharpCode.ILSpy.TextView { @@ -339,7 +338,7 @@ Inline ConvertReference(IEntity referencedEntity) { var h = new Hyperlink(new Run(ambience.ConvertSymbol(referencedEntity))); h.Click += (sender, e) => { - MainWindow.Instance.JumpToReference(referencedEntity); + MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity)); }; return h; } @@ -382,7 +381,7 @@ void AddSee(XmlDocumentationElement element) { Hyperlink link = new Hyperlink(); link.Click += (sender, e) => { - MainWindow.Instance.JumpToReference(referencedEntity); + MessageBus.Send(this, new NavigateToReferenceEventArgs(referencedEntity)); }; AddSpan(link, element.Children); } diff --git a/ILSpy/Themes/WindowStyleManagerBehavior.cs b/ILSpy/Themes/WindowStyleManagerBehavior.cs index 60e771f1a4..681fbf97eb 100644 --- a/ILSpy/Themes/WindowStyleManagerBehavior.cs +++ b/ILSpy/Themes/WindowStyleManagerBehavior.cs @@ -24,7 +24,6 @@ using System.Windows.Media; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpy.Util; using TomsToolbox.Essentials; using TomsToolbox.Wpf; diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index 9808ddcfd3..c7668c1c54 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -24,10 +24,9 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; using ICSharpCode.ILSpyX.TreeView; +using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; namespace ICSharpCode.ILSpy.TreeNodes { @@ -87,13 +86,10 @@ public override bool CanDrop(IPlatformDragEventArgs e, int index) e.Effects = XPlatDragDropEffects.Move | XPlatDragDropEffects.Copy | XPlatDragDropEffects.Link; if (e.Data.GetDataPresent(AssemblyTreeNode.DataFormat)) return true; - else if (e.Data.GetDataPresent(DataFormats.FileDrop)) + if (e.Data.GetDataPresent(DataFormats.FileDrop)) return true; - else - { - e.Effects = XPlatDragDropEffects.None; - return false; - } + e.Effects = XPlatDragDropEffects.None; + return false; } public override void Drop(IPlatformDragEventArgs e, int index) @@ -110,8 +106,8 @@ public override void Drop(IPlatformDragEventArgs e, int index) .Distinct() .ToArray(); assemblyList.Move(assemblies, index); - var nodes = assemblies.SelectArray(MainWindow.Instance.FindTreeNode); - MainWindow.Instance.SelectNodes(nodes); + var nodes = assemblies.SelectArray(MainWindow.Instance.AssemblyTreeModel.FindTreeNode); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); } } @@ -187,7 +183,7 @@ public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) if (bundle == null) return null; bundle.EnsureLazyChildren(); - foreach (var node in ILSpyX.TreeView.TreeTraversal.PreOrder(bundle.Children, ExpandAndGetChildren).OfType()) + foreach (var node in TreeTraversal.PreOrder(bundle.Children, ExpandAndGetChildren).OfType()) { if (node.LoadedAssembly == asm) return node; diff --git a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs index 25602d8757..57a326353e 100644 --- a/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyReferenceTreeNode.cs @@ -26,7 +26,6 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.Themes; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; namespace ICSharpCode.ILSpy.TreeNodes diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index d2f102ef2f..acbe3fcf54 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -30,10 +30,10 @@ using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Controls.TreeView; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.FileLoaders; @@ -630,13 +630,13 @@ public void Execute(TextViewContext context) { foreach (var node in context.SelectedTreeNodes) { - paths.Add(MainWindow.GetPathForNode(node)); + paths.Add(AssemblyListPaneModel.GetPathForNode(node)); var la = ((AssemblyTreeNode)node).LoadedAssembly; la.AssemblyList.ReloadAssembly(la.FileName); } } - MainWindow.Instance.SelectNodes(paths.Select(p => MainWindow.Instance.FindNodeByPath(p, true)).ToArray()); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(paths.Select(p => MainWindow.Instance.AssemblyTreeModel.FindNodeByPath(p, true)).ToArray()); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } } @@ -676,7 +676,7 @@ public async void Execute(TextViewContext context) } } await Task.WhenAll(tasks); - MainWindow.Instance.RefreshDecompiledView(); + MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView(); } } @@ -711,7 +711,7 @@ public void Execute(TextViewContext context) node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded)); } } - MainWindow.Instance.CurrentAssemblyList.RefreshSave(); + MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.RefreshSave(); } } diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index e3958a1bb1..3c319ab545 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -27,7 +27,6 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Abstractions; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; using ICSharpCode.ILSpyX.TreeView; @@ -72,8 +71,8 @@ public virtual bool View(ViewModels.TabPageModel tabPage) public override void ActivateItemSecondary(IPlatformRoutedEventArgs e) { - MainWindow.Instance.SelectNode(this, inNewTabPage: true); - MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.RefreshDecompiledView); + MainWindow.Instance.AssemblyTreeModel.SelectNode(this, inNewTabPage: true); + MainWindow.Instance.Dispatcher.BeginInvoke(DispatcherPriority.Background, (Action)MainWindow.Instance.AssemblyTreeModel.RefreshDecompiledView); } /// diff --git a/ILSpy/Util/MessageBus.cs b/ILSpy/Util/MessageBus.cs index 7573cb33c7..40d8743b8d 100644 --- a/ILSpy/Util/MessageBus.cs +++ b/ILSpy/Util/MessageBus.cs @@ -57,4 +57,13 @@ public class LanguageSettingsChangedEventArgs(PropertyChangedEventArgs e) : Wrap public class SessionSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs(e); public class DockWorkspaceActiveTabPageChangedEventArgs : EventArgs; + + public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs + { + public object Reference { get; } = reference; + + public bool InNewTabPage { get; } = inNewTabPage; + } + + public class AssemblyTreeSelectionChangedEventArgs() : EventArgs; } \ No newline at end of file diff --git a/ILSpy/Util/NavigationHistoryService.cs b/ILSpy/Util/NavigationHistoryService.cs new file mode 100644 index 0000000000..a13304663f --- /dev/null +++ b/ILSpy/Util/NavigationHistoryService.cs @@ -0,0 +1,48 @@ +using System; + +namespace ICSharpCode.ILSpy.Util +{ + public class NavigationHistoryService + { + readonly NavigationHistory history = new(); + + public static NavigationHistoryService Instance { get; } = new(); + + public bool CanNavigateBack => history.CanNavigateBack; + public bool CanNavigateForward => history.CanNavigateForward; + + private NavigationHistoryService() + { + } + + public void UpdateCurrent(NavigationState navigationState) + { + history.UpdateCurrent(navigationState); + } + + public void Record(NavigationState navigationState) + { + history.Record(navigationState); + } + + public NavigationState GoForward() + { + return history.GoForward(); + } + + public NavigationState GoBack() + { + return history.GoBack(); + } + + public void Clear() + { + history.Clear(); + } + + public void RemoveAll(Predicate predicate) + { + history.RemoveAll(predicate); + } + } +} diff --git a/ILSpy/ViewModels/AssemblyListPaneModel.cs b/ILSpy/ViewModels/AssemblyListPaneModel.cs deleted file mode 100644 index d6156cf8fe..0000000000 --- a/ILSpy/ViewModels/AssemblyListPaneModel.cs +++ /dev/null @@ -1,41 +0,0 @@ -// Copyright (c) 2019 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.ComponentModel.Composition; -using System.Windows; -using System.Windows.Input; - -using ICSharpCode.ILSpy.Properties; - -namespace ICSharpCode.ILSpy.ViewModels -{ - [ExportToolPane] - [PartCreationPolicy(CreationPolicy.Shared)] - public class AssemblyListPaneModel : ToolPaneModel - { - public const string PaneContentId = "assemblyListPane"; - - public AssemblyListPaneModel() - { - Title = Resources.Assemblies; - ContentId = PaneContentId; - IsCloseable = false; - ShortcutKey = new KeyGesture(Key.F6); - } - } -} diff --git a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs index ec039552f3..76ab7aedb5 100644 --- a/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs +++ b/ILSpy/ViewModels/ManageAssemblyListsViewModel.cs @@ -27,7 +27,6 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy.ViewModels diff --git a/ILSpy/ViewModels/PaneModel.cs b/ILSpy/ViewModels/PaneModel.cs index 8663688e44..f9ef1b90e5 100644 --- a/ILSpy/ViewModels/PaneModel.cs +++ b/ILSpy/ViewModels/PaneModel.cs @@ -21,7 +21,7 @@ using System.Windows; using System.Windows.Input; -using ICSharpCode.ILSpy.Docking; +using ICSharpCode.Decompiler.IL; using TomsToolbox.Wpf; @@ -29,6 +29,13 @@ namespace ICSharpCode.ILSpy.ViewModels { public abstract class PaneModel : ObservableObject { + private Throttle titleChangeThrottle; + + protected PaneModel() + { + titleChangeThrottle = new Throttle(() => OnPropertyChanged(nameof(Title))); + } + class CloseCommandImpl : ICommand { readonly PaneModel model; @@ -107,7 +114,10 @@ public string ContentId { public string Title { get => title; - set => SetProperty(ref title, value); + set { + title = value; + titleChangeThrottle.Tick(); + } } } diff --git a/ILSpy/ViewModels/TabPageModel.cs b/ILSpy/ViewModels/TabPageModel.cs index 10c28d47c0..abcaa46660 100644 --- a/ILSpy/ViewModels/TabPageModel.cs +++ b/ILSpy/ViewModels/TabPageModel.cs @@ -20,8 +20,6 @@ using System.Threading.Tasks; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; -using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy.ViewModels { diff --git a/ILSpy/Views/DebugSteps.xaml.cs b/ILSpy/Views/DebugSteps.xaml.cs index b619cc6b71..ff82daeba0 100644 --- a/ILSpy/Views/DebugSteps.xaml.cs +++ b/ILSpy/Views/DebugSteps.xaml.cs @@ -8,7 +8,6 @@ using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.IL.Transforms; using ICSharpCode.ILSpy.Docking; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Wpf.Composition.Mef; @@ -35,8 +34,8 @@ public DebugSteps() #if DEBUG MessageBus.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); + MessageBus.Subscribers += SelectionChanged; - MainWindow.Instance.SelectionChanged += SelectionChanged; writingOptions.PropertyChanged += WritingOptions_PropertyChanged; if (SettingsService.Instance.SessionSettings.LanguageSettings.Language is ILAstLanguage l) @@ -53,7 +52,7 @@ private void WritingOptions_PropertyChanged(object sender, System.ComponentModel DecompileAsync(lastSelectedStep); } - private void SelectionChanged(object sender, SelectionChangedEventArgs e) + private void SelectionChanged(object sender, EventArgs e) { Dispatcher.Invoke(() => { tree.ItemsSource = null; @@ -123,8 +122,8 @@ void DecompileAsync(int step, bool isDebug = false) lastSelectedStep = step; var window = MainWindow.Instance; var state = DockWorkspace.Instance.ActiveTabPage.GetState(); - DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(window.CurrentLanguage, window.SelectedNodes, - new DecompilationOptions(window.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { + DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(window.AssemblyTreeModel.CurrentLanguage, window.AssemblyTreeModel.SelectedNodes, + new DecompilationOptions(window.AssemblyTreeModel.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { StepLimit = step, IsDebug = isDebug, TextViewState = state as TextView.DecompilerTextViewState diff --git a/TestPlugin/MainMenuCommand.cs b/TestPlugin/MainMenuCommand.cs index bb4a041f8c..f2909e15b7 100644 --- a/TestPlugin/MainMenuCommand.cs +++ b/TestPlugin/MainMenuCommand.cs @@ -23,7 +23,7 @@ public class UnloadAllAssembliesCommand : SimpleCommand { public override void Execute(object parameter) { - foreach (var loadedAssembly in MainWindow.Instance.CurrentAssemblyList.GetAssemblies()) + foreach (var loadedAssembly in MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()) { loadedAssembly.AssemblyList.Unload(loadedAssembly); } From f8883211b8a26b6b01623c70f64a2dca2412d9ad Mon Sep 17 00:00:00 2001 From: tom-englert Date: Mon, 2 Sep 2024 13:27:57 +0200 Subject: [PATCH 02/14] Move menu/toolbar logic from MainWindow to separate service. --- ILSpy/AssemblyTree/AssemblyListPaneModel.cs | 17 +- ILSpy/Commands/DecompileAllCommand.cs | 10 +- ILSpy/Commands/DisassembleAllCommand.cs | 15 +- ILSpy/Commands/GeneratePdbContextMenuEntry.cs | 6 +- ILSpy/Commands/SaveCodeContextMenuEntry.cs | 11 +- ILSpy/ContextMenuEntry.cs | 2 +- ILSpy/Docking/DockWorkspace.cs | 7 + ILSpy/Languages/ILLanguage.cs | 6 +- ILSpy/MainWindow.xaml.cs | 432 +----------------- ILSpy/Options/OptionsDialog.xaml.cs | 2 +- ILSpy/SolutionWriter.cs | 3 +- ILSpy/TreeNodes/AssemblyTreeNode.cs | 3 +- ILSpy/Util/MenuService.cs | 357 +++++++++++++++ ILSpy/Util/ResourceHelper.cs | 15 + ILSpy/Util/SettingsService.cs | 10 +- 15 files changed, 446 insertions(+), 450 deletions(-) create mode 100644 ILSpy/Util/MenuService.cs create mode 100644 ILSpy/Util/ResourceHelper.cs diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs index cda948a4cd..adbdd5aeec 100644 --- a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs +++ b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs @@ -766,20 +766,21 @@ public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool if (SelectedItems.Count == 0 && refreshInProgress) return; + var activeTabPage = DockWorkspace.Instance.ActiveTabPage; + if (recordHistory) { - var tabPage = DockWorkspace.Instance.ActiveTabPage; - var currentState = tabPage.GetState(); + var currentState = activeTabPage.GetState(); if (currentState != null) - history.UpdateCurrent(new NavigationState(tabPage, currentState)); - history.Record(new NavigationState(tabPage, SelectedItems)); + history.UpdateCurrent(new NavigationState(activeTabPage, currentState)); + history.Record(new NavigationState(activeTabPage, SelectedItems)); } - DockWorkspace.Instance.ActiveTabPage.SupportsLanguageSwitching = true; + activeTabPage.SupportsLanguageSwitching = true; if (SelectedItems.Count == 1) { - if (SelectedItem is ILSpyTreeNode node && node.View(DockWorkspace.Instance.ActiveTabPage)) + if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage)) return; } if (newState?.ViewedUri != null) @@ -787,9 +788,9 @@ public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool MainWindow.Instance.NavigateTo(new(newState.ViewedUri, null), recordHistory: false); return; } - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(activeTabPage); options.TextViewState = newState; - DockWorkspace.Instance.ActiveTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); + activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); } public void RefreshDecompiledView() diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index 3a41480582..ef8be645e0 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -26,6 +26,7 @@ using System.Threading.Tasks; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpyX; @@ -59,7 +60,7 @@ public override void Execute(object parameter) { try { - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.CancellationToken = ct; options.FullDecompilation = true; new CSharpLanguage().DecompileAssembly(asm, new PlainTextOutput(writer), options); @@ -96,8 +97,9 @@ public override void Execute(object parameter) const int numRuns = 100; var language = SettingsService.Instance.SessionSettings.LanguageSettings.Language; var nodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes.ToArray(); - var options = MainWindow.Instance.CreateDecompilationOptions(); - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { + DockWorkspace dockWorkspace = DockWorkspace.Instance; + var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { options.CancellationToken = ct; Stopwatch w = Stopwatch.StartNew(); for (int i = 0; i < numRuns; ++i) @@ -112,7 +114,7 @@ public override void Execute(object parameter) double msPerRun = w.Elapsed.TotalMilliseconds / numRuns; output.Write($"Average time: {msPerRun.ToString("f1")}ms\n"); return output; - }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); + }, ct)).Then(output => dockWorkspace.ShowText(output)).HandleExceptions(); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index aa81bd855e..60c97d227e 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -26,7 +26,6 @@ using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy { @@ -41,12 +40,14 @@ public override bool CanExecute(object parameter) public override void Execute(object parameter) { - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { - AvalonEditTextOutput output = new AvalonEditTextOutput(); + var dockWorkspace = Docking.DockWorkspace.Instance; + + dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { + AvalonEditTextOutput output = new(); Parallel.ForEach( Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies(), loadBalance: true), - new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, - delegate (LoadedAssembly asm) { + new() { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, + asm => { if (!asm.HasLoadError) { Stopwatch w = Stopwatch.StartNew(); @@ -55,7 +56,7 @@ public override void Execute(object parameter) { try { - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; new ILLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), options); @@ -79,7 +80,7 @@ public override void Execute(object parameter) } }); return output; - }, ct)).Then(output => Docking.DockWorkspace.Instance.ShowText(output)).HandleExceptions(); + }, ct)).Then(dockWorkspace.ShowText).HandleExceptions(); } } } diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index fff3a9fb32..4eb7449920 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -29,6 +29,7 @@ using ICSharpCode.Decompiler.CSharp.ProjectDecompiler; using ICSharpCode.Decompiler.DebugInfo; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; @@ -73,9 +74,10 @@ internal static void GeneratePdbForAssembly(LoadedAssembly assembly) dlg.InitialDirectory = Path.GetDirectoryName(assembly.FileName); if (dlg.ShowDialog() != true) return; - DecompilationOptions options = MainWindow.Instance.CreateDecompilationOptions(); + DockWorkspace dockWorkspace = DockWorkspace.Instance; + DecompilationOptions options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); string fileName = dlg.FileName; - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { + dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Stopwatch stopwatch = Stopwatch.StartNew(); options.CancellationToken = ct; diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index 9b013daad3..41d45728e5 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -32,6 +32,8 @@ using ICSharpCode.ILSpyX.TreeView; using System.ComponentModel.Composition; +using ICSharpCode.ILSpy.Docking; + namespace ICSharpCode.ILSpy.TextView { @@ -61,8 +63,11 @@ public static bool CanExecute(IReadOnlyList selectedNodes) public static void Execute(IReadOnlyList selectedNodes) { - var currentLanguage = SettingsService.Instance.SessionSettings.LanguageSettings.Language; - var tabPage = Docking.DockWorkspace.Instance.ActiveTabPage; + var settingsService = SettingsService.Instance; + var dockWorkspace = Docking.DockWorkspace.Instance; + + var currentLanguage = settingsService.SessionSettings.LanguageSettings.Language; + var tabPage = dockWorkspace.ActiveTabPage; tabPage.ShowTextView(textView => { if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) { @@ -87,7 +92,7 @@ public static void Execute(IReadOnlyList selectedNodes) // Fallback: if nobody was able to handle the request, use default behavior. // try to save all nodes to disk. - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; textView.SaveToDisk(currentLanguage, selectedNodes.OfType(), options); }); diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index 9a50cb6498..81022b9dc7 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -345,7 +345,7 @@ void BuildMenu(IExport[] menuGroup needSeparatorForCategory = false; } MenuItem menuItem = new MenuItem(); - menuItem.Header = MainWindow.GetResourceString(entryPair.Metadata.Header); + menuItem.Header = ResourceHelper.GetString(entryPair.Metadata.Header); menuItem.InputGestureText = entryPair.Metadata.InputGestureText; if (!string.IsNullOrEmpty(entryPair.Metadata.Icon)) { diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index c0a2b40816..232a49e35d 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -34,7 +34,9 @@ using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Composition; using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Docking @@ -43,6 +45,8 @@ public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy { private static SessionSettings SessionSettings => SettingsService.Instance.SessionSettings; + private readonly IExportProvider exportProvider = App.ExportProvider; + public static readonly DockWorkspace Instance = new(); private DockWorkspace() @@ -148,6 +152,9 @@ public TabPageModel ActiveTabPage { public void InitializeLayout(DockingManager manager) { + var toolPanes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); + ToolPanes.AddRange(toolPanes); + manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); serializer.LayoutSerializationCallback += LayoutSerializationCallback; diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs index f2638bcabb..dae3031245 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -29,6 +29,7 @@ using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpyX; @@ -197,7 +198,10 @@ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput public override RichText GetRichTextTooltip(IEntity entity) { var output = new AvalonEditTextOutput() { IgnoreNewLineAndIndent = true }; - var disasm = CreateDisassembler(output, MainWindow.Instance.CreateDecompilationOptions()); + var settingsService = SettingsService.Instance; + var dockWorkspace = DockWorkspace.Instance; + + var disasm = CreateDisassembler(output, settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage)); MetadataFile module = entity.ParentModule?.MetadataFile; if (module == null) { diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 5a4d6ab6c4..64f78260f9 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -17,43 +17,36 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; -using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics; +using System.Drawing; using System.Globalization; using System.IO; using System.Linq; using System.Threading; using System.Threading.Tasks; using System.Windows; -using System.Windows.Controls; -using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Navigation; using AvalonDock.Layout.Serialization; -using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AssemblyTree; -using ICSharpCode.ILSpy.Commands; using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX.FileLoaders; using ICSharpCode.ILSpyX.Settings; -using ICSharpCode.ILSpyX.Extensions; +using ICSharpCode.ILSpyX.TreeView; using Microsoft.Win32; -using ICSharpCode.ILSpyX.TreeView; -using TomsToolbox.Composition; +using Screen = System.Windows.Forms.Screen; namespace ICSharpCode.ILSpy { @@ -76,13 +69,6 @@ public AssemblyListPaneModel AssemblyTreeModel { } } - public DecompilationOptions CreateDecompilationOptions() - { - var decompilerView = DockWorkspace.Instance.ActiveTabPage.Content as IProgress; - - return new(AssemblyTreeModel.CurrentLanguageVersion, SettingsService.Instance.DecompilerSettings, SettingsService.Instance.DisplaySettings) { Progress = decompilerView }; - } - public MainWindow() { instance = this; @@ -104,33 +90,16 @@ public MainWindow() Thread.CurrentThread.CurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture); } InitializeComponent(); - InitToolPanes(); + DockWorkspace.Instance.InitializeLayout(dockManager); - MessageBus.Subscribers += DockWorkspace_ActiveTabPageChanged; + MenuService.Instance.Init(mainMenu, toolBar, InputBindings); - InitMainMenu(); - InitWindowMenu(); - InitToolbar(); InitFileLoaders(); this.Loaded += MainWindow_Loaded; } - private void DockWorkspace_ActiveTabPageChanged(object sender, EventArgs e) - { - DockWorkspace dock = DockWorkspace.Instance; - - var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) - { - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - } - } - } - void SetWindowBounds(Rect bounds) { this.Left = bounds.Left; @@ -139,367 +108,6 @@ void SetWindowBounds(Rect bounds) this.Height = bounds.Height; } - #region Toolbar extensibility - - void InitToolbar() - { - int navigationPos = 0; - int openPos = 1; - var toolbarCommands = App.ExportProvider.GetExports("ToolbarCommand"); - foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) - { - if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) - { - foreach (var command in commandGroup) - { - toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); - openPos++; - } - } - else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) - { - foreach (var command in commandGroup) - { - toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); - } - } - else - { - toolBar.Items.Add(new Separator()); - foreach (var command in commandGroup) - { - toolBar.Items.Add(MakeToolbarItem(command)); - } - } - } - - } - - Button MakeToolbarItem(IExport command) - { - return new Button { - Style = ThemeManager.Current.CreateToolBarButtonStyle(), - Command = CommandWrapper.Unwrap(command.Value), - ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), - Tag = command.Metadata.Tag, - Content = new System.Windows.Controls.Image { - Width = 16, - Height = 16, - Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) - } - }; - } - #endregion - - #region Main Menu extensibility - - void InitMainMenu() - { - var mainMenuCommands = App.ExportProvider.GetExports("MainMenuCommand"); - // Start by constructing the individual flat menus - var parentMenuItems = new Dictionary(); - var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata.MenuOrder).GroupBy(c => c.Metadata.ParentMenuID); - foreach (var menu in menuGroups) - { - // Get or add the target menu item and add all items grouped by menu category - var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key); - foreach (var category in menu.GroupBy(c => c.Metadata.MenuCategory)) - { - if (parentMenuItem.Items.Count > 0) - { - parentMenuItem.Items.Add(new Separator { Tag = category.Key }); - } - foreach (var entry in category) - { - if (menuGroups.Any(g => g.Key == entry.Metadata.MenuID)) - { - var menuItem = GetOrAddParentMenuItem(entry.Metadata.MenuID, entry.Metadata.Header); - // replace potential dummy text with real name - menuItem.Header = GetResourceString(entry.Metadata.Header); - parentMenuItem.Items.Add(menuItem); - } - else - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = CommandWrapper.Unwrap(entry.Value); - menuItem.Tag = entry.Metadata.MenuID; - menuItem.Header = GetResourceString(entry.Metadata.Header); - if (!string.IsNullOrEmpty(entry.Metadata.MenuIcon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) - }; - } - - menuItem.IsEnabled = entry.Metadata.IsEnabled; - if (entry.Value is ToggleableCommand toggle) - { - menuItem.IsCheckable = true; - menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); - } - - menuItem.InputGestureText = entry.Metadata.InputGestureText; - parentMenuItem.Items.Add(menuItem); - } - } - } - } - - foreach (var (key, item) in parentMenuItems) - { - if (item.Parent == null) - { - mainMenu.Items.Add(item); - } - } - - MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) - { - if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) - { - var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuID); - if (topLevelMenuItem == null) - { - parentMenuItem = new MenuItem(); - parentMenuItem.Header = GetResourceString(resourceKey); - parentMenuItem.Tag = menuID; - parentMenuItems.Add(menuID, parentMenuItem); - } - else - { - parentMenuItems.Add(menuID, topLevelMenuItem); - parentMenuItem = topLevelMenuItem; - } - } - return parentMenuItem; - } - } - - internal static string GetResourceString(string key) - { - if (string.IsNullOrEmpty(key)) - { - return null; - } - string value = Properties.Resources.ResourceManager.GetString(key); - if (!string.IsNullOrEmpty(value)) - { - return value; - } - return key; - } - #endregion - - #region Tool Pane extensibility - - private void InitToolPanes() - { - var toolPanes = App.ExportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); - - DockWorkspace.Instance.ToolPanes.AddRange(toolPanes); - } - - private void InitWindowMenu() - { - var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - Separator separatorBeforeTools, separatorBeforeDocuments; - windowMenuItem.Items.Add(separatorBeforeTools = new Separator()); - windowMenuItem.Items.Add(separatorBeforeDocuments = new Separator()); - - var dock = DockWorkspace.Instance; - dock.ToolPanes.CollectionChanged += ToolsChanged; - dock.TabPages.CollectionChanged += TabsChanged; - - ToolsChanged(dock.ToolPanes, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - TabsChanged(dock.TabPages, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); - - void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (ToolPaneModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (ToolPaneModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[0]; - item.Tag = null; - windowMenuItem.Items.RemoveAt(i); - endIndex--; - } - insertionIndex = endIndex; - foreach (ToolPaneModel pane in dock.ToolPanes) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(ToolPaneModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); - menuItem.Header = pane.Title; - menuItem.Tag = pane; - var shortcutKey = pane.ShortcutKey; - if (shortcutKey != null) - { - InputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); - } - if (!string.IsNullOrEmpty(pane.Icon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(pane, pane.Icon) - }; - } - - return menuItem; - } - } - - void TabsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.Count; - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (TabPageModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (TabPageModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - pane.PropertyChanged -= TabPageChanged; - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - windowMenuItem.Items.RemoveAt(i); - ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; - endIndex--; - } - insertionIndex = endIndex; - foreach (TabPageModel pane in dock.TabPages) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(TabPageModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = new TabPageCommand(pane); - menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; - menuItem.Tag = pane; - menuItem.IsCheckable = true; - - return menuItem; - } - } - - static void TabPageChanged(object sender, PropertyChangedEventArgs e) - { - var windowMenuItem = Instance.mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag == sender) - { - string title = ((TabPageModel)sender).Title; - menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; - } - } - } - } - - public void ShowInTopPane(string title, object content) - { - var model = DockWorkspace.Instance.ToolPanes.OfType().FirstOrDefault(p => p.Content == content); - if (model == null) - { - model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Top); - DockWorkspace.Instance.ToolPanes.Add(model); - } - model.Show(); - } - - public void ShowInBottomPane(string title, object content) - { - var model = DockWorkspace.Instance.ToolPanes.OfType().FirstOrDefault(p => p.Content == content); - if (model == null) - { - model = new LegacyToolPaneModel(title, content, LegacyToolPaneLocation.Bottom); - DockWorkspace.Instance.ToolPanes.Add(model); - } - model.Show(); - } - #endregion - #region File Loader extensibility void InitFileLoaders() @@ -514,20 +122,22 @@ void InitFileLoaders() #endregion #region Message Hook + protected override void OnSourceInitialized(EventArgs e) { base.OnSourceInitialized(e); - PresentationSource source = PresentationSource.FromVisual(this); + + var source = PresentationSource.FromVisual(this); var sessionSettings = SettingsService.Instance.SessionSettings; // Validate and Set Window Bounds - Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source.CompositionTarget.TransformToDevice); - var boundsRect = new System.Drawing.Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); + Rect bounds = Rect.Transform(sessionSettings.WindowBounds, source?.CompositionTarget?.TransformToDevice ?? Matrix.Identity); + var boundsRect = new Rectangle((int)bounds.Left, (int)bounds.Top, (int)bounds.Width, (int)bounds.Height); bool boundsOK = false; - foreach (var screen in System.Windows.Forms.Screen.AllScreens) + foreach (var screen in Screen.AllScreens) { - var intersection = System.Drawing.Rectangle.Intersect(boundsRect, screen.WorkingArea); + var intersection = Rectangle.Intersect(boundsRect, screen.WorkingArea); if (intersection.Width > 10 && intersection.Height > 10) boundsOK = true; } @@ -853,23 +463,5 @@ private string GetAutoLoadedAssemblyNode(SharpTreeNode node) return loadedAssy.FileName; } - - public void SetStatus(string status, Brush foreground) - { - if (this.statusBar.Visibility == Visibility.Collapsed) - this.statusBar.Visibility = Visibility.Visible; - this.statusLabel.Foreground = foreground; - this.statusLabel.Text = status; - } - - public ItemCollection GetMainMenuItems() - { - return mainMenu.Items; - } - - public ItemCollection GetToolBarItems() - { - return toolBar.Items; - } } } diff --git a/ILSpy/Options/OptionsDialog.xaml.cs b/ILSpy/Options/OptionsDialog.xaml.cs index 72413f6725..191f41ef79 100644 --- a/ILSpy/Options/OptionsDialog.xaml.cs +++ b/ILSpy/Options/OptionsDialog.xaml.cs @@ -59,7 +59,7 @@ public OptionsDialog() ILSpySettings settings = ILSpySettings.Load(); foreach (var optionPage in optionPages.OrderBy(p => p.Metadata.Order)) { - var tabItem = new TabItemViewModel(MainWindow.GetResourceString(optionPage.Metadata.Title), optionPage.Value); + var tabItem = new TabItemViewModel(Util.ResourceHelper.GetString(optionPage.Metadata.Title), optionPage.Value); tabControl.Items.Add(tabItem); diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs index a3f412d095..dd2361ddcc 100644 --- a/ILSpy/SolutionWriter.cs +++ b/ILSpy/SolutionWriter.cs @@ -28,6 +28,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpyX; @@ -212,7 +213,7 @@ void WriteProject(LoadedAssembly loadedAssembly, Language language, string targe using (var projectFileWriter = new StreamWriter(projectFileName)) { var projectFileOutput = new PlainTextOutput(projectFileWriter); - var options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; options.SaveAsProjectDirectory = targetDirectory; diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index acbe3fcf54..ad87b4d416 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -32,6 +32,7 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Controls.TreeView; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.ViewModels; @@ -546,7 +547,7 @@ public override bool Save(TabPageModel tabPage) dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; if (dlg.ShowDialog() == true) { - DecompilationOptions options = MainWindow.Instance.CreateDecompilationOptions(); + var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; if (dlg.FilterIndex == 1) { diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs new file mode 100644 index 0000000000..6220f73afd --- /dev/null +++ b/ILSpy/Util/MenuService.cs @@ -0,0 +1,357 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Globalization; +using System.Linq; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; + +using ICSharpCode.ILSpy.Commands; +using ICSharpCode.ILSpy.Docking; +using ICSharpCode.ILSpy.Themes; +using ICSharpCode.ILSpy.ViewModels; + +using TomsToolbox.Composition; + +namespace ICSharpCode.ILSpy.Util +{ + internal class MenuService + { + public static readonly MenuService Instance = new(); + + public void InitMainMenu(Menu mainMenu) + { + var mainMenuCommands = App.ExportProvider.GetExports("MainMenuCommand"); + // Start by constructing the individual flat menus + var parentMenuItems = new Dictionary(); + var menuGroups = mainMenuCommands.OrderBy(c => c.Metadata?.MenuOrder).GroupBy(c => c.Metadata?.ParentMenuID).ToArray(); + foreach (var menu in menuGroups) + { + // Get or add the target menu item and add all items grouped by menu category + var parentMenuItem = GetOrAddParentMenuItem(menu.Key, menu.Key); + foreach (var category in menu.GroupBy(c => c.Metadata?.MenuCategory)) + { + if (parentMenuItem.Items.Count > 0) + { + parentMenuItem.Items.Add(new Separator { Tag = category.Key }); + } + foreach (var entry in category) + { + if (menuGroups.Any(g => g.Key == entry.Metadata?.MenuID)) + { + var menuItem = GetOrAddParentMenuItem(entry.Metadata?.MenuID, entry.Metadata?.Header); + // replace potential dummy text with real name + menuItem.Header = ResourceHelper.GetString(entry.Metadata?.Header); + parentMenuItem.Items.Add(menuItem); + } + else + { + MenuItem menuItem = new MenuItem(); + menuItem.Command = CommandWrapper.Unwrap(entry.Value); + menuItem.Tag = entry.Metadata?.MenuID; + menuItem.Header = ResourceHelper.GetString(entry.Metadata?.Header); + if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(entry.Value, entry.Metadata.MenuIcon) + }; + } + + menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false; + if (entry.Value is ToggleableCommand toggle) + { + menuItem.IsCheckable = true; + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); + } + + menuItem.InputGestureText = entry.Metadata?.InputGestureText; + parentMenuItem.Items.Add(menuItem); + } + } + } + } + + foreach (var (key, item) in parentMenuItems) + { + if (item.Parent == null) + { + mainMenu.Items.Add(item); + } + } + + MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) + { + if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) + { + var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuID); + if (topLevelMenuItem == null) + { + parentMenuItem = new MenuItem(); + parentMenuItem.Header = ResourceHelper.GetString(resourceKey); + parentMenuItem.Tag = menuID; + parentMenuItems.Add(menuID, parentMenuItem); + } + else + { + parentMenuItems.Add(menuID, topLevelMenuItem); + parentMenuItem = topLevelMenuItem; + } + } + return parentMenuItem; + } + } + + public void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) + { + var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); + + var separatorBeforeTools = new Separator(); + var separatorBeforeDocuments = new Separator(); + + windowMenuItem.Items.Add(separatorBeforeTools); + windowMenuItem.Items.Add(separatorBeforeDocuments); + + var dock = DockWorkspace.Instance; + + dock.ToolPanes.CollectionChanged += ToolsChanged; + dock.TabPages.CollectionChanged += TabsChanged; + MessageBus.Subscribers += ActiveTabPageChanged; + + ToolsChanged(dock.ToolPanes, new(NotifyCollectionChangedAction.Reset)); + TabsChanged(dock.TabPages, new(NotifyCollectionChangedAction.Reset)); + + void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); + int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; + int insertionIndex; + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); + foreach (ToolPaneModel pane in e.NewItems) + { + MenuItem menuItem = CreateMenuItem(pane); + windowMenuItem.Items.Insert(insertionIndex, menuItem); + insertionIndex++; + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (ToolPaneModel pane in e.OldItems) + { + for (int i = endIndex - 1; i >= startIndex; i--) + { + MenuItem item = (MenuItem)windowMenuItem.Items[i]; + if (pane == item.Tag) + { + windowMenuItem.Items.RemoveAt(i); + item.Tag = null; + endIndex--; + break; + } + } + } + break; + case NotifyCollectionChangedAction.Replace: + break; + case NotifyCollectionChangedAction.Move: + break; + case NotifyCollectionChangedAction.Reset: + for (int i = endIndex - 1; i >= startIndex; i--) + { + MenuItem item = (MenuItem)windowMenuItem.Items[0]; + item.Tag = null; + windowMenuItem.Items.RemoveAt(i); + endIndex--; + } + insertionIndex = endIndex; + foreach (ToolPaneModel pane in dock.ToolPanes) + { + MenuItem menuItem = CreateMenuItem(pane); + windowMenuItem.Items.Insert(insertionIndex, menuItem); + insertionIndex++; + } + break; + } + + MenuItem CreateMenuItem(ToolPaneModel pane) + { + MenuItem menuItem = new MenuItem(); + menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); + menuItem.Header = pane.Title; + menuItem.Tag = pane; + var shortcutKey = pane.ShortcutKey; + if (shortcutKey != null) + { + inputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); + } + if (!string.IsNullOrEmpty(pane.Icon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(pane, pane.Icon) + }; + } + + return menuItem; + } + } + + void TabsChanged(object sender, NotifyCollectionChangedEventArgs e) + { + int endIndex = windowMenuItem.Items.Count; + int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; + int insertionIndex; + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); + foreach (TabPageModel pane in e.NewItems) + { + MenuItem menuItem = CreateMenuItem(pane); + pane.PropertyChanged += TabPageChanged; + windowMenuItem.Items.Insert(insertionIndex, menuItem); + insertionIndex++; + } + break; + case NotifyCollectionChangedAction.Remove: + foreach (TabPageModel pane in e.OldItems) + { + for (int i = endIndex - 1; i >= startIndex; i--) + { + MenuItem item = (MenuItem)windowMenuItem.Items[i]; + if (pane == item.Tag) + { + windowMenuItem.Items.RemoveAt(i); + pane.PropertyChanged -= TabPageChanged; + item.Tag = null; + endIndex--; + break; + } + } + } + break; + case NotifyCollectionChangedAction.Replace: + break; + case NotifyCollectionChangedAction.Move: + break; + case NotifyCollectionChangedAction.Reset: + for (int i = endIndex - 1; i >= startIndex; i--) + { + MenuItem item = (MenuItem)windowMenuItem.Items[i]; + windowMenuItem.Items.RemoveAt(i); + ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; + endIndex--; + } + insertionIndex = endIndex; + foreach (TabPageModel pane in dock.TabPages) + { + MenuItem menuItem = CreateMenuItem(pane); + pane.PropertyChanged += TabPageChanged; + windowMenuItem.Items.Insert(insertionIndex, menuItem); + insertionIndex++; + } + break; + } + + MenuItem CreateMenuItem(TabPageModel pane) + { + MenuItem menuItem = new MenuItem(); + menuItem.Command = new TabPageCommand(pane); + menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; + menuItem.Tag = pane; + menuItem.IsCheckable = true; + menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; + + return menuItem; + } + } + + void TabPageChanged(object sender, PropertyChangedEventArgs e) + { + var windowMenu = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); + foreach (MenuItem menuItem in windowMenu.Items.OfType()) + { + if (menuItem.IsCheckable && menuItem.Tag == sender) + { + string title = ((TabPageModel)sender).Title; + menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; + } + } + } + + void ActiveTabPageChanged(object sender, EventArgs e) + { + foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) + { + if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) + { + menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; + } + } + } + } + + public void InitToolbar(ToolBar toolBar) + { + int navigationPos = 0; + int openPos = 1; + var toolbarCommands = App.ExportProvider.GetExports("ToolbarCommand"); + foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) + { + if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) + { + foreach (var command in commandGroup) + { + toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); + openPos++; + } + } + else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) + { + foreach (var command in commandGroup) + { + toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); + } + } + else + { + toolBar.Items.Add(new Separator()); + foreach (var command in commandGroup) + { + toolBar.Items.Add(MakeToolbarItem(command)); + } + } + } + + } + + Button MakeToolbarItem(IExport command) + { + return new Button { + Style = ThemeManager.Current.CreateToolBarButtonStyle(), + Command = CommandWrapper.Unwrap(command.Value), + ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), + Tag = command.Metadata.Tag, + Content = new Image { + Width = 16, + Height = 16, + Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) + } + }; + } + + public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) + { + InitMainMenu(mainMenu); + InitWindowMenu(mainMenu, inputBindings); + InitToolbar(toolBar); + } + } +} diff --git a/ILSpy/Util/ResourceHelper.cs b/ILSpy/Util/ResourceHelper.cs new file mode 100644 index 0000000000..b37bb2cbb4 --- /dev/null +++ b/ILSpy/Util/ResourceHelper.cs @@ -0,0 +1,15 @@ +namespace ICSharpCode.ILSpy.Util +{ + internal static class ResourceHelper + { + internal static string GetString(string key) + { + if (string.IsNullOrEmpty(key)) + return null; + + string value = Properties.Resources.ResourceManager.GetString(key); + + return !string.IsNullOrEmpty(value) ? value : key; + } + } +} diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index 9068733f34..e843c85e35 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -1,5 +1,8 @@ -using ICSharpCode.Decompiler; +using System; + +using ICSharpCode.Decompiler; using ICSharpCode.ILSpy.Options; +using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; @@ -30,5 +33,10 @@ private SettingsService() public DisplaySettings DisplaySettings { get; } public AssemblyListManager AssemblyListManager { get; } + + public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) + { + return new(SessionSettings.LanguageSettings.LanguageVersion, DecompilerSettings, DisplaySettings) { Progress = tabPage.Content as IProgress }; + } } } From 3b2f44b1f0436047d94b87d72554e08f45bebdff Mon Sep 17 00:00:00 2001 From: tom-englert Date: Tue, 3 Sep 2024 09:59:50 +0200 Subject: [PATCH 03/14] Refactor window menu composition: simplify by using WPF patterns. --- ILSpy/Docking/PaneCollection.cs | 13 +- ILSpy/MainWindow.xaml | 60 ++-- ILSpy/Metadata/CorTables/PtrTableTreeNode.cs | 2 +- ILSpy/Util/MenuService.cs | 326 ++++++------------- 4 files changed, 146 insertions(+), 255 deletions(-) diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs index 72b1b851be..a8e397fe53 100644 --- a/ILSpy/Docking/PaneCollection.cs +++ b/ILSpy/Docking/PaneCollection.cs @@ -16,6 +16,7 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; @@ -25,10 +26,10 @@ namespace ICSharpCode.ILSpy.Docking { - public class PaneCollection : INotifyCollectionChanged, ICollection + public class PaneCollection : INotifyCollectionChanged, IList where T : PaneModel, new() { - private ObservableCollection observableCollection = new ObservableCollection(); + private readonly ObservableCollection observableCollection = []; public event NotifyCollectionChangedEventHandler CollectionChanged; @@ -46,7 +47,6 @@ public void Add(T item = null) item.IsVisible = true; item.IsActive = true; } - public int Count => observableCollection.Count; public bool IsReadOnly => false; public void Clear() => observableCollection.Clear(); @@ -55,5 +55,12 @@ public void Add(T item = null) public bool Remove(T item) => observableCollection.Remove(item); public IEnumerator GetEnumerator() => observableCollection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); + int IList.IndexOf(T item) => observableCollection.IndexOf(item); + void IList.Insert(int index, T item) => throw new NotImplementedException("Only Add is supported"); + void IList.RemoveAt(int index) => observableCollection.RemoveAt(index); + T IList.this[int index] { + get => observableCollection[index]; + set => observableCollection[index] = value; + } } } diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 62c51508ce..8b253493a8 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -1,27 +1,25 @@  - + @@ -78,9 +76,12 @@ - - - + + + @@ -101,9 +102,12 @@ - - - + + + diff --git a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs index 245507368d..cf196ef032 100644 --- a/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs +++ b/ILSpy/Metadata/CorTables/PtrTableTreeNode.cs @@ -112,7 +112,7 @@ struct PtrEntry public void OnHandleClick() { - MainWindow.Instance.JumpToReference(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata")); + MessageBus.Send(this, new NavigateToReferenceEventArgs(new EntityReference(metadataFile, handlePtr.Handle, protocol: "metadata"))); } string handleTooltip; diff --git a/ILSpy/Util/MenuService.cs b/ILSpy/Util/MenuService.cs index 6220f73afd..7766ab77a3 100644 --- a/ILSpy/Util/MenuService.cs +++ b/ILSpy/Util/MenuService.cs @@ -1,9 +1,7 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.ComponentModel; +using System.Collections.Generic; using System.Globalization; using System.Linq; +using System.Windows; using System.Windows.Controls; using System.Windows.Data; using System.Windows.Input; @@ -14,6 +12,8 @@ using ICSharpCode.ILSpy.ViewModels; using TomsToolbox.Composition; +using TomsToolbox.ObservableCollections; +using TomsToolbox.Wpf.Converters; namespace ICSharpCode.ILSpy.Util { @@ -21,7 +21,16 @@ internal class MenuService { public static readonly MenuService Instance = new(); - public void InitMainMenu(Menu mainMenu) + private readonly DockWorkspace dockWorkspace = DockWorkspace.Instance; + + public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) + { + InitMainMenu(mainMenu); + InitWindowMenu(mainMenu, inputBindings); + InitToolbar(toolBar); + } + + static void InitMainMenu(Menu mainMenu) { var mainMenuCommands = App.ExportProvider.GetExports("MainMenuCommand"); // Start by constructing the individual flat menus @@ -48,10 +57,11 @@ public void InitMainMenu(Menu mainMenu) } else { - MenuItem menuItem = new MenuItem(); - menuItem.Command = CommandWrapper.Unwrap(entry.Value); - menuItem.Tag = entry.Metadata?.MenuID; - menuItem.Header = ResourceHelper.GetString(entry.Metadata?.Header); + var menuItem = new MenuItem { + Command = CommandWrapper.Unwrap(entry.Value), + Tag = entry.Metadata?.MenuID, + Header = ResourceHelper.GetString(entry.Metadata?.Header) + }; if (!string.IsNullOrEmpty(entry.Metadata?.MenuIcon)) { menuItem.Icon = new Image { @@ -62,7 +72,7 @@ public void InitMainMenu(Menu mainMenu) } menuItem.IsEnabled = entry.Metadata?.IsEnabled ?? false; - if (entry.Value is ToggleableCommand toggle) + if (entry.Value is ToggleableCommand) { menuItem.IsCheckable = true; menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding("IsChecked") { Source = entry.Value, Mode = BindingMode.OneWay }); @@ -75,7 +85,7 @@ public void InitMainMenu(Menu mainMenu) } } - foreach (var (key, item) in parentMenuItems) + foreach (var item in parentMenuItems.Values) { if (item.Parent == null) { @@ -83,21 +93,22 @@ public void InitMainMenu(Menu mainMenu) } } - MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) + MenuItem GetOrAddParentMenuItem(string menuId, string resourceKey) { - if (!parentMenuItems.TryGetValue(menuID, out var parentMenuItem)) + if (!parentMenuItems.TryGetValue(menuId, out var parentMenuItem)) { - var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuID); + var topLevelMenuItem = mainMenu.Items.OfType().FirstOrDefault(m => (string)m.Tag == menuId); if (topLevelMenuItem == null) { - parentMenuItem = new MenuItem(); - parentMenuItem.Header = ResourceHelper.GetString(resourceKey); - parentMenuItem.Tag = menuID; - parentMenuItems.Add(menuID, parentMenuItem); + parentMenuItem = new() { + Header = ResourceHelper.GetString(resourceKey), + Tag = menuId + }; + parentMenuItems.Add(menuId, parentMenuItem); } else { - parentMenuItems.Add(menuID, topLevelMenuItem); + parentMenuItems.Add(menuId, topLevelMenuItem); parentMenuItem = topLevelMenuItem; } } @@ -105,219 +116,45 @@ MenuItem GetOrAddParentMenuItem(string menuID, string resourceKey) } } - public void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) + void InitWindowMenu(Menu mainMenu, InputBindingCollection inputBindings) { var windowMenuItem = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - var separatorBeforeTools = new Separator(); - var separatorBeforeDocuments = new Separator(); - - windowMenuItem.Items.Add(separatorBeforeTools); - windowMenuItem.Items.Add(separatorBeforeDocuments); - - var dock = DockWorkspace.Instance; + var defaultItems = windowMenuItem.Items.Cast().ToArray(); - dock.ToolPanes.CollectionChanged += ToolsChanged; - dock.TabPages.CollectionChanged += TabsChanged; - MessageBus.Subscribers += ActiveTabPageChanged; - - ToolsChanged(dock.ToolPanes, new(NotifyCollectionChangedAction.Reset)); - TabsChanged(dock.TabPages, new(NotifyCollectionChangedAction.Reset)); - - void ToolsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments); - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeTools) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (ToolPaneModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (ToolPaneModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[0]; - item.Tag = null; - windowMenuItem.Items.RemoveAt(i); - endIndex--; - } - insertionIndex = endIndex; - foreach (ToolPaneModel pane in dock.ToolPanes) - { - MenuItem menuItem = CreateMenuItem(pane); - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } + windowMenuItem.Items.Clear(); - MenuItem CreateMenuItem(ToolPaneModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId); - menuItem.Header = pane.Title; - menuItem.Tag = pane; - var shortcutKey = pane.ShortcutKey; - if (shortcutKey != null) - { - inputBindings.Add(new InputBinding(menuItem.Command, shortcutKey)); - menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); - } - if (!string.IsNullOrEmpty(pane.Icon)) - { - menuItem.Icon = new Image { - Width = 16, - Height = 16, - Source = Images.Load(pane, pane.Icon) - }; - } + var toolItems = dockWorkspace.ToolPanes.ObservableSelect(toolPane => CreateMenuItem(toolPane, inputBindings)); + var tabItems = dockWorkspace.TabPages.ObservableSelect(tabPage => CreateMenuItem(tabPage, dockWorkspace)); - return menuItem; - } - } + var allItems = new ObservableCompositeCollection(defaultItems, [new Separator()], toolItems, [new Separator()], tabItems); - void TabsChanged(object sender, NotifyCollectionChangedEventArgs e) - { - int endIndex = windowMenuItem.Items.Count; - int startIndex = windowMenuItem.Items.IndexOf(separatorBeforeDocuments) + 1; - int insertionIndex; - switch (e.Action) - { - case NotifyCollectionChangedAction.Add: - insertionIndex = Math.Min(endIndex, startIndex + e.NewStartingIndex); - foreach (TabPageModel pane in e.NewItems) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - case NotifyCollectionChangedAction.Remove: - foreach (TabPageModel pane in e.OldItems) - { - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - if (pane == item.Tag) - { - windowMenuItem.Items.RemoveAt(i); - pane.PropertyChanged -= TabPageChanged; - item.Tag = null; - endIndex--; - break; - } - } - } - break; - case NotifyCollectionChangedAction.Replace: - break; - case NotifyCollectionChangedAction.Move: - break; - case NotifyCollectionChangedAction.Reset: - for (int i = endIndex - 1; i >= startIndex; i--) - { - MenuItem item = (MenuItem)windowMenuItem.Items[i]; - windowMenuItem.Items.RemoveAt(i); - ((TabPageModel)item.Tag).PropertyChanged -= TabPageChanged; - endIndex--; - } - insertionIndex = endIndex; - foreach (TabPageModel pane in dock.TabPages) - { - MenuItem menuItem = CreateMenuItem(pane); - pane.PropertyChanged += TabPageChanged; - windowMenuItem.Items.Insert(insertionIndex, menuItem); - insertionIndex++; - } - break; - } - - MenuItem CreateMenuItem(TabPageModel pane) - { - MenuItem menuItem = new MenuItem(); - menuItem.Command = new TabPageCommand(pane); - menuItem.Header = pane.Title.Length > 20 ? pane.Title.Substring(20) + "..." : pane.Title; - menuItem.Tag = pane; - menuItem.IsCheckable = true; - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - - return menuItem; - } - } - - void TabPageChanged(object sender, PropertyChangedEventArgs e) - { - var windowMenu = mainMenu.Items.OfType().First(m => (string)m.Tag == nameof(Properties.Resources._Window)); - foreach (MenuItem menuItem in windowMenu.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag == sender) - { - string title = ((TabPageModel)sender).Title; - menuItem.Header = title.Length > 20 ? title.Substring(0, 20) + "..." : title; - } - } - } - - void ActiveTabPageChanged(object sender, EventArgs e) - { - foreach (MenuItem menuItem in windowMenuItem.Items.OfType()) - { - if (menuItem.IsCheckable && menuItem.Tag is TabPageModel) - { - menuItem.IsChecked = menuItem.Tag == dock.ActiveTabPage; - } - } - } + windowMenuItem.ItemsSource = allItems; } - public void InitToolbar(ToolBar toolBar) + static void InitToolbar(ToolBar toolBar) { int navigationPos = 0; int openPos = 1; - var toolbarCommands = App.ExportProvider.GetExports("ToolbarCommand"); - foreach (var commandGroup in toolbarCommands.OrderBy(c => c.Metadata.ToolbarOrder).GroupBy(c => Properties.Resources.ResourceManager.GetString(c.Metadata.ToolbarCategory))) + var toolbarCommandsByTitle = App.ExportProvider.GetExports("ToolbarCommand") + .OrderBy(c => c.Metadata?.ToolbarOrder) + .GroupBy(c => c.Metadata?.ToolbarCategory); + + foreach (var commandGroup in toolbarCommandsByTitle) { - if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Navigation")) + if (commandGroup.Key == nameof(Properties.Resources.Navigation)) { foreach (var command in commandGroup) { - toolBar.Items.Insert(navigationPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(navigationPos++, CreateToolbarItem(command)); openPos++; } } - else if (commandGroup.Key == Properties.Resources.ResourceManager.GetString("Open")) + else if (commandGroup.Key == nameof(Properties.Resources.Open)) { foreach (var command in commandGroup) { - toolBar.Items.Insert(openPos++, MakeToolbarItem(command)); + toolBar.Items.Insert(openPos++, CreateToolbarItem(command)); } } else @@ -325,33 +162,76 @@ public void InitToolbar(ToolBar toolBar) toolBar.Items.Add(new Separator()); foreach (var command in commandGroup) { - toolBar.Items.Add(MakeToolbarItem(command)); + toolBar.Items.Add(CreateToolbarItem(command)); } } } } - Button MakeToolbarItem(IExport command) + static Control CreateMenuItem(TabPageModel pane, DockWorkspace dock) { - return new Button { + var header = new TextBlock { + MaxWidth = 200, + TextTrimming = TextTrimming.CharacterEllipsis + }; + + header.SetBinding(TextBlock.TextProperty, new Binding(nameof(pane.Title)) { + Source = pane + }); + + MenuItem menuItem = new() { + Command = new TabPageCommand(pane), + Header = header, + IsCheckable = true + }; + + menuItem.SetBinding(MenuItem.IsCheckedProperty, new Binding(nameof(dock.ActiveTabPage)) { + Source = dock, + ConverterParameter = pane, + Converter = BinaryOperationConverter.Equality + }); + + return menuItem; + } + + static Control CreateMenuItem(ToolPaneModel pane, InputBindingCollection inputBindings) + { + MenuItem menuItem = new() { + Command = pane.AssociatedCommand ?? new ToolPaneCommand(pane.ContentId), + Header = pane.Title + }; + var shortcutKey = pane.ShortcutKey; + if (shortcutKey != null) + { + inputBindings.Add(new(menuItem.Command, shortcutKey)); + menuItem.InputGestureText = shortcutKey.GetDisplayStringForCulture(CultureInfo.CurrentUICulture); + } + if (!string.IsNullOrEmpty(pane.Icon)) + { + menuItem.Icon = new Image { + Width = 16, + Height = 16, + Source = Images.Load(pane, pane.Icon) + }; + } + + return menuItem; + } + + static Button CreateToolbarItem(IExport command) + { + return new() { Style = ThemeManager.Current.CreateToolBarButtonStyle(), Command = CommandWrapper.Unwrap(command.Value), - ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata.ToolTip), - Tag = command.Metadata.Tag, + ToolTip = Properties.Resources.ResourceManager.GetString(command.Metadata?.ToolTip), + Tag = command.Metadata?.Tag, Content = new Image { Width = 16, Height = 16, - Source = Images.Load(command.Value, command.Metadata.ToolbarIcon) + Source = Images.Load(command.Value, command.Metadata?.ToolbarIcon) } }; } - - public void Init(Menu mainMenu, ToolBar toolBar, InputBindingCollection inputBindings) - { - InitMainMenu(mainMenu); - InitWindowMenu(mainMenu, inputBindings); - InitToolbar(toolBar); - } } } From 84d635633ef1ece31361b7012d640d73c0af4047 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Tue, 3 Sep 2024 17:31:26 +0200 Subject: [PATCH 04/14] Omit the need for a special PaneCollection --- ILSpy/AssemblyTree/AssemblyListPaneModel.cs | 4 +- ILSpy/Docking/DockWorkspace.cs | 54 +++++++++++------ ILSpy/Docking/PaneCollection.cs | 66 --------------------- ILSpy/MainWindow.xaml.cs | 4 +- 4 files changed, 40 insertions(+), 88 deletions(-) delete mode 100644 ILSpy/Docking/PaneCollection.cs diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs index adbdd5aeec..9fff284be6 100644 --- a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs +++ b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs @@ -481,7 +481,7 @@ public void SelectNode(SharpTreeNode node, bool inNewTabPage = false) if (inNewTabPage) { - DockWorkspace.Instance.TabPages.Add(); + DockWorkspace.Instance.AddTabPage(); SelectedItem = null; } @@ -505,7 +505,7 @@ internal void SelectNodes(IEnumerable nodes, bool inNewTabPage = if (inNewTabPage) { - DockWorkspace.Instance.TabPages.Add(); + DockWorkspace.Instance.AddTabPage(); } // Ensure nodes exist diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index 232a49e35d..a708b93275 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -49,9 +50,15 @@ public class DockWorkspace : ObservableObject, ILayoutUpdateStrategy public static readonly DockWorkspace Instance = new(); + private readonly ObservableCollection tabPages = []; + private readonly ObservableCollection toolPanes = []; + private DockWorkspace() { - this.TabPages.CollectionChanged += Documents_CollectionChanged; + this.tabPages.CollectionChanged += TabPages_CollectionChanged; + TabPages = new(tabPages); + ToolPanes = new(toolPanes); + MessageBus.Subscribers += (sender, e) => CurrentAssemblyList_Changed(sender, e); } @@ -61,7 +68,7 @@ private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedE { return; } - foreach (var tab in TabPages.ToArray()) + foreach (var tab in tabPages.ToArray()) { var state = tab.GetState(); if (state == null || state.DecompiledNodes == null) @@ -78,35 +85,45 @@ private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedE break; } } - if (!found && TabPages.Count > 1) + if (!found && tabPages.Count > 1) { - TabPages.Remove(tab); + tabPages.Remove(tab); } } } - private void Documents_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + private void TabPages_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) { - var collection = (PaneCollection)sender; if (e.Action == NotifyCollectionChangedAction.Add) { - ActiveTabPage = e.NewItems?[0] as TabPageModel; + if (e.NewItems?[0] is TabPageModel model) + { + ActiveTabPage = model; + model.IsActive = true; + model.IsVisible = true; + } } - bool canClose = collection.Count > 1; - foreach (var item in collection) + bool canClose = tabPages.Count > 1; + + foreach (var item in tabPages) { item.IsCloseable = canClose; } } - public PaneCollection TabPages { get; } = new PaneCollection(); + public void AddTabPage(TabPageModel tabPage = null) + { + tabPages.Add(tabPage ?? new TabPageModel()); + } - public ObservableCollection ToolPanes { get; } = new ObservableCollection(); + public ReadOnlyObservableCollection TabPages { get; } + + public ReadOnlyObservableCollection ToolPanes { get; } public bool ShowToolPane(string contentId) { - var pane = ToolPanes.FirstOrDefault(p => p.ContentId == contentId); + var pane = toolPanes.FirstOrDefault(p => p.ContentId == contentId); if (pane != null) { pane.Show(); @@ -118,7 +135,7 @@ public bool ShowToolPane(string contentId) public void Remove(PaneModel model) { if (model is TabPageModel document) - TabPages.Remove(document); + tabPages.Remove(document); if (model is ToolPaneModel tool) tool.IsVisible = false; } @@ -153,7 +170,8 @@ public TabPageModel ActiveTabPage { public void InitializeLayout(DockingManager manager) { var toolPanes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); - ToolPanes.AddRange(toolPanes); + + this.toolPanes.AddRange(toolPanes); manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); @@ -173,7 +191,7 @@ void LayoutSerializationCallback(object sender, LayoutSerializationCallbackEvent switch (e.Model) { case LayoutAnchorable la: - e.Content = ToolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); + e.Content = this.toolPanes.FirstOrDefault(p => p.ContentId == la.ContentId); e.Cancel = e.Content == null; la.CanDockAsTabbedDocument = false; if (!e.Cancel) @@ -205,16 +223,16 @@ internal void ShowNodes(AvalonEditTextOutput output, TreeNodes.ILSpyTreeNode[] n internal void CloseAllTabs() { - foreach (var doc in TabPages.ToArray()) + foreach (var doc in tabPages.ToArray()) { if (doc.IsCloseable) - TabPages.Remove(doc); + tabPages.Remove(doc); } } internal void ResetLayout() { - foreach (var pane in ToolPanes) + foreach (var pane in toolPanes) { pane.IsVisible = false; } diff --git a/ILSpy/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs deleted file mode 100644 index a8e397fe53..0000000000 --- a/ILSpy/Docking/PaneCollection.cs +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) 2019 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 System.Collections; -using System.Collections.Generic; -using System.Collections.ObjectModel; -using System.Collections.Specialized; - -using ICSharpCode.ILSpy.ViewModels; - -namespace ICSharpCode.ILSpy.Docking -{ - public class PaneCollection : INotifyCollectionChanged, IList - where T : PaneModel, new() - { - private readonly ObservableCollection observableCollection = []; - - public event NotifyCollectionChangedEventHandler CollectionChanged; - - public PaneCollection() - { - observableCollection.CollectionChanged += (sender, e) => CollectionChanged?.Invoke(this, e); - } - - public void Add(T item = null) - { - item ??= new T(); - - observableCollection.Add(item); - - item.IsVisible = true; - item.IsActive = true; - } - public int Count => observableCollection.Count; - public bool IsReadOnly => false; - public void Clear() => observableCollection.Clear(); - public bool Contains(T item) => observableCollection.Contains(item); - public void CopyTo(T[] array, int arrayIndex) => observableCollection.CopyTo(array, arrayIndex); - public bool Remove(T item) => observableCollection.Remove(item); - public IEnumerator GetEnumerator() => observableCollection.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => observableCollection.GetEnumerator(); - int IList.IndexOf(T item) => observableCollection.IndexOf(item); - void IList.Insert(int index, T item) => throw new NotImplementedException("Only Add is supported"); - void IList.RemoveAt(int index) => observableCollection.RemoveAt(index); - T IList.this[int index] { - get => observableCollection[index]; - set => observableCollection[index] = value; - } - } -} diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 64f78260f9..7dd5f5c56e 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -191,7 +191,7 @@ await Dispatcher.InvokeAsync(() => { void MainWindow_Loaded(object sender, RoutedEventArgs e) { - DockWorkspace.Instance.TabPages.Add(); + DockWorkspace.Instance.AddTabPage(); AssemblyTreeModel.Initialize(); } @@ -380,7 +380,7 @@ internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, { if (inNewTabPage) { - DockWorkspace.Instance.TabPages.Add(); + DockWorkspace.Instance.AddTabPage(); } if (e.Uri.Host == "aboutpage") From 2d8ad69d3baa063a8fb269eeff96dba6c1ac31c2 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Tue, 3 Sep 2024 17:38:56 +0200 Subject: [PATCH 05/14] Fix possible null de-reference --- ILSpy/ExtensionMethods.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 39595224fb..df6909ab37 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -29,6 +29,8 @@ using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.ILSpyX; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy { /// @@ -124,9 +126,9 @@ public static Point TransformFromDevice(this Point point, Visual visual) for (int i = 0; i < VisualTreeHelper.GetChildrenCount(depObj); i++) { DependencyObject child = VisualTreeHelper.GetChild(depObj, i); - if (child != null && child is T) + if (child is T dependencyObject) { - return (T)child; + return dependencyObject; } T? childItem = FindVisualChild(child); @@ -177,17 +179,18 @@ internal static bool FormatExceptions(this IList exceptions, else output.AppendLine("-------------------------------------------------"); output.AppendLine("Error(s) loading plugin: " + item.PluginName); - if (item.Exception is System.Reflection.ReflectionTypeLoadException) + if (item.Exception is System.Reflection.ReflectionTypeLoadException exception) { - var e = (System.Reflection.ReflectionTypeLoadException)item.Exception; - foreach (var ex in e.LoaderExceptions) + foreach (var ex in exception.LoaderExceptions.ExceptNullItems()) { output.AppendLine(ex.ToString()); output.AppendLine(); } } else + { output.AppendLine(item.Exception.ToString()); + } } return true; From 29026f4df467bd0b10ed25dfc10057cbacd34c56 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Wed, 4 Sep 2024 08:35:40 +0200 Subject: [PATCH 06/14] Avoid using mouse position to find already known elements. --- ILSpy/ContextMenuEntry.cs | 72 ++++++++++++------------ ILSpy/Controls/TreeView/SharpTreeView.cs | 6 +- ILSpy/Metadata/GoToTokenCommand.cs | 22 +++----- 3 files changed, 46 insertions(+), 54 deletions(-) diff --git a/ILSpy/ContextMenuEntry.cs b/ILSpy/ContextMenuEntry.cs index 81022b9dc7..4da82c40d0 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -19,12 +19,9 @@ using System; using System.Collections.Generic; using System.ComponentModel.Composition; -using System.Diagnostics; using System.Linq; using System.Windows; using System.Windows.Controls; -using System.Windows.Input; -using System.Windows.Media; using ICSharpCode.AvalonEdit; using ICSharpCode.ILSpy.TextView; @@ -88,36 +85,40 @@ public class TextViewContext /// public TextViewPosition? Position { get; private set; } - public Point MousePosition { get; private set; } + /// + /// Returns the original source of the context menu event. + /// + public DependencyObject OriginalSource { get; private set; } - public static TextViewContext Create(SharpTreeView treeView = null, DecompilerTextView textView = null, ListBox listBox = null, DataGrid dataGrid = null) + public static TextViewContext Create(ContextMenuEventArgs eventArgs, SharpTreeView treeView = null, DecompilerTextView textView = null, ListBox listBox = null, DataGrid dataGrid = null) { ReferenceSegment reference; - if (textView != null) + + if (textView is not null) + { reference = textView.GetReferenceSegmentAtMousePosition(); - else if (listBox?.SelectedItem is SearchResult result) - reference = new ReferenceSegment { Reference = result.Reference }; - else if (listBox?.SelectedItem is TreeNodes.IMemberTreeNode provider) - reference = new ReferenceSegment { Reference = provider.Member }; - else if (listBox?.SelectedItem != null) - reference = new ReferenceSegment { Reference = listBox.SelectedItem }; - else if (dataGrid?.SelectedItem is TreeNodes.IMemberTreeNode provider2) - reference = new ReferenceSegment { Reference = provider2.Member }; - else if (dataGrid?.SelectedItem != null) - reference = new ReferenceSegment { Reference = dataGrid.SelectedItem }; + } else - reference = null; - var position = textView != null ? textView.GetPositionFromMousePosition() : null; - var selectedTreeNodes = treeView != null ? treeView.GetTopLevelSelection().ToArray() : null; - return new TextViewContext { + { + reference = (listBox?.SelectedItem ?? dataGrid?.SelectedItem) switch { + SearchResult searchResult => new() { Reference = searchResult.Reference }, + TreeNodes.IMemberTreeNode treeNode => new() { Reference = treeNode.Member }, { } value => new() { Reference = value }, + _ => null + }; + } + + var position = textView?.GetPositionFromMousePosition(); + var selectedTreeNodes = treeView?.GetTopLevelSelection().ToArray(); + + return new() { ListBox = listBox, DataGrid = dataGrid, TreeView = treeView, - SelectedTreeNodes = selectedTreeNodes, TextView = textView, + SelectedTreeNodes = selectedTreeNodes, Reference = reference, Position = position, - MousePosition = ((Visual)textView ?? treeView ?? (Visual)listBox ?? dataGrid).PointToScreen(Mouse.GetPosition((IInputElement)textView ?? treeView ?? (IInputElement)listBox ?? dataGrid)) + OriginalSource = eventArgs.OriginalSource as DependencyObject }; } } @@ -252,14 +253,14 @@ private ContextMenuProvider(Control control) void treeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(treeView); + var context = TextViewContext.Create(e, treeView: treeView); if (context.SelectedTreeNodes.Length == 0) { e.Handled = true; // don't show the menu return; } - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + + if (ShowContextMenu(context, out var menu)) treeView.ContextMenu = menu; else // hide the context menu. @@ -268,9 +269,8 @@ void treeView_ContextMenuOpening(object sender, ContextMenuEventArgs e) void textView_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(textView: textView); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, textView: textView); + if (ShowContextMenu(context, out var menu)) textView.ContextMenu = menu; else // hide the context menu. @@ -279,9 +279,8 @@ void textView_ContextMenuOpening(object sender, ContextMenuEventArgs e) void listBox_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(listBox: listBox); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, listBox: listBox); + if (ShowContextMenu(context, out var menu)) listBox.ContextMenu = menu; else // hide the context menu. @@ -290,9 +289,8 @@ void listBox_ContextMenuOpening(object sender, ContextMenuEventArgs e) void dataGrid_ContextMenuOpening(object sender, ContextMenuEventArgs e) { - TextViewContext context = TextViewContext.Create(dataGrid: dataGrid); - ContextMenu menu; - if (ShowContextMenu(context, out menu)) + var context = TextViewContext.Create(e, dataGrid: dataGrid); + if (ShowContextMenu(context, out var menu)) dataGrid.ContextMenu = menu; else // hide the context menu. @@ -333,10 +331,10 @@ void BuildMenu(IExport[] menuGroup { foreach (var category in menuGroup.GroupBy(c => c.Metadata.Category)) { - bool needSeparatorForCategory = parent.Count > 0; + var needSeparatorForCategory = parent.Count > 0; foreach (var entryPair in category) { - IContextMenuEntry entry = entryPair.Value; + var entry = entryPair.Value; if (entry.IsVisible(context)) { if (needSeparatorForCategory) @@ -344,7 +342,7 @@ void BuildMenu(IExport[] menuGroup parent.Add(new Separator()); needSeparatorForCategory = false; } - MenuItem menuItem = new MenuItem(); + var menuItem = new MenuItem(); menuItem.Header = ResourceHelper.GetString(entryPair.Metadata.Header); menuItem.InputGestureText = entryPair.Metadata.InputGestureText; if (!string.IsNullOrEmpty(entryPair.Metadata.Icon)) diff --git a/ILSpy/Controls/TreeView/SharpTreeView.cs b/ILSpy/Controls/TreeView/SharpTreeView.cs index c60cd5ad76..80558388e4 100644 --- a/ILSpy/Controls/TreeView/SharpTreeView.cs +++ b/ILSpy/Controls/TreeView/SharpTreeView.cs @@ -819,9 +819,9 @@ static void HandleCanExecute_Delete(object sender, CanExecuteRoutedEventArgs e) /// public IEnumerable GetTopLevelSelection() { - var selection = this.SelectedItems.OfType(); - var selectionHash = new HashSet(selection); - return selection.Where(item => item.Ancestors().All(a => !selectionHash.Contains(a))); + var selection = this.SelectedItems.OfType().ToHashSet(); + + return selection.Where(item => item.Ancestors().All(a => !selection.Contains(a))); } #endregion diff --git a/ILSpy/Metadata/GoToTokenCommand.cs b/ILSpy/Metadata/GoToTokenCommand.cs index d451f27aba..164f9b4514 100644 --- a/ILSpy/Metadata/GoToTokenCommand.cs +++ b/ILSpy/Metadata/GoToTokenCommand.cs @@ -23,12 +23,13 @@ using System.Reflection.Metadata.Ecma335; using System.Windows; using System.Windows.Controls; -using System.Windows.Media; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.ILSpy.Metadata; using ICSharpCode.ILSpy.Properties; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy.Commands { [ExportContextMenuEntry(Header = nameof(Resources.GoToToken), Order = 10)] @@ -75,7 +76,7 @@ class CopyCommand : IContextMenuEntry { public void Execute(TextViewContext context) { - string content = GetSelectedCellContent(context.DataGrid, context.MousePosition); + string content = GetSelectedCellContent(context.OriginalSource); Clipboard.SetText(content); } @@ -87,21 +88,14 @@ public bool IsEnabled(TextViewContext context) public bool IsVisible(TextViewContext context) { return context.DataGrid?.Name == "MetadataView" - && GetSelectedCellContent(context.DataGrid, context.MousePosition) != null; + && GetSelectedCellContent(context.OriginalSource) != null; } - private string GetSelectedCellContent(DataGrid grid, Point position) + private static string GetSelectedCellContent(DependencyObject originalSource) { - position = grid.PointFromScreen(position); - var hit = VisualTreeHelper.HitTest(grid, position); - if (hit == null) - return null; - var cell = hit.VisualHit.GetParent(); - if (cell == null) - return null; - return cell.DataContext.GetType() - .GetProperty(cell.Column.Header.ToString(), BindingFlags.Instance | BindingFlags.Public) - .GetValue(cell.DataContext).ToString(); + var cell = originalSource.AncestorsAndSelf().OfType().FirstOrDefault(); + + return cell?.Column.OnCopyingCellClipboardContent(cell.DataContext).ToString(); } } } From 152f70e7898c5e3466369d64dc23771dd6bc8811 Mon Sep 17 00:00:00 2001 From: tom-englert Date: Thu, 5 Sep 2024 15:51:29 +0200 Subject: [PATCH 07/14] ILSpyX: remove InternalsVisibleTo for ILSpy + remove some redundant code. --- ICSharpCode.ILSpyX/AssemblyList.cs | 4 +- .../Extensions/CollectionExtensions.cs | 2 +- ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj | 1 - ICSharpCode.ILSpyX/LoadedPackage.cs | 8 +- .../PdbProvider/PortableDebugInfoProvider.cs | 6 +- ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs | 6 +- ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs | 2 +- ILSpy/Analyzers/AnalyzerSearchTreeNode.cs | 2 +- ILSpy/AssemblyTree/AssemblyListPane.xaml | 46 ++--- ILSpy/AssemblyTree/AssemblyListPaneModel.cs | 162 ++++++------------ ILSpy/Commands/DecompileAllCommand.cs | 2 +- ILSpy/Commands/DecompileInNewViewCommand.cs | 5 +- ILSpy/Commands/DisassembleAllCommand.cs | 2 +- .../RemoveAssembliesWithLoadErrors.cs | 8 +- ILSpy/Docking/DockWorkspace.cs | 30 ++-- ILSpy/ExtensionMethods.cs | 16 ++ ILSpy/Languages/CSharpLanguage.cs | 2 +- ILSpy/MainWindow.xaml.cs | 2 +- ILSpy/Metadata/CoffHeaderTreeNode.cs | 2 + ILSpy/Metadata/OptionalHeaderTreeNode.cs | 2 + ILSpy/Search/SearchPane.xaml.cs | 2 +- ILSpy/TextView/DecompilerTextView.cs | 4 +- ILSpy/TreeNodes/AssemblyListTreeNode.cs | 1 + ILSpy/TreeNodes/AssemblyTreeNode.cs | 2 +- ILSpy/TreeNodes/ILSpyTreeNode.cs | 2 +- ILSpy/Util/MessageBus.cs | 10 +- ILSpy/Views/OpenFromGacDialog.xaml.cs | 2 + TestPlugin/MainMenuCommand.cs | 2 +- 28 files changed, 147 insertions(+), 188 deletions(-) diff --git a/ICSharpCode.ILSpyX/AssemblyList.cs b/ICSharpCode.ILSpyX/AssemblyList.cs index ec10a4e92e..190f45e520 100644 --- a/ICSharpCode.ILSpyX/AssemblyList.cs +++ b/ICSharpCode.ILSpyX/AssemblyList.cs @@ -186,7 +186,7 @@ public string ListName { get { return listName; } } - internal void Move(LoadedAssembly[] assembliesToMove, int index) + public void Move(LoadedAssembly[] assembliesToMove, int index) { VerifyAccess(); lock (lockObj) @@ -230,7 +230,7 @@ static bool CollectionChangeHasEffectOnSave(NotifyCollectionChangedEventArgs e) } } - internal void RefreshSave() + public void RefreshSave() { // Whenever the assembly list is modified, mark it as dirty // and enqueue a task that saves it once the UI has finished modifying the assembly list. diff --git a/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs b/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs index 772c464767..1d4f359e15 100644 --- a/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs +++ b/ICSharpCode.ILSpyX/Extensions/CollectionExtensions.cs @@ -27,7 +27,7 @@ namespace ICSharpCode.ILSpyX.Extensions { public static class CollectionExtensions { - public static void AddRange(this ICollection list, IEnumerable items) + internal static void AddRange(this ICollection list, IEnumerable items) { foreach (T item in items) if (!list.Contains(item)) diff --git a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj index bcea038e2e..21a9363b0c 100644 --- a/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj +++ b/ICSharpCode.ILSpyX/ICSharpCode.ILSpyX.csproj @@ -41,7 +41,6 @@ - diff --git a/ICSharpCode.ILSpyX/LoadedPackage.cs b/ICSharpCode.ILSpyX/LoadedPackage.cs index 47e80b962d..78f80b3490 100644 --- a/ICSharpCode.ILSpyX/LoadedPackage.cs +++ b/ICSharpCode.ILSpyX/LoadedPackage.cs @@ -51,14 +51,14 @@ public enum PackageKind public PackageKind Kind { get; } - internal SingleFileBundle.Header BundleHeader { get; set; } + public SingleFileBundle.Header BundleHeader { get; set; } /// /// List of all entries, including those in sub-directories within the package. /// public IReadOnlyList Entries { get; } - internal PackageFolder RootFolder { get; } + public PackageFolder RootFolder { get; } public LoadedPackage(PackageKind kind, IEnumerable entries) { @@ -256,7 +256,7 @@ public abstract class PackageEntry : Resource public abstract string FullName { get; } } - sealed class PackageFolder : IAssemblyResolver + public sealed class PackageFolder : IAssemblyResolver { /// /// Gets the short name of the folder. @@ -326,7 +326,7 @@ internal PackageFolder(LoadedPackage package, PackageFolder? parent, string name readonly Dictionary assemblies = new Dictionary(StringComparer.OrdinalIgnoreCase); - internal LoadedAssembly? ResolveFileName(string name) + public LoadedAssembly? ResolveFileName(string name) { if (package.LoadedAssembly == null) return null; diff --git a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs index ec4cb8faf2..a5432f47cb 100644 --- a/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs +++ b/ICSharpCode.ILSpyX/PdbProvider/PortableDebugInfoProvider.cs @@ -32,7 +32,7 @@ namespace ICSharpCode.ILSpyX.PdbProvider { - class PortableDebugInfoProvider : IDebugInfoProvider + public class PortableDebugInfoProvider : IDebugInfoProvider { string? pdbFileName; string moduleFileName; @@ -40,7 +40,7 @@ class PortableDebugInfoProvider : IDebugInfoProvider MetadataReaderOptions options; bool hasError; - internal bool IsEmbedded => pdbFileName == null; + public bool IsEmbedded => pdbFileName == null; public PortableDebugInfoProvider(string moduleFileName, MetadataReaderProvider provider, MetadataReaderOptions options = MetadataReaderOptions.Default, @@ -69,7 +69,7 @@ public string Description { } } - internal MetadataReader? GetMetadataReader() + public MetadataReader? GetMetadataReader() { try { diff --git a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs index e34675fc3c..3fd6bb662c 100644 --- a/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs +++ b/ICSharpCode.ILSpyX/TreeView/SharpTreeNode.cs @@ -182,7 +182,7 @@ public bool IsSelected { #endregion #region OnChildrenChanged - protected internal virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) + public virtual void OnChildrenChanged(NotifyCollectionChangedEventArgs e) { if (e.OldItems != null) { @@ -359,7 +359,7 @@ internal IEnumerable VisibleDescendants() return TreeTraversal.PreOrder(this.Children.Where(c => c.isVisible), n => n.Children.Where(c => c.isVisible)); } - internal IEnumerable VisibleDescendantsAndSelf() + public IEnumerable VisibleDescendantsAndSelf() { return TreeTraversal.PreOrder(this, n => n.Children.Where(c => c.isVisible)); } @@ -637,7 +637,7 @@ public virtual bool CanDrop(IPlatformDragEventArgs e, int index) return false; } - internal void InternalDrop(IPlatformDragEventArgs e, int index) + public void InternalDrop(IPlatformDragEventArgs e, int index) { if (LazyLoading) { diff --git a/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs b/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs index 46cc5cafd9..80803c194e 100644 --- a/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs +++ b/ICSharpCode.ILSpyX/TreeView/TreeFlattener.cs @@ -26,7 +26,7 @@ namespace ICSharpCode.ILSpyX.TreeView { - sealed class TreeFlattener : IList, INotifyCollectionChanged + public sealed class TreeFlattener : IList, INotifyCollectionChanged { /// /// The root node of the flat list tree. diff --git a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs index e0ade86662..7c3d484bcd 100644 --- a/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerSearchTreeNode.cs @@ -61,7 +61,7 @@ protected IEnumerable FetchChildren(CancellationToken ct) var context = new AnalyzerContext() { CancellationToken = ct, Language = Language, - AssemblyList = MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList + AssemblyList = MainWindow.Instance.AssemblyTreeModel.AssemblyList }; var results = analyzer.Analyze(symbol, context).Select(SymbolTreeNodeFactory); if (context.SortResults) diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml b/ILSpy/AssemblyTree/AssemblyListPane.xaml index 8739412a89..d0f8e97333 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.xaml +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml @@ -1,28 +1,28 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" + xmlns:treeView="clr-namespace:ICSharpCode.ILSpy.Controls.TreeView" + xmlns:treeNodes="clr-namespace:ICSharpCode.ILSpy.TreeNodes" + xmlns:assemblyTree="clr-namespace:ICSharpCode.ILSpy.AssemblyTree" + xmlns:toms="urn:TomsToolbox" + mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" + d:DataContext="{d:DesignInstance assemblyTree:AssemblyListPaneModel}" + AutomationProperties.Name="Assemblies and Classes" + ShowRoot="False" + AllowDropOrder="True" + AllowDrop="True" + BorderThickness="0" Visibility="Visible" + Root="{Binding Root}" + SelectedItem="{Binding SelectedItem, Mode=TwoWay}" + toms:MultiSelectorExtensions.SelectionBinding="{Binding SelectedItems}"> - + \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs index 9fff284be6..97199d059b 100644 --- a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs +++ b/ILSpy/AssemblyTree/AssemblyListPaneModel.cs @@ -36,7 +36,6 @@ using ICSharpCode.ILSpyX.TreeView; using System.Collections.Specialized; using System.ComponentModel; -using System.Diagnostics; using System.Linq; using ICSharpCode.Decompiler.Metadata; @@ -52,6 +51,7 @@ using ICSharpCode.Decompiler; using System.Text; +using TomsToolbox.Essentials; using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.AssemblyTree @@ -64,12 +64,8 @@ public class AssemblyListPaneModel : ToolPaneModel public const string PaneContentId = "assemblyListPane"; AssemblyListPane activeView; - AssemblyList assemblyList; AssemblyListTreeNode assemblyListTreeNode; - bool refreshInProgress; - bool changingActiveTab; - readonly NavigationHistoryService history = NavigationHistoryService.Instance; public AssemblyListPaneModel() @@ -107,9 +103,7 @@ private void SessionSettings_PropertyChanged(object sender, PropertyChangedEvent } } - public AssemblyList CurrentAssemblyList { - get { return assemblyList; } - } + public AssemblyList AssemblyList { get; private set; } private SharpTreeNode root; public SharpTreeNode Root { @@ -230,7 +224,7 @@ public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPat SharpTreeNode node = null; if (activeTreeViewPath?.Length > 0) { - foreach (var asm in CurrentAssemblyList.GetAssemblies()) + foreach (var asm in AssemblyList.GetAssemblies()) { if (asm.FileName == activeTreeViewPath[0]) { @@ -327,37 +321,37 @@ public void Initialize() if (loadPreviousAssemblies) { - this.assemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); + this.AssemblyList = SettingsService.Instance.AssemblyListManager.LoadList(sessionSettings.ActiveAssemblyList); } else { SettingsService.Instance.AssemblyListManager.ClearAll(); - this.assemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); + this.AssemblyList = SettingsService.Instance.AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); } HandleCommandLineArguments(App.CommandLineArguments); - if (assemblyList.GetAssemblies().Length == 0 - && assemblyList.ListName == AssemblyListManager.DefaultListName + if (AssemblyList.GetAssemblies().Length == 0 + && AssemblyList.ListName == AssemblyListManager.DefaultListName && loadPreviousAssemblies) { LoadInitialAssemblies(); } - ShowAssemblyList(this.assemblyList); + ShowAssemblyList(this.AssemblyList); if (sessionSettings.ActiveAutoLoadedAssembly != null && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) { - this.assemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); + this.AssemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); } - Dispatcher.BeginInvoke(DispatcherPriority.Loaded, new Action(() => OpenAssemblies(SettingsService.Instance.SpySettings))); + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies); } - void OpenAssemblies(ILSpySettings spySettings) + void OpenAssemblies() { - HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, spySettings); + HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.SpySettings); AvalonEditTextOutput output = new(); if (FormatExceptions(App.StartupExceptions.ToArray(), output)) @@ -379,7 +373,7 @@ public void ShowAssemblyList(string name) { AssemblyList list = SettingsService.Instance.AssemblyListManager.LoadList(name); //Only load a new list when it is a different one - if (list.ListName != CurrentAssemblyList.ListName) + if (list.ListName != AssemblyList.ListName) { ShowAssemblyList(list); SelectNode(Root); @@ -389,12 +383,12 @@ public void ShowAssemblyList(string name) void ShowAssemblyList(AssemblyList assemblyList) { history.Clear(); - if (this.assemblyList != null) + if (this.AssemblyList != null) { - this.assemblyList.CollectionChanged -= assemblyList_CollectionChanged; + this.AssemblyList.CollectionChanged -= assemblyList_CollectionChanged; } - this.assemblyList = assemblyList; + this.AssemblyList = assemblyList; assemblyList.CollectionChanged += assemblyList_CollectionChanged; @@ -450,12 +444,12 @@ void LoadInitialAssemblies() typeof(System.Windows.FrameworkElement).Assembly }; foreach (System.Reflection.Assembly asm in initialAssemblies) - assemblyList.OpenAssembly(asm.Location); + AssemblyList.OpenAssembly(asm.Location); } void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) { - if (e.PropertyName == "Language" || e.PropertyName == "LanguageVersion") + if (e.PropertyName is nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion)) { DecompileSelectedNodes(recordHistory: false); } @@ -496,18 +490,12 @@ public void SelectNode(SharpTreeNode node, bool inNewTabPage = false) } } - internal void SelectNodes(IEnumerable nodes, bool inNewTabPage = false, bool setFocus = true, bool changingTab = false, bool ignoreCompilationRequests = false) + internal void SelectNodes(IEnumerable nodes, bool ignoreCompilationRequests = false) { this.ignoreDecompilationRequests = ignoreCompilationRequests; try { - - if (inNewTabPage) - { - DockWorkspace.Instance.AddTabPage(); - } - // Ensure nodes exist var nodesList = nodes.Select(n => FindNodeByPath(GetPathForNode(n), true)) .Where(n => n != null) @@ -518,28 +506,14 @@ internal void SelectNodes(IEnumerable nodes, bool inNewTabPage = return; } - this.changingActiveTab = changingTab || inNewTabPage; - - var currentFocused = Keyboard.FocusedElement; - - try + if (SelectedItems.SequenceEqual(nodesList)) { - if (SelectedItems.SequenceEqual(nodesList)) - { - Dispatcher.BeginInvoke(RefreshDecompiledView); - return; - } - - this.SelectedItems.Clear(); - this.SelectedItems.AddRange(nodesList); + Dispatcher.BeginInvoke(RefreshDecompiledView); + return; } - finally - { - if (!setFocus) - currentFocused.Focus(); - this.changingActiveTab = false; - } + SelectedItems.Clear(); + SelectedItems.AddRange(nodesList); } finally { @@ -642,7 +616,7 @@ private Task JumpToReferenceAsync(object reference, bool inNewTabPage = false) break; case EntityReference unresolvedEntity: string protocol = unresolvedEntity.Protocol; - var file = unresolvedEntity.ResolveAssembly(assemblyList); + var file = unresolvedEntity.ResolveAssembly(AssemblyList); if (file == null) { break; @@ -681,35 +655,32 @@ private Task JumpToReferenceAsync(object reference, bool inNewTabPage = false) public void LoadAssemblies(IEnumerable fileNames, List loadedAssemblies = null, bool focusNode = true) { - var currentFocus = Keyboard.FocusedElement; - AssemblyTreeNode lastNode = null; - - foreach (string file in fileNames) + using (Keyboard.FocusedElement.PreserveFocus(!focusNode)) { - var assembly = assemblyList.OpenAssembly(file); + AssemblyTreeNode lastNode = null; - if (loadedAssemblies != null) + foreach (string file in fileNames) { - loadedAssemblies.Add(assembly); - } - else - { - var node = assemblyListTreeNode.FindAssemblyNode(assembly); - if (node != null && focusNode) + var assembly = AssemblyList.OpenAssembly(file); + + if (loadedAssemblies != null) { - lastNode = node; - SelectedItems.Add(node); + loadedAssemblies.Add(assembly); + } + else + { + var node = assemblyListTreeNode.FindAssemblyNode(assembly); + if (node != null && focusNode) + { + lastNode = node; + SelectedItems.Add(node); + } } } - } - - if (!focusNode) - { - currentFocus?.Focus(); - } - else if (lastNode != null) - { - activeView?.FocusNode(lastNode); + if (focusNode && lastNode != null) + { + activeView?.FocusNode(lastNode); + } } } @@ -717,22 +688,11 @@ public void LoadAssemblies(IEnumerable fileNames, List l void TreeView_SelectionChanged() { - DecompilerTextViewState state = null; - - // These are probably no longer needed and can be removed! - Debug.Assert(!refreshInProgress); - Debug.Assert(!changingActiveTab); - - if (refreshInProgress || changingActiveTab) - { - state = DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState; - } - var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; - if (!changingActiveTab && !delayDecompilationRequestDueToContextMenu) + if (!delayDecompilationRequestDueToContextMenu) { - DecompileSelectedNodes(state); + DecompileSelectedNodes(); } else { @@ -763,9 +723,6 @@ public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool if (ignoreDecompilationRequests) return; - if (SelectedItems.Count == 0 && refreshInProgress) - return; - var activeTabPage = DockWorkspace.Instance.ActiveTabPage; if (recordHistory) @@ -788,6 +745,7 @@ public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool MainWindow.Instance.NavigateTo(new(newState.ViewedUri, null), recordHistory: false); return; } + var options = SettingsService.Instance.CreateDecompilationOptions(activeTabPage); options.TextViewState = newState; activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); @@ -795,15 +753,7 @@ public void DecompileSelectedNodes(DecompilerTextViewState newState = null, bool public void RefreshDecompiledView() { - try - { - refreshInProgress = true; - DecompileSelectedNodes(); - } - finally - { - refreshInProgress = false; - } + DecompileSelectedNodes(); } public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; @@ -821,20 +771,12 @@ public IEnumerable SelectedNodes { public void Refresh() { - var currentFocus = Keyboard.FocusedElement; - - try + using (Keyboard.FocusedElement.PreserveFocus()) { - refreshInProgress = true; var path = GetPathForNode(SelectedItem); - ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(assemblyList.ListName)); + ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName)); SelectNode(FindNodeByPath(path, true), inNewTabPage: false); } - finally - { - refreshInProgress = false; - currentFocus?.Focus(); - } } public void UnselectAll(bool ignoreCompilationRequests = false) @@ -861,7 +803,7 @@ public void SortAssemblyList() { using (activeView?.LockUpdates()) { - CurrentAssemblyList.Sort(AssemblyComparer.Instance); + AssemblyList.Sort(AssemblyComparer.Instance); } } diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index ef8be645e0..b041bf7a7f 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -49,7 +49,7 @@ public override void Execute(object parameter) Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies(), loadBalance: true), new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, delegate (LoadedAssembly asm) { if (!asm.HasLoadError) diff --git a/ILSpy/Commands/DecompileInNewViewCommand.cs b/ILSpy/Commands/DecompileInNewViewCommand.cs index 0ecc50bf83..fd83244ebd 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -23,6 +23,7 @@ using System.Windows.Threading; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TreeNodes; @@ -84,7 +85,9 @@ static void DecompileNodes(ILSpyTreeNode[] nodes) if (nodes.Length == 0) return; - MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes, inNewTabPage: true); + DockWorkspace.Instance.AddTabPage(); + + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index 60c97d227e..07afced394 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -45,7 +45,7 @@ public override void Execute(object parameter) dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new(); Parallel.ForEach( - Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies(), loadBalance: true), + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies(), loadBalance: true), new() { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, asm => { if (!asm.HasLoadError) diff --git a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs index 73d9769a75..9cc357374a 100644 --- a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs +++ b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs @@ -29,12 +29,12 @@ class RemoveAssembliesWithLoadErrors : SimpleCommand { public override bool CanExecute(object parameter) { - return MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; + return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.GetAssemblies().Any(l => l.HasLoadError) == true; } public override void Execute(object parameter) { - foreach (var asm in MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()) + foreach (var asm in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) { if (!asm.HasLoadError) continue; @@ -51,12 +51,12 @@ class ClearAssemblyList : SimpleCommand { public override bool CanExecute(object parameter) { - return MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.Count > 0; + return MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Count > 0; } public override void Execute(object parameter) { - MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList?.Clear(); + MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Clear(); } } } diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index a708b93275..f0b90967d0 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Linq; @@ -38,6 +37,7 @@ using ICSharpCode.ILSpyX.Extensions; using TomsToolbox.Composition; +using TomsToolbox.Essentials; using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Docking @@ -71,20 +71,15 @@ private void CurrentAssemblyList_Changed(object sender, NotifyCollectionChangedE foreach (var tab in tabPages.ToArray()) { var state = tab.GetState(); - if (state == null || state.DecompiledNodes == null) - { + var decompiledNodes = state?.DecompiledNodes; + if (decompiledNodes == null) continue; - } - bool found = false; - foreach (var node in state.DecompiledNodes) - { - var assemblyNode = node.Ancestors().OfType().LastOrDefault(); - if (assemblyNode != null && !e.OldItems.Contains(assemblyNode.LoadedAssembly)) - { - found = true; - break; - } - } + + bool found = decompiledNodes + .Select(node => node.Ancestors().OfType().LastOrDefault()) + .ExceptNullItems() + .Any(assemblyNode => !e.OldItems.Contains(assemblyNode.LoadedAssembly)); + if (!found && tabPages.Count > 1) { tabPages.Remove(tab); @@ -156,22 +151,21 @@ public TabPageModel ActiveTabPage { { if (state.DecompiledNodes != null) { - MainWindow.Instance.AssemblyTreeModel.SelectNodes(state.DecompiledNodes, inNewTabPage: false, setFocus: true, changingTab: true); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(state.DecompiledNodes); } else { MainWindow.Instance.NavigateTo(new(state.ViewedUri, null)); } } - MessageBus.Send(this, new DockWorkspaceActiveTabPageChangedEventArgs()); } } public void InitializeLayout(DockingManager manager) { - var toolPanes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); + var panes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); - this.toolPanes.AddRange(toolPanes); + this.toolPanes.AddRange(panes); manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index df6909ab37..39d623fd9a 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -195,5 +195,21 @@ internal static bool FormatExceptions(this IList exceptions, return true; } + + public static IDisposable PreserveFocus(this IInputElement? inputElement, bool preserve = true) + { + return new RestoreFocusHelper(inputElement, preserve); + } + + private sealed class RestoreFocusHelper(IInputElement? inputElement, bool preserve) : IDisposable + { + public void Dispose() + { + if (preserve) + { + inputElement?.Focus(); + } + } + } } } diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index b570ba12eb..f7cf473677 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -358,7 +358,7 @@ public override void DecompileType(ITypeDefinition type, ITextOutput output, Dec void AddReferenceWarningMessage(MetadataFile module, ITextOutput output) { - var loadedAssembly = MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); + var loadedAssembly = MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies().FirstOrDefault(la => la.GetMetadataFileOrNull() == module); if (loadedAssembly == null || !loadedAssembly.LoadedAssemblyReferencesInfo.HasErrors) return; string line1 = Properties.Resources.WarningSomeAssemblyReference; diff --git a/ILSpy/MainWindow.xaml.cs b/ILSpy/MainWindow.xaml.cs index 7dd5f5c56e..7b1e2b1d6a 100644 --- a/ILSpy/MainWindow.xaml.cs +++ b/ILSpy/MainWindow.xaml.cs @@ -439,7 +439,7 @@ protected override void OnClosing(CancelEventArgs e) base.OnClosing(e); var sessionSettings = SettingsService.Instance.SessionSettings; - sessionSettings.ActiveAssemblyList = AssemblyTreeModel.CurrentAssemblyList.ListName; + sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName; sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); sessionSettings.WindowBounds = this.RestoreBounds; diff --git a/ILSpy/Metadata/CoffHeaderTreeNode.cs b/ILSpy/Metadata/CoffHeaderTreeNode.cs index 78eca5bf2f..36b9c8be8d 100644 --- a/ILSpy/Metadata/CoffHeaderTreeNode.cs +++ b/ILSpy/Metadata/CoffHeaderTreeNode.cs @@ -28,6 +28,8 @@ using ICSharpCode.ILSpy.ViewModels; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Metadata { class CoffHeaderTreeNode : ILSpyTreeNode diff --git a/ILSpy/Metadata/OptionalHeaderTreeNode.cs b/ILSpy/Metadata/OptionalHeaderTreeNode.cs index dc1be30ec7..8353ead80b 100644 --- a/ILSpy/Metadata/OptionalHeaderTreeNode.cs +++ b/ILSpy/Metadata/OptionalHeaderTreeNode.cs @@ -27,6 +27,8 @@ using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy.Metadata { class OptionalHeaderTreeNode : ILSpyTreeNode diff --git a/ILSpy/Search/SearchPane.xaml.cs b/ILSpy/Search/SearchPane.xaml.cs index 2a5a104d6c..454112d5ca 100644 --- a/ILSpy/Search/SearchPane.xaml.cs +++ b/ILSpy/Search/SearchPane.xaml.cs @@ -249,7 +249,7 @@ async void StartSearch(string searchTerm) { searchProgressBar.IsIndeterminate = true; - startedSearch = new(await mainWindow.AssemblyTreeModel.CurrentAssemblyList.GetAllAssemblies(), searchTerm, + startedSearch = new(await mainWindow.AssemblyTreeModel.AssemblyList.GetAllAssemblies(), searchTerm, (SearchMode)searchModeComboBox.SelectedIndex, mainWindow.AssemblyTreeModel.CurrentLanguage, SettingsService.Instance.SessionSettings.LanguageSettings.ShowApiLevel); currentSearch = startedSearch; diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 842fc860f8..40fd4ba7cc 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -414,7 +414,7 @@ void ToolTipClosed(object? sender, EventArgs e) } else if (segment.Reference is EntityReference unresolvedEntity) { - var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList); + var module = unresolvedEntity.ResolveAssembly(MainWindow.Instance.AssemblyTreeModel.AssemblyList); if (module == null) return null; var typeSystem = new DecompilerTypeSystem(module, @@ -474,7 +474,7 @@ void ToolTipClosed(object? sender, EventArgs e) IEntity? ResolveReference(string idString) { - return AssemblyListPaneModel.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()); + return AssemblyListPaneModel.FindEntityInRelevantAssemblies(idString, MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()); } } diff --git a/ILSpy/TreeNodes/AssemblyListTreeNode.cs b/ILSpy/TreeNodes/AssemblyListTreeNode.cs index c7668c1c54..e8a9840876 100644 --- a/ILSpy/TreeNodes/AssemblyListTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyListTreeNode.cs @@ -24,6 +24,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; +using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.TreeView; using ICSharpCode.ILSpyX.TreeView.PlatformAbstractions; diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index ad87b4d416..1c38c4e4c7 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -712,7 +712,7 @@ public void Execute(TextViewContext context) node.RaisePropertyChanged(nameof(ILSpyTreeNode.IsAutoLoaded)); } } - MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.RefreshSave(); + MainWindow.Instance.AssemblyTreeModel.AssemblyList.RefreshSave(); } } diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 3c319ab545..4c151f4d5d 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -85,7 +85,7 @@ public virtual bool Save(ViewModels.TabPageModel tabPage) return false; } - protected internal override void OnChildrenChanged(NotifyCollectionChangedEventArgs e) + public override void OnChildrenChanged(NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { diff --git a/ILSpy/Util/MessageBus.cs b/ILSpy/Util/MessageBus.cs index 40d8743b8d..0ba600ce8d 100644 --- a/ILSpy/Util/MessageBus.cs +++ b/ILSpy/Util/MessageBus.cs @@ -22,16 +22,16 @@ public static void Send(object sender, T e) public static class MessageBus where T : EventArgs { - private static readonly WeakEventSource Subscriptions = new(); + private static readonly WeakEventSource subscriptions = new(); public static event EventHandler Subscribers { - add => Subscriptions.Subscribe(value); - remove => Subscriptions.Unsubscribe(value); + add => subscriptions.Subscribe(value); + remove => subscriptions.Unsubscribe(value); } public static void Send(object sender, T e) { - Subscriptions.Raise(sender, e); + subscriptions.Raise(sender, e); } } @@ -56,8 +56,6 @@ public class LanguageSettingsChangedEventArgs(PropertyChangedEventArgs e) : Wrap public class SessionSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs(e); - public class DockWorkspaceActiveTabPageChangedEventArgs : EventArgs; - public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs { public object Reference { get; } = reference; diff --git a/ILSpy/Views/OpenFromGacDialog.xaml.cs b/ILSpy/Views/OpenFromGacDialog.xaml.cs index 5189be034f..24b9f7b2bf 100644 --- a/ILSpy/Views/OpenFromGacDialog.xaml.cs +++ b/ILSpy/Views/OpenFromGacDialog.xaml.cs @@ -31,6 +31,8 @@ using ICSharpCode.ILSpy.Controls; using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy { /// diff --git a/TestPlugin/MainMenuCommand.cs b/TestPlugin/MainMenuCommand.cs index f2909e15b7..8c28e7fe1d 100644 --- a/TestPlugin/MainMenuCommand.cs +++ b/TestPlugin/MainMenuCommand.cs @@ -23,7 +23,7 @@ public class UnloadAllAssembliesCommand : SimpleCommand { public override void Execute(object parameter) { - foreach (var loadedAssembly in MainWindow.Instance.AssemblyTreeModel.CurrentAssemblyList.GetAssemblies()) + foreach (var loadedAssembly in MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies()) { loadedAssembly.AssemblyList.Unload(loadedAssembly); } From 39b036b0279a3134589a52b73b348578cd037d0d Mon Sep 17 00:00:00 2001 From: tom-englert Date: Sat, 7 Sep 2024 07:19:27 +0200 Subject: [PATCH 08/14] Remove more code from MainWindow, refactor startup sequence --- ICSharpCode.ILSpyX/AssemblyListManager.cs | 5 +- ICSharpCode.ILSpyX/Settings/ILSpySettings.cs | 48 ++---- .../Settings/ISettingsProvider.cs | 7 +- ICSharpCode.ILSpyX/Settings/MiscSettings.cs | 4 +- ILSpy.ReadyToRun/ReadyToRunDisassembler.cs | 1 + ILSpy.ReadyToRun/ReadyToRunOptions.cs | 25 ++- ILSpy/AboutPage.cs | 4 +- ILSpy/App.xaml | 17 +-- ILSpy/App.xaml.cs | 48 +++--- ILSpy/AssemblyTree/AssemblyListPaneModel.cs | 105 +++++++++++-- ILSpy/Commands/CheckForUpdatesCommand.cs | 2 +- ILSpy/Docking/DockWorkspace.cs | 7 +- ILSpy/ILSpy.csproj | 3 +- ILSpy/Languages/Languages.cs | 3 + ILSpy/MainWindow.xaml | 113 +++++++------- ILSpy/MainWindow.xaml.cs | 143 +++--------------- ILSpy/Options/MiscSettingsPanel.xaml | 2 +- ILSpy/Options/MiscSettingsPanel.xaml.cs | 30 ++-- ILSpy/Options/MiscSettingsViewModel.cs | 67 +++----- ILSpy/Options/OptionsDialog.xaml.cs | 15 +- ILSpy/SessionSettings.cs | 6 +- ILSpy/Updates/UpdateSettings.cs | 2 +- ILSpy/Util/SettingsService.cs | 18 +++ 23 files changed, 304 insertions(+), 371 deletions(-) diff --git a/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 8a886df5f5..510af433b3 100644 --- a/ICSharpCode.ILSpyX/AssemblyListManager.cs +++ b/ICSharpCode.ILSpyX/AssemblyListManager.cs @@ -39,7 +39,7 @@ public sealed class AssemblyListManager public const string DotNet35List = ".NET 3.5"; public const string ASPDotNetMVC3List = "ASP.NET (MVC3)"; - private ISettingsProvider settingsProvider; + private readonly ISettingsProvider settingsProvider; public AssemblyListManager(ISettingsProvider settingsProvider) { @@ -69,14 +69,13 @@ public AssemblyListManager(ISettingsProvider settingsProvider) /// public AssemblyList LoadList(string listName) { - this.settingsProvider = this.settingsProvider.Load(); AssemblyList list = DoLoadList(listName); if (!AssemblyLists.Contains(list.ListName)) AssemblyLists.Add(list.ListName); return list; } - AssemblyList DoLoadList(string listName) + AssemblyList DoLoadList(string? listName) { XElement doc = this.settingsProvider["AssemblyLists"]; if (listName != null) diff --git a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs index 63e94299b5..c7dc899f1e 100644 --- a/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs +++ b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs @@ -34,16 +34,11 @@ public class ILSpySettings : ISettingsProvider /// public static ISettingsFilePathProvider? SettingsFilePathProvider { get; set; } - readonly XElement root; + XElement root; - ILSpySettings() + ILSpySettings(XElement? root = null) { - this.root = new XElement("ILSpy"); - } - - ILSpySettings(XElement root) - { - this.root = root; + this.root = root ?? new XElement("ILSpy"); } public XElement this[XName section] { @@ -64,13 +59,7 @@ public static ILSpySettings Load() { try { - XDocument doc = LoadWithoutCheckingCharacters(GetConfigFile()); - if (null == doc.Root) - { - return new ILSpySettings(); - } - - return new ILSpySettings(doc.Root); + return new ILSpySettings(LoadFile(GetConfigFile()).Root); } catch (IOException) { @@ -83,7 +72,7 @@ public static ILSpySettings Load() } } - static XDocument LoadWithoutCheckingCharacters(string fileName) + static XDocument LoadFile(string fileName) { return XDocument.Load(fileName, LoadOptions.None); } @@ -91,16 +80,15 @@ static XDocument LoadWithoutCheckingCharacters(string fileName) /// /// Saves a setting section. /// - public static void SaveSettings(XElement section) + public void SaveSettings(XElement section) { - Update( - delegate (XElement root) { - XElement? existingElement = root.Element(section.Name); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - }); + Update(rootElement => { + XElement? existingElement = rootElement.Element(section.Name); + if (existingElement != null) + existingElement.ReplaceWith(section); + else + rootElement.Add(section); + }); } /// @@ -108,7 +96,7 @@ public static void SaveSettings(XElement section) /// We always reload the file on updates to ensure we aren't overwriting unrelated changes performed /// by another ILSpy instance. /// - public static void Update(Action action) + public void Update(Action action) { using (new MutexProtector(ConfigFileMutex)) { @@ -116,7 +104,7 @@ public static void Update(Action action) XDocument doc; try { - doc = LoadWithoutCheckingCharacters(config); + doc = LoadFile(config); } catch (IOException) { @@ -131,6 +119,7 @@ public static void Update(Action action) doc.Root!.SetAttributeValue("version", DecompilerVersionInfo.Major + "." + DecompilerVersionInfo.Minor + "." + DecompilerVersionInfo.Build + "." + DecompilerVersionInfo.Revision); action(doc.Root); doc.Save(config, SaveOptions.None); + this.root = doc.Root; } } @@ -148,11 +137,6 @@ static string GetConfigFile() // return "ILSpy.xml"; } - ISettingsProvider ISettingsProvider.Load() - { - return Load(); - } - const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971"; /// diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs index 3f62dba764..664acb5e22 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs @@ -22,8 +22,6 @@ using System.Reflection; using System.Xml.Linq; -using static System.Collections.Specialized.BitVector32; - namespace ICSharpCode.ILSpyX.Settings { public interface ISettingsProvider @@ -31,9 +29,8 @@ public interface ISettingsProvider XElement this[XName section] { get; } void Update(Action action); - ISettingsProvider Load(); - public static ICSharpCode.Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) + public static Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) { XElement e = settingsProvider["DecompilerSettings"]; var newSettings = new Decompiler.DecompilerSettings(); @@ -48,7 +45,7 @@ public static ICSharpCode.Decompiler.DecompilerSettings LoadDecompilerSettings(I return newSettings; } - public static void SaveDecompilerSettings(XElement root, ICSharpCode.Decompiler.DecompilerSettings newSettings) + public static void SaveDecompilerSettings(XElement root, Decompiler.DecompilerSettings newSettings) { var properties = typeof(Decompiler.DecompilerSettings).GetProperties() .Where(p => p.GetCustomAttribute()?.Browsable != false); diff --git a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs b/ICSharpCode.ILSpyX/Settings/MiscSettings.cs index d349764d47..c47d5cff2d 100644 --- a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs +++ b/ICSharpCode.ILSpyX/Settings/MiscSettings.cs @@ -23,12 +23,12 @@ namespace ICSharpCode.ILSpyX.Settings { public class MiscSettings : IMiscSettings, ISettingsSection { - private MiscSettings() + public MiscSettings() { } public bool AllowMultipleInstances { get; set; } - public bool LoadPreviousAssemblies { get; set; } + public bool LoadPreviousAssemblies { get; set; } = true; public static MiscSettings Load(ISettingsProvider settingsProvider) { diff --git a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs index 0b7d0ee6c9..e9c11ff938 100644 --- a/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs +++ b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs @@ -26,6 +26,7 @@ using ICSharpCode.Decompiler; using ICSharpCode.Decompiler.IL; using ICSharpCode.Decompiler.Metadata; +using ICSharpCode.ILSpy.Util; using ILCompiler.Reflection.ReadyToRun; using ILCompiler.Reflection.ReadyToRun.Amd64; diff --git a/ILSpy.ReadyToRun/ReadyToRunOptions.cs b/ILSpy.ReadyToRun/ReadyToRunOptions.cs index 365d1dd9a3..1167497bba 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptions.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptions.cs @@ -18,6 +18,7 @@ using System.Xml.Linq; +using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX.Settings; @@ -33,10 +34,8 @@ internal class ReadyToRunOptions public static string GetDisassemblyFormat(ILSpySettings settings) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } + settings ??= SettingsService.Instance.SpySettings; + XElement e = settings[ns + "ReadyToRunOptions"]; XAttribute a = e.Attribute("DisassemblyFormat"); if (a == null) @@ -51,10 +50,8 @@ public static string GetDisassemblyFormat(ILSpySettings settings) public static bool GetIsShowUnwindInfo(ILSpySettings settings) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } + settings ??= SettingsService.Instance.SpySettings; + XElement e = settings[ns + "ReadyToRunOptions"]; XAttribute a = e.Attribute("IsShowUnwindInfo"); @@ -70,10 +67,8 @@ public static bool GetIsShowUnwindInfo(ILSpySettings settings) public static bool GetIsShowDebugInfo(ILSpySettings settings) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } + settings ??= SettingsService.Instance.SpySettings; + XElement e = settings[ns + "ReadyToRunOptions"]; XAttribute a = e.Attribute("IsShowDebugInfo"); @@ -89,10 +84,8 @@ public static bool GetIsShowDebugInfo(ILSpySettings settings) public static bool GetIsShowGCInfo(ILSpySettings settings) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } + settings ??= SettingsService.Instance.SpySettings; + XElement e = settings[ns + "ReadyToRunOptions"]; XAttribute a = e.Attribute("IsShowGCInfo"); diff --git a/ILSpy/AboutPage.cs b/ILSpy/AboutPage.cs index 43303bf889..c9e1989f4b 100644 --- a/ILSpy/AboutPage.cs +++ b/ILSpy/AboutPage.cs @@ -42,7 +42,7 @@ sealed class AboutPage : SimpleCommand { public override void Execute(object parameter) { - MainWindow.Instance.NavigateTo( + MainWindow.Instance.AssemblyTreeModel.NavigateTo( new RequestNavigateEventArgs(new Uri("resource://aboutpage"), null), inNewTabPage: true ); @@ -76,7 +76,7 @@ public static void Display(DecompilerTextView textView) CheckBox checkBox = new CheckBox(); checkBox.Margin = new Thickness(4); checkBox.Content = Resources.AutomaticallyCheckUpdatesEveryWeek; - UpdateSettings settings = new UpdateSettings(ILSpySettings.Load()); + UpdateSettings settings = new UpdateSettings(SettingsService.Instance.SpySettings); checkBox.SetBinding(CheckBox.IsCheckedProperty, new Binding("AutomaticUpdateCheckEnabled") { Source = settings }); return new StackPanel { Margin = new Thickness(0, 4, 0, 0), diff --git a/ILSpy/App.xaml b/ILSpy/App.xaml index 0cfda48ce3..ce2e14086c 100644 --- a/ILSpy/App.xaml +++ b/ILSpy/App.xaml @@ -1,15 +1,14 @@  + xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:styles="urn:TomsToolbox.Wpf.Styles" + xmlns:toms="urn:TomsToolbox" + xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes">