diff --git a/ICSharpCode.ILSpyX/AssemblyList.cs b/ICSharpCode.ILSpyX/AssemblyList.cs index b52e791173..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. @@ -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/ICSharpCode.ILSpyX/AssemblyListManager.cs b/ICSharpCode.ILSpyX/AssemblyListManager.cs index 8a886df5f5..35e1021ce9 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) { @@ -59,9 +59,9 @@ public AssemblyListManager(ISettingsProvider settingsProvider) public bool UseDebugSymbols { get; set; } - public ObservableCollection AssemblyLists { get; } = new ObservableCollection(); + public ObservableCollection AssemblyLists { get; } = []; - public FileLoaderRegistry LoaderRegistry { get; } = new FileLoaderRegistry(); + public FileLoaderRegistry LoaderRegistry { get; } = new(); /// /// Loads an assembly list from the ILSpySettings. @@ -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/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/Settings/ILSpySettings.cs b/ICSharpCode.ILSpyX/Settings/ILSpySettings.cs index 63e94299b5..d19998587b 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,14 +119,10 @@ 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; } } - void ISettingsProvider.Update(Action action) - { - Update(action); - } - static string GetConfigFile() { if (null != SettingsFilePathProvider) @@ -148,11 +132,6 @@ static string GetConfigFile() // return "ILSpy.xml"; } - ISettingsProvider ISettingsProvider.Load() - { - return Load(); - } - const string ConfigFileMutex = "01A91708-49D1-410D-B8EB-4DE2662B3971"; /// @@ -164,17 +143,16 @@ sealed class MutexProtector : IDisposable public MutexProtector(string name) { - bool createdNew; - this.mutex = new Mutex(true, name, out createdNew); - if (!createdNew) + this.mutex = new Mutex(true, name, out bool createdNew); + if (createdNew) + return; + + try + { + mutex.WaitOne(); + } + catch (AbandonedMutexException) { - try - { - mutex.WaitOne(); - } - catch (AbandonedMutexException) - { - } } } diff --git a/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs b/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs deleted file mode 100644 index 363dba66be..0000000000 --- a/ICSharpCode.ILSpyX/Settings/IMiscSettings.cs +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (c) 2022 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.Xml.Linq; - -namespace ICSharpCode.ILSpyX.Settings -{ - public interface IMiscSettings - { - public bool AllowMultipleInstances { get; set; } - public bool LoadPreviousAssemblies { get; set; } - - public static void Save(XElement root, IMiscSettings miscSettings) - { - var section = new XElement("MiscSettings"); - section.SetAttributeValue(nameof(miscSettings.AllowMultipleInstances), miscSettings.AllowMultipleInstances); - section.SetAttributeValue(nameof(miscSettings.LoadPreviousAssemblies), miscSettings.LoadPreviousAssemblies); - - XElement? existingElement = root.Element("MiscSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - } - } -} diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs index 15b16a09b0..5e3ffba9b5 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs @@ -16,8 +16,6 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System; - namespace ICSharpCode.ILSpyX.Settings { public interface ISettingsFilePathProvider diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs index 3f62dba764..1ddcec1310 100644 --- a/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs +++ b/ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs @@ -17,13 +17,8 @@ // DEALINGS IN THE SOFTWARE. using System; -using System.ComponentModel; -using System.Linq; -using System.Reflection; using System.Xml.Linq; -using static System.Collections.Specialized.BitVector32; - namespace ICSharpCode.ILSpyX.Settings { public interface ISettingsProvider @@ -31,39 +26,7 @@ public interface ISettingsProvider XElement this[XName section] { get; } void Update(Action action); - ISettingsProvider Load(); - - public static ICSharpCode.Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) - { - XElement e = settingsProvider["DecompilerSettings"]; - var newSettings = new Decompiler.DecompilerSettings(); - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false); - foreach (var p in properties) - { - var value = (bool?)e.Attribute(p.Name); - if (value.HasValue) - p.SetValue(newSettings, value.Value); - } - return newSettings; - } - - public static void SaveDecompilerSettings(XElement root, ICSharpCode.Decompiler.DecompilerSettings newSettings) - { - var properties = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false); - - XElement section = new XElement("DecompilerSettings"); - foreach (var p in properties) - { - section.SetAttributeValue(p.Name, p.GetValue(newSettings)); - } - XElement? existingElement = root.Element("DecompilerSettings"); - if (existingElement != null) - existingElement.ReplaceWith(section); - else - root.Add(section); - } + void SaveSettings(XElement section); } } diff --git a/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs b/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs deleted file mode 100644 index 1735289efa..0000000000 --- a/ICSharpCode.ILSpyX/Settings/ISettingsSection.cs +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2022 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; - -namespace ICSharpCode.ILSpyX.Settings -{ - public interface ISettingsSection - { - // This should be abstract, but that needs C# 11.0 (see IParseable) - // Keep it to be enabled in the future - public static TSelf Load(ISettingsProvider settingsProvider) - { - throw new NotImplementedException(); - } - } -} 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.ReadyToRun/ReadyToRunDisassembler.cs b/ILSpy.ReadyToRun/ReadyToRunDisassembler.cs index 0b7d0ee6c9..ce9e62a1e3 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; @@ -50,11 +51,13 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho ReadyToRunMethod readyToRunMethod = runtimeFunction.Method; WriteCommentLine(readyToRunMethod.SignatureString); - if (ReadyToRunOptions.GetIsShowGCInfo(null)) + var options = SettingsService.Instance.GetSettings(); + + if (options.IsShowGCInfo) { if (readyToRunMethod.GcInfo != null) { - string[] lines = readyToRunMethod.GcInfo.ToString().Split(Environment.NewLine); + string[] lines = readyToRunMethod.GcInfo.ToString()?.Split(Environment.NewLine) ?? []; WriteCommentLine("GC info:"); foreach (string line in lines) { @@ -68,12 +71,12 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho } Dictionary unwindInfo = null; - if (ReadyToRunOptions.GetIsShowUnwindInfo(null) && bitness == 64) + if (options.IsShowUnwindInfo && bitness == 64) { unwindInfo = WriteUnwindInfo(); } - bool isShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(null); + bool isShowDebugInfo = options.IsShowDebugInfo; DebugInfoHelper debugInfo = null; if (isShowDebugInfo) { @@ -97,7 +100,7 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho decoder.Decode(out instructions.AllocUninitializedElement()); } - string disassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(null); + string disassemblyFormat = options.DisassemblyFormat; Formatter formatter = null; if (disassemblyFormat.Equals(ReadyToRunOptions.intel)) { @@ -144,7 +147,7 @@ public void Disassemble(PEFile currentFile, int bitness, ulong address, bool sho } } } - if (ReadyToRunOptions.GetIsShowGCInfo(null)) + if (options.IsShowGCInfo) { DecorateGCInfo(instr, baseInstrIP, readyToRunMethod.GcInfo); } diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml index 137e0a6a14..3889236780 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml @@ -1,7 +1,11 @@  + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500" mc:Ignorable="d" + xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" + xmlns:readyToRun="clr-namespace:ICSharpCode.ILSpy.ReadyToRun" + d:DataContext="{d:DesignInstance readyToRun:ReadyToRunOptionsViewModel}"> @@ -14,12 +18,12 @@ - - - + + + - + - + \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs index fbe046ea4a..d48d55ec1a 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs @@ -16,112 +16,47 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; using System.ComponentModel.Composition; -using System.Windows.Controls; -using System.Xml.Linq; using ICSharpCode.ILSpy.Options; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; +using TomsToolbox.Wpf.Composition.Mef; namespace ICSharpCode.ILSpy.ReadyToRun { - [ExportOptionPage(Title = nameof(global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun), Order = 40)] + [DataTemplate(typeof(ReadyToRunOptionsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - partial class ReadyToRunOptionPage : UserControl, IOptionPage + partial class ReadyToRunOptionPage { public ReadyToRunOptionPage() { InitializeComponent(); } - - public void Load(ILSpySettings settings) - { - Options s = new Options(); - s.DisassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(settings); - s.IsShowUnwindInfo = ReadyToRunOptions.GetIsShowUnwindInfo(settings); - s.IsShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(settings); - s.IsShowGCInfo = ReadyToRunOptions.GetIsShowGCInfo(settings); - - this.DataContext = s; - } - - public void LoadDefaults() - { - this.DataContext = new Options(); - } - - public void Save(XElement root) - { - Options s = (Options)this.DataContext; - ReadyToRunOptions.SetDisassemblyOptions(root, s.DisassemblyFormat, s.IsShowUnwindInfo, s.IsShowDebugInfo, s.IsShowGCInfo); - } } - internal class Options : INotifyPropertyChanged + [ExportOptionPage(Order = 40)] + [PartCreationPolicy(CreationPolicy.NonShared)] + class ReadyToRunOptionsViewModel : ObservableObject, IOptionPage { - public string[] DisassemblyFormats { - get { - return ReadyToRunOptions.disassemblyFormats; - } - } - - private bool isShowUnwindInfo; - public bool IsShowUnwindInfo { - get { - return isShowUnwindInfo; - } - set { - isShowUnwindInfo = value; - OnPropertyChanged(nameof(IsShowUnwindInfo)); - } - } - - private bool isShowDebugInfo; + private ReadyToRunOptions options; - public bool IsShowDebugInfo { - get { - return isShowDebugInfo; - } - set { - isShowDebugInfo = value; - OnPropertyChanged(nameof(IsShowDebugInfo)); - } + public ReadyToRunOptions Options { + get => options; + set => SetProperty(ref options, value); } - private bool isShowGCInfo; + public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun; - public bool IsShowGCInfo { - get { - return isShowGCInfo; - } - set { - isShowGCInfo = value; - OnPropertyChanged(nameof(IsShowGCInfo)); - } - } - - private string disassemblyFormat; - - public string DisassemblyFormat { - get { return disassemblyFormat; } - set { - if (disassemblyFormat != value) - { - disassemblyFormat = value; - OnPropertyChanged(nameof(DisassemblyFormat)); - } - } + public void Load(SettingsSnapshot snapshot) + { + Options = snapshot.GetSettings(); } - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged(string propertyName) + public void LoadDefaults() { - if (PropertyChanged != null) - { - PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); - } + Options.LoadFromXml(new("empty")); } } } \ No newline at end of file diff --git a/ILSpy.ReadyToRun/ReadyToRunOptions.cs b/ILSpy.ReadyToRun/ReadyToRunOptions.cs index 365d1dd9a3..9b13f7ba7f 100644 --- a/ILSpy.ReadyToRun/ReadyToRunOptions.cs +++ b/ILSpy.ReadyToRun/ReadyToRunOptions.cs @@ -18,110 +18,77 @@ using System.Xml.Linq; -using ICSharpCode.ILSpyX; -using ICSharpCode.ILSpyX.Settings; +using ICSharpCode.ILSpy.Util; + +using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.ReadyToRun { - internal class ReadyToRunOptions + internal partial class ReadyToRunOptions : ObservableObject, ISettingsSection { private static readonly XNamespace ns = "http://www.ilspy.net/ready-to-run"; internal static string intel = "Intel"; internal static string gas = "AT & T"; - internal static string[] disassemblyFormats = new string[] { intel, gas }; + internal static string[] disassemblyFormats = [intel, gas]; - public static string GetDisassemblyFormat(ILSpySettings settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("DisassemblyFormat"); - if (a == null) - { - return ReadyToRunOptions.intel; - } - else - { - return (string)a; + public string[] DisassemblyFormats { + get { + return disassemblyFormats; } } - public static bool GetIsShowUnwindInfo(ILSpySettings settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("IsShowUnwindInfo"); + private bool isShowUnwindInfo; + public bool IsShowUnwindInfo { + get => isShowUnwindInfo; + set => SetProperty(ref isShowUnwindInfo, value); + } - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + private bool isShowDebugInfo; + public bool IsShowDebugInfo { + get => isShowDebugInfo; + set => SetProperty(ref isShowDebugInfo, value); } - public static bool GetIsShowDebugInfo(ILSpySettings settings) - { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("IsShowDebugInfo"); + private bool isShowGCInfo; + public bool IsShowGCInfo { + get => isShowGCInfo; + set => SetProperty(ref isShowGCInfo, value); + } - if (a == null) - { - return true; - } - else - { - return (bool)a; - } + private string disassemblyFormat; + public string DisassemblyFormat { + get => disassemblyFormat; + set => SetProperty(ref disassemblyFormat, value); } - public static bool GetIsShowGCInfo(ILSpySettings settings) + public XName SectionName { get; } = ns + "ReadyToRunOptions"; + + public void LoadFromXml(XElement e) { - if (settings == null) - { - settings = ILSpySettings.Load(); - } - XElement e = settings[ns + "ReadyToRunOptions"]; - XAttribute a = e.Attribute("IsShowGCInfo"); + XAttribute format = e.Attribute("DisassemblyFormat"); + DisassemblyFormat = format == null ? intel : (string)format; - if (a == null) - { - return false; - } - else - { - return (bool)a; - } + XAttribute unwind = e.Attribute("IsShowUnwindInfo"); + IsShowUnwindInfo = unwind != null && (bool)unwind; + + XAttribute debug = e.Attribute("IsShowDebugInfo"); + IsShowDebugInfo = debug == null || (bool)debug; + + XAttribute showGc = e.Attribute("IsShowGCInfo"); + IsShowGCInfo = showGc != null && (bool)showGc; } - public static void SetDisassemblyOptions(XElement root, string disassemblyFormat, bool isShowUnwindInfo, bool isShowDebugInfo, bool isShowGCInfo) + public XElement SaveToXml() { - XElement section = new XElement(ns + "ReadyToRunOptions"); + var section = new XElement(SectionName); + section.SetAttributeValue("DisassemblyFormat", disassemblyFormat); section.SetAttributeValue("IsShowUnwindInfo", isShowUnwindInfo); section.SetAttributeValue("IsShowDebugInfo", isShowDebugInfo); section.SetAttributeValue("IsShowGCInfo", isShowGCInfo); - XElement existingElement = root.Element(ns + "ReadyToRunOptions"); - if (existingElement != null) - { - existingElement.ReplaceWith(section); - } - else - { - root.Add(section); - } + + return section; } } } 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/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..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.CurrentAssemblyList + AssemblyList = MainWindow.Instance.AssemblyTreeModel.AssemblyList }; 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/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"> + + \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs new file mode 100644 index 0000000000..9b6e7fb799 --- /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(AssemblyTreeModel))] + [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 AssemblyTreeModel 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/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs new file mode 100644 index 0000000000..a62a0ce0f3 --- /dev/null +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -0,0 +1,932 @@ +// 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.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.ILSpy.AppEnv; +using ICSharpCode.ILSpy.Search; +using ICSharpCode.Decompiler; +using System.Text; + +using TomsToolbox.Essentials; +using TomsToolbox.Wpf; +using System.Windows.Navigation; + +namespace ICSharpCode.ILSpy.AssemblyTree +{ + [ExportToolPane] + [PartCreationPolicy(CreationPolicy.Shared)] + [Export] + public class AssemblyTreeModel : ToolPaneModel + { + public const string PaneContentId = "assemblyListPane"; + + AssemblyListPane activeView; + AssemblyListTreeNode assemblyListTreeNode; + + readonly NavigationHistory history = new(); + + public AssemblyTreeModel() + { + Title = Resources.Assemblies; + ContentId = PaneContentId; + IsCloseable = false; + ShortcutKey = new KeyGesture(Key.F6); + + MessageBus.Subscribers += JumpToReference; + MessageBus.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e); + + var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Background, TreeView_SelectionChanged); + SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); + } + + private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e) + { + if (sender is SessionSettings 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; + } + } + else if (sender is LanguageSettings) + { + switch (e.PropertyName) + { + case nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion): + DecompileSelectedNodes(recordHistory: false); + break; + } + } + } + + public AssemblyList AssemblyList { get; private set; } + + 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, ISettingsProvider 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 Task HandleSingleInstanceCommandLineArguments(string[] args) + { + var cmdArgs = CommandLineArguments.Create(args); + + await Dispatcher.InvokeAsync(() => { + + if (!HandleCommandLineArguments(cmdArgs)) + return; + + var window = Application.Current.MainWindow; + + if (!cmdArgs.NoActivate && window is { WindowState: WindowState.Minimized }) + { + window.WindowState = WindowState.Normal; + } + + HandleCommandLineArgumentsAfterShowList(cmdArgs); + }); + } + + public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ISettingsProvider 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 AssemblyList.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() + { + this.AssemblyList = SettingsService.Instance.LoadInitialAssemblyList(); + + HandleCommandLineArguments(App.CommandLineArguments); + + var loadPreviousAssemblies = SettingsService.Instance.MiscSettings.LoadPreviousAssemblies; + if (AssemblyList.GetAssemblies().Length == 0 + && AssemblyList.ListName == AssemblyListManager.DefaultListName + && loadPreviousAssemblies) + { + LoadInitialAssemblies(); + } + + ShowAssemblyList(this.AssemblyList); + + var sessionSettings = SettingsService.Instance.SessionSettings; + if (sessionSettings.ActiveAutoLoadedAssembly != null + && File.Exists(sessionSettings.ActiveAutoLoadedAssembly)) + { + this.AssemblyList.Open(sessionSettings.ActiveAutoLoadedAssembly, true); + } + + Dispatcher.BeginInvoke(DispatcherPriority.Loaded, OpenAssemblies); + } + + void OpenAssemblies() + { + HandleCommandLineArgumentsAfterShowList(App.CommandLineArguments, SettingsService.Instance.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 != AssemblyList.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); + } + + 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.AddTabPage(); + SelectedItem = null; + } + + if (SelectedItem == node) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + } + else + { + activeView?.ScrollIntoView(node); + SelectedItem = node; + } + } + + internal void SelectNodes(IEnumerable nodes, bool ignoreCompilationRequests = false) + { + this.ignoreDecompilationRequests = ignoreCompilationRequests; + + try + { + // 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; + } + + if (SelectedItems.SequenceEqual(nodesList)) + { + Dispatcher.BeginInvoke(RefreshDecompiledView); + return; + } + + SelectedItems.Clear(); + SelectedItems.AddRange(nodesList); + } + 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) + { + using (Keyboard.FocusedElement.PreserveFocus(!focusNode)) + { + 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 && lastNode != null) + { + activeView?.FocusNode(lastNode); + } + } + } + + #region Decompile (TreeView_SelectionChanged) + + void TreeView_SelectionChanged() + { + var delayDecompilationRequestDueToContextMenu = Mouse.RightButton == MouseButtonState.Pressed; + + if (!delayDecompilationRequestDueToContextMenu) + { + DecompileSelectedNodes(); + } + 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; + + var activeTabPage = DockWorkspace.Instance.ActiveTabPage; + + if (recordHistory) + { + var currentState = activeTabPage.GetState(); + if (currentState != null) + history.UpdateCurrent(new NavigationState(activeTabPage, currentState)); + history.Record(new NavigationState(activeTabPage, SelectedItems)); + } + + activeTabPage.SupportsLanguageSwitching = true; + + if (SelectedItems.Count == 1) + { + if (SelectedItem is ILSpyTreeNode node && node.View(activeTabPage)) + return; + } + if (newState?.ViewedUri != null) + { + MainWindow.Instance.AssemblyTreeModel.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)); + } + + public void RefreshDecompiledView() + { + DecompileSelectedNodes(); + } + + 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 NavigateHistory(bool forward) + { + TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; + var state = tabPage.GetState(); + if (state != null) + history.UpdateCurrent(new NavigationState(tabPage, state)); + var newState = forward ? history.GoForward() : history.GoBack(); + + DockWorkspace.Instance.ActiveTabPage = newState.TabPage; + + SelectNodes(newState.TreeNodes, ignoreCompilationRequests: true); + DecompileSelectedNodes(newState.ViewState as DecompilerTextViewState, false); + } + + public bool CanNavigateBack => history.CanNavigateBack; + + public bool CanNavigateForward => history.CanNavigateForward; + + internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, bool inNewTabPage = false) + { + if (e.Uri.Scheme == "resource") + { + if (inNewTabPage) + { + DockWorkspace.Instance.AddTabPage(); + } + + if (e.Uri.Host == "aboutpage") + { + RecordHistory(); + DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); + e.Handled = true; + return; + } + AvalonEditTextOutput output = new AvalonEditTextOutput { + Address = e.Uri, + Title = e.Uri.AbsolutePath, + EnableHyperlinks = true + }; + using (Stream s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath)) + { + using (StreamReader r = new StreamReader(s)) + { + string line; + while ((line = r.ReadLine()) != null) + { + output.Write(line); + output.WriteLine(); + } + } + } + RecordHistory(); + DockWorkspace.Instance.ShowText(output); + e.Handled = true; + } + + void RecordHistory() + { + if (!recordHistory) + return; + TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; + var currentState = tabPage.GetState(); + if (currentState != null) + history.UpdateCurrent(new NavigationState(tabPage, currentState)); + + UnselectAll(ignoreCompilationRequests: true); + + history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); + } + } + + public void Refresh() + { + using (Keyboard.FocusedElement.PreserveFocus()) + { + var path = GetPathForNode(SelectedItem); + ShowAssemblyList(SettingsService.Instance.AssemblyListManager.LoadList(AssemblyList.ListName)); + SelectNode(FindNodeByPath(path, true), inNewTabPage: false); + } + } + + 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()) + { + AssemblyList.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; + } + } + + public void OpenFiles(string[] fileNames, bool focusNode = true) + { + if (fileNames == null) + throw new ArgumentNullException(nameof(fileNames)); + + if (focusNode) + UnselectAll(); + + LoadAssemblies(fileNames, focusNode: focusNode); + } + } +} diff --git a/ILSpy/Commands/BrowseBackCommand.cs b/ILSpy/Commands/BrowseBackCommand.cs index cf424cbd43..ae53e19efc 100644 --- a/ILSpy/Commands/BrowseBackCommand.cs +++ b/ILSpy/Commands/BrowseBackCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,9 +28,30 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class BrowseBackCommand : CommandWrapper { - public BrowseBackCommand() + readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public BrowseBackCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.BrowseBack) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + base.OnCanExecute(sender, e); + + e.Handled = true; + e.CanExecute = assemblyTreeModel.CanNavigateBack; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + if (assemblyTreeModel.CanNavigateBack) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(false); + } } } } diff --git a/ILSpy/Commands/BrowseForwardCommand.cs b/ILSpy/Commands/BrowseForwardCommand.cs index 3a82764576..ea86ebc118 100644 --- a/ILSpy/Commands/BrowseForwardCommand.cs +++ b/ILSpy/Commands/BrowseForwardCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,9 +28,31 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class BrowseForwardCommand : CommandWrapper { - public BrowseForwardCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public BrowseForwardCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.BrowseForward) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + base.OnCanExecute(sender, e); + + e.Handled = true; + e.CanExecute = assemblyTreeModel.CanNavigateForward; } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + if (assemblyTreeModel.CanNavigateForward) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(true); + } + } + } } diff --git a/ILSpy/Commands/CheckForUpdatesCommand.cs b/ILSpy/Commands/CheckForUpdatesCommand.cs index 58438c6043..779f854a10 100644 --- a/ILSpy/Commands/CheckForUpdatesCommand.cs +++ b/ILSpy/Commands/CheckForUpdatesCommand.cs @@ -20,7 +20,6 @@ using System.ComponentModel.Composition; using ICSharpCode.ILSpy.Properties; -using ICSharpCode.ILSpyX.Settings; namespace ICSharpCode.ILSpy { @@ -35,7 +34,7 @@ public override bool CanExecute(object parameter) public override async void Execute(object parameter) { - await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(ILSpySettings.Load(), forceCheck: true); + await MainWindow.Instance.ShowMessageIfUpdatesAvailableAsync(SettingsService.Instance.SpySettings, forceCheck: true); } } } diff --git a/ILSpy/Commands/CommandWrapper.cs b/ILSpy/Commands/CommandWrapper.cs index 42b34ee055..8091843911 100644 --- a/ILSpy/Commands/CommandWrapper.cs +++ b/ILSpy/Commands/CommandWrapper.cs @@ -17,26 +17,28 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Windows; using System.Windows.Input; namespace ICSharpCode.ILSpy { - class CommandWrapper : ICommand + abstract class CommandWrapper : ICommand { private readonly ICommand wrappedCommand; - public CommandWrapper(ICommand wrappedCommand) + protected CommandWrapper(ICommand wrappedCommand) { this.wrappedCommand = wrappedCommand; + + Application.Current.MainWindow?.CommandBindings.Add(new CommandBinding(wrappedCommand, OnExecute, OnCanExecute)); } public static ICommand Unwrap(ICommand command) { - CommandWrapper w = command as CommandWrapper; - if (w != null) + if (command is CommandWrapper w) return w.wrappedCommand; - else - return command; + + return command; } public event EventHandler CanExecuteChanged { @@ -53,5 +55,12 @@ public bool CanExecute(object parameter) { return wrappedCommand.CanExecute(parameter); } + + protected abstract void OnExecute(object sender, ExecutedRoutedEventArgs e); + + protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = true; + } } } diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index c905c9a87b..b041bf7a7f 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -26,9 +26,9 @@ using System.Threading.Tasks; using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; using TomsToolbox.Essentials; @@ -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.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) @@ -60,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,9 +96,10 @@ public override void Execute(object parameter) { const int numRuns = 100; var language = SettingsService.Instance.SessionSettings.LanguageSettings.Language; - var nodes = MainWindow.Instance.SelectedNodes.ToArray(); - var options = MainWindow.Instance.CreateDecompilationOptions(); - Docking.DockWorkspace.Instance.RunWithCancellation(ct => Task.Factory.StartNew(() => { + var nodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes.ToArray(); + 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) @@ -113,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/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..fd83244ebd 100644 --- a/ILSpy/Commands/DecompileInNewViewCommand.cs +++ b/ILSpy/Commands/DecompileInNewViewCommand.cs @@ -25,9 +25,10 @@ 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 +54,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 +76,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 +85,9 @@ 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); + DockWorkspace.Instance.AddTabPage(); + + MainWindow.Instance.AssemblyTreeModel.SelectNodes(nodes); } } } diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index fc44d405a3..07afced394 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.CurrentAssemblyList.GetAssemblies(), loadBalance: true), - new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount, CancellationToken = ct }, - delegate (LoadedAssembly asm) { + Partitioner.Create(MainWindow.Instance.AssemblyTreeModel.AssemblyList.GetAssemblies(), loadBalance: true), + 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 b7c3997d06..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; @@ -110,14 +112,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/OpenCommand.cs b/ILSpy/Commands/OpenCommand.cs index 7c66ac43a8..06ac419e90 100644 --- a/ILSpy/Commands/OpenCommand.cs +++ b/ILSpy/Commands/OpenCommand.cs @@ -19,8 +19,11 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; +using Microsoft.Win32; + namespace ICSharpCode.ILSpy { [ExportToolbarCommand(ToolTip = nameof(Resources.Open), ToolbarIcon = "Images/Open", ToolbarCategory = nameof(Resources.Open), ToolbarOrder = 0)] @@ -28,9 +31,28 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class OpenCommand : CommandWrapper { - public OpenCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public OpenCommand(AssemblyTreeModel assemblyTreeModel) : base(ApplicationCommands.Open) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + e.Handled = true; + OpenFileDialog dlg = new OpenFileDialog { + Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*", + Multiselect = true, + RestoreDirectory = true + }; + + if (dlg.ShowDialog() == true) + { + assemblyTreeModel.OpenFiles(dlg.FileNames); + } } } } diff --git a/ILSpy/Commands/OpenFromGacCommand.cs b/ILSpy/Commands/OpenFromGacCommand.cs index 393e00eeb5..9d1c312148 100644 --- a/ILSpy/Commands/OpenFromGacCommand.cs +++ b/ILSpy/Commands/OpenFromGacCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using ICSharpCode.ILSpy.AppEnv; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -27,6 +28,14 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class OpenFromGacCommand : SimpleCommand { + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public OpenFromGacCommand(AssemblyTreeModel assemblyTreeModel) + { + this.assemblyTreeModel = assemblyTreeModel; + } + public override bool CanExecute(object parameter) { return AppEnvironment.IsWindows; @@ -34,10 +43,14 @@ public override bool CanExecute(object parameter) public override void Execute(object parameter) { - OpenFromGacDialog dlg = new OpenFromGacDialog(); - dlg.Owner = MainWindow.Instance; + OpenFromGacDialog dlg = new OpenFromGacDialog { + Owner = MainWindow.Instance + }; + if (dlg.ShowDialog() == true) - MainWindow.Instance.OpenFiles(dlg.SelectedFileNames); + { + assemblyTreeModel.OpenFiles(dlg.SelectedFileNames); + } } } } 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/RefreshCommand.cs b/ILSpy/Commands/RefreshCommand.cs index cfb03bf34c..e01e70f0e8 100644 --- a/ILSpy/Commands/RefreshCommand.cs +++ b/ILSpy/Commands/RefreshCommand.cs @@ -19,6 +19,7 @@ using System.ComponentModel.Composition; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; namespace ICSharpCode.ILSpy @@ -28,9 +29,18 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class RefreshCommand : CommandWrapper { - public RefreshCommand() + private readonly AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public RefreshCommand(AssemblyTreeModel assemblyTreeModel) : base(NavigationCommands.Refresh) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + assemblyTreeModel.Refresh(); } } } diff --git a/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs b/ILSpy/Commands/RemoveAssembliesWithLoadErrors.cs index c8696104bd..9cc357374a 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.AssemblyList?.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.AssemblyList.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.AssemblyList?.Count > 0; } public override void Execute(object parameter) { - MainWindow.Instance.CurrentAssemblyList?.Clear(); + MainWindow.Instance.AssemblyTreeModel.AssemblyList?.Clear(); } } } diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index d7e38ada3d..41d45728e5 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -32,7 +32,8 @@ using ICSharpCode.ILSpyX.TreeView; using System.ComponentModel.Composition; -using ICSharpCode.ILSpy.Util; +using ICSharpCode.ILSpy.Docking; + namespace ICSharpCode.ILSpy.TextView { @@ -62,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) { @@ -88,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/Commands/SaveCommand.cs b/ILSpy/Commands/SaveCommand.cs index 8b8871e0a8..9e43df69b6 100644 --- a/ILSpy/Commands/SaveCommand.cs +++ b/ILSpy/Commands/SaveCommand.cs @@ -17,9 +17,12 @@ // DEALINGS IN THE SOFTWARE. using System.ComponentModel.Composition; +using System.Linq; using System.Windows.Input; +using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.Properties; +using ICSharpCode.ILSpy.TextView; namespace ICSharpCode.ILSpy { @@ -27,9 +30,24 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] sealed class SaveCommand : CommandWrapper { - public SaveCommand() + private AssemblyTreeModel assemblyTreeModel; + + [ImportingConstructor] + public SaveCommand(AssemblyTreeModel assemblyTreeModel) : base(ApplicationCommands.Save) { + this.assemblyTreeModel = assemblyTreeModel; + } + + protected override void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.Handled = true; + e.CanExecute = SaveCodeContextMenuEntry.CanExecute(assemblyTreeModel.SelectedNodes.ToList()); + } + + protected override void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + SaveCodeContextMenuEntry.Execute(assemblyTreeModel.SelectedNodes.ToList()); } } } 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..1f9a1226c9 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 @@ -7,7 +6,9 @@ public class SetThemeCommand : SimpleCommand public override void Execute(object parameter) { if (parameter is string theme) + { SettingsService.Instance.SessionSettings.Theme = theme; + } } } } 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..4da82c40d0 100644 --- a/ILSpy/ContextMenuEntry.cs +++ b/ILSpy/ContextMenuEntry.cs @@ -22,8 +22,6 @@ 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; @@ -32,6 +30,7 @@ using ICSharpCode.ILSpyX.TreeView; using TomsToolbox.Composition; +using TomsToolbox.Essentials; namespace ICSharpCode.ILSpy { @@ -86,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 }; } } @@ -168,6 +171,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,51 +213,54 @@ 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)); } 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. @@ -256,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. @@ -267,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. @@ -278,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. @@ -289,7 +299,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)) @@ -310,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) @@ -321,8 +342,8 @@ void BuildMenu(IExport[] menuGroup parent.Add(new Separator()); needSeparatorForCategory = false; } - MenuItem menuItem = new MenuItem(); - menuItem.Header = MainWindow.GetResourceString(entryPair.Metadata.Header); + 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 faa7cb2f05..80558388e4 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) { @@ -818,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/DecompilationOptions.cs b/ILSpy/DecompilationOptions.cs index e5d480c3e0..c7c87322d5 100644 --- a/ILSpy/DecompilationOptions.cs +++ b/ILSpy/DecompilationOptions.cs @@ -23,6 +23,8 @@ using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpyX; +using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings; + namespace ICSharpCode.ILSpy { /// @@ -87,7 +89,9 @@ public DecompilationOptions(LanguageVersion version, DecompilerSettings settings { if (!Enum.TryParse(version?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) languageVersion = Decompiler.CSharp.LanguageVersion.Latest; + var newSettings = this.DecompilerSettings = settings.Clone(); + newSettings.SetLanguageVersion(languageVersion); newSettings.ExpandMemberDefinitions = displaySettings.ExpandMemberDefinitions; newSettings.ExpandUsingDeclarations = displaySettings.ExpandUsingDeclarations; diff --git a/ILSpy/Docking/DockLayoutSettings.cs b/ILSpy/Docking/DockLayoutSettings.cs index fc776db795..73ca3aa592 100644 --- a/ILSpy/Docking/DockLayoutSettings.cs +++ b/ILSpy/Docking/DockLayoutSettings.cs @@ -19,7 +19,6 @@ using System; using System.IO; using System.Linq; -using System.Xml; using System.Xml.Linq; using AvalonDock.Layout.Serialization; diff --git a/ILSpy/Docking/DockWorkspace.cs b/ILSpy/Docking/DockWorkspace.cs index a7b4c71022..9ca68cc266 100644 --- a/ILSpy/Docking/DockWorkspace.cs +++ b/ILSpy/Docking/DockWorkspace.cs @@ -33,9 +33,11 @@ using ICSharpCode.ILSpy.Analyzers; using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX.Extensions; +using TomsToolbox.Composition; +using TomsToolbox.Essentials; using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Docking @@ -44,11 +46,22 @@ 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 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); + + // Make sure there is at least one tab open + AddTabPage(); + MessageBus.Subscribers += (sender, e) => CurrentAssemblyList_Changed(sender, e); } @@ -58,52 +71,57 @@ 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) - { + 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; - } - } - if (!found && TabPages.Count > 1) + + 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); + 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 ReadOnlyObservableCollection TabPages { get; } - public ObservableCollection ToolPanes { get; } = new ObservableCollection(); + 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(); @@ -115,7 +133,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; } @@ -131,25 +149,27 @@ public TabPageModel ActiveTabPage { return; } - var state = value.GetState(); + var state = value?.GetState(); if (state != null) { if (state.DecompiledNodes != null) { - MainWindow.Instance.SelectNodes(state.DecompiledNodes, - inNewTabPage: false, setFocus: true, changingActiveTab: true); + MainWindow.Instance.AssemblyTreeModel.SelectNodes(state.DecompiledNodes); } else { - MainWindow.Instance.NavigateTo(new(state.ViewedUri, null)); + MainWindow.Instance.AssemblyTreeModel.NavigateTo(new(state.ViewedUri, null)); } } - MessageBus.Send(this, new DockWorkspaceActiveTabPageChangedEventArgs()); } } public void InitializeLayout(DockingManager manager) { + var panes = exportProvider.GetExportedValues("ToolPane").OrderBy(item => item.Title); + + this.toolPanes.AddRange(panes); + manager.LayoutUpdateStrategy = this; XmlLayoutSerializer serializer = new XmlLayoutSerializer(manager); serializer.LayoutSerializationCallback += LayoutSerializationCallback; @@ -168,7 +188,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) @@ -200,23 +220,23 @@ 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; } 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/Docking/PaneCollection.cs b/ILSpy/Docking/PaneCollection.cs deleted file mode 100644 index 72b1b851be..0000000000 --- a/ILSpy/Docking/PaneCollection.cs +++ /dev/null @@ -1,59 +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.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, ICollection - where T : PaneModel, new() - { - private ObservableCollection observableCollection = new 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(); - } -} diff --git a/ILSpy/ExtensionMethods.cs b/ILSpy/ExtensionMethods.cs index 374ad026e2..39d623fd9a 100644 --- a/ILSpy/ExtensionMethods.cs +++ b/ILSpy/ExtensionMethods.cs @@ -27,9 +27,10 @@ using ICSharpCode.Decompiler.Metadata; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; +using TomsToolbox.Essentials; + namespace ICSharpCode.ILSpy { /// @@ -125,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); @@ -178,20 +179,37 @@ 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; } + + 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/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/ILSpy.csproj b/ILSpy/ILSpy.csproj index 36a8e7b443..9b0dd62593 100644 --- a/ILSpy/ILSpy.csproj +++ b/ILSpy/ILSpy.csproj @@ -1,5 +1,4 @@ - - + WinExe diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index ef7613ccfc..3748038bd9 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -16,22 +16,20 @@ // 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.Generic; -using System.ComponentModel; using System.Linq; -using System.Runtime.CompilerServices; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy { /// /// Represents the filters applied to the tree view. /// - public class LanguageSettings : INotifyPropertyChanged + public class LanguageSettings : ObservableObject, IChildSettings { /// /// This dictionary is necessary to remember language versions across language changes. For example, @@ -40,14 +38,17 @@ public class LanguageSettings : INotifyPropertyChanged /// private readonly Dictionary languageVersionHistory = new Dictionary(); - public LanguageSettings(XElement element) + public LanguageSettings(XElement element, ISettingsSection parent) { + Parent = parent; this.ShowApiLevel = (ApiVisibility?)(int?)element.Element("ShowAPILevel") ?? ApiVisibility.PublicAndInternal; this.Language = Languages.GetLanguage((string)element.Element("Language")) ?? Languages.AllLanguages.First(); this.LanguageVersion = Language.LanguageVersions.FirstOrDefault(v => v.Version == (string)element.Element("LanguageVersion")) ?? Language.LanguageVersions.LastOrDefault(); } + public ISettingsSection Parent { get; } + public XElement SaveAsXml() { return new XElement( @@ -173,20 +174,6 @@ public LanguageVersion LanguageVersion { } } - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - if (PropertyChanged != null) - { - var args = new PropertyChangedEventArgs(propertyName); - - PropertyChanged(this, args); - MessageBus.Send(this, new LanguageSettingsChangedEventArgs(args)); - } - } - - // This class has been initially called FilterSettings, but then has been Hijacked to store language settings as well. // While the filter settings were some sort of local, the language settings are global. This is a bit of a mess. // There has been a lot of workarounds cloning the FilterSettings to pass them down to the tree nodes, without messing up the global language settings. 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..f7cf473677 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.AssemblyList.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..dae3031245 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -29,8 +29,8 @@ using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; using ICSharpCode.Decompiler.Util; +using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.TextView; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX; namespace ICSharpCode.ILSpy @@ -198,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/Languages/Languages.cs b/ILSpy/Languages/Languages.cs index a58df78da1..980f90accc 100644 --- a/ILSpy/Languages/Languages.cs +++ b/ILSpy/Languages/Languages.cs @@ -16,6 +16,8 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +#nullable enable + using System; using System.Collections.ObjectModel; using System.Linq; @@ -31,9 +33,9 @@ public static class Languages /// public static ReadOnlyCollection AllLanguages { get; } = Initialize(App.ExportProvider); - static ReadOnlyCollection Initialize(IExportProvider ep) + static ReadOnlyCollection Initialize(IExportProvider exportProvider) { - var languages = ep.GetExportedValues().ToList(); + var languages = exportProvider.GetExportedValues().ToList(); languages.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); #if DEBUG @@ -52,7 +54,8 @@ public static Language GetLanguage(string name) return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); } - static ILLanguage ilLanguage; + static ILLanguage? ilLanguage; + public static ILLanguage ILLanguage { get { if (ilLanguage == null) diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 39407e92e1..6979e2e8c4 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -1,72 +1,25 @@  - - + - - - - - - - - - @@ -76,38 +29,12 @@ - - - - - - - - - - - @@ -122,9 +49,12 @@ - - - + + + @@ -137,7 +67,7 @@ - + @@ -145,9 +75,12 @@ - - - + + + @@ -183,52 +116,52 @@ + ItemsSource="{Binding AssemblyListManager.AssemblyLists}" + ToolTip="{x:Static properties:Resources.SelectAssemblyListDropdownTooltip}" + SelectedItem="{Binding SessionSettings.ActiveAssemblyList}" /> + ToolTip="{x:Static properties:Resources.ShowPublicOnlyTypesMembers}"> + Style="{StaticResource DarkModeAwareImageStyle}" /> + ToolTip="{x:Static properties:Resources.ShowInternalTypesMembers}"> + Style="{StaticResource DarkModeAwareImageStyle}" /> + ToolTip="{x:Static properties:Resources.ShowAllTypesAndMembers}"> + Style="{StaticResource DarkModeAwareImageStyle}" /> + IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" + ItemsSource="{x:Static local:Languages.AllLanguages}" + ToolTip="{x:Static properties:Resources.SelectLanguageDropdownTooltip}" + SelectedItem="{Binding SessionSettings.LanguageSettings.Language}" /> + ToolTip="{x:Static properties:Resources.SelectVersionDropdownTooltip}" + Visibility="{Binding SelectedItem.HasLanguageVersions, ElementName=languageComboBox, Converter={toms:BooleanToVisibilityConverter}}" + IsEnabled="{Binding Workspace.ActiveTabPage.SupportsLanguageSwitching}" + ItemsSource="{Binding SelectedItem.LanguageVersions, ElementName=languageComboBox, UpdateSourceTrigger=PropertyChanged}" + SelectedItem="{Binding SessionSettings.LanguageSettings.LanguageVersion, UpdateSourceTrigger=PropertyChanged}" /> - + - partial class MainWindow : Window { - bool refreshInProgress, changingActiveTab; - readonly NavigationHistory history = new NavigationHistory(); - - AssemblyList assemblyList; - AssemblyListTreeNode assemblyListTreeNode; - static MainWindow instance; + private readonly MainWindowViewModel mainWindowViewModel = new(); + public static MainWindow Instance { get { return instance; } } - public SharpTreeView AssemblyTreeView { + public AssemblyTreeModel 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 }; - } - public MainWindow() { instance = this; - var sessionSettings = SettingsService.Instance.SessionSettings; - // Make sure Images are initialized on the UI thread. this.Icon = Images.ILSpyIcon; - this.DataContext = new MainWindowViewModel { - Workspace = DockWorkspace.Instance, - SessionSettings = sessionSettings, - AssemblyListManager = SettingsService.Instance.AssemblyListManager - }; + this.DataContext = mainWindowViewModel; - SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); - if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) - { - System.Threading.Thread.CurrentThread.CurrentUICulture = new System.Globalization.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; - } - private void DockWorkspace_ActiveTabPageChanged(object sender, EventArgs e) - { - DockWorkspace dock = DockWorkspace.Instance; + mainWindowViewModel.Workspace.InitializeLayout(dockManager); - 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; - } - } - } + MenuService.Instance.Init(mainMenu, toolBar, InputBindings); - 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; - } + InitFileLoaders(); } void SetWindowBounds(Rect bounds) @@ -174,367 +85,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 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(System.Globalization.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() @@ -549,27 +99,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); - bool boundsOK = false; - foreach (var screen in System.Windows.Forms.Screen.AllScreens) - { - var intersection = System.Drawing.Rectangle.Intersect(boundsRect, screen.WorkingArea); - if (intersection.Width > 10 && intersection.Height > 10) - boundsOK = true; - } - if (boundsOK) - SetWindowBounds(sessionSettings.WindowBounds); - else - SetWindowBounds(SessionSettings.DefaultWindowBounds); + var windowBounds = Rect.Transform(sessionSettings.WindowBounds, source?.CompositionTarget?.TransformToDevice ?? Matrix.Identity); + var boundsRect = new Rectangle((int)windowBounds.Left, (int)windowBounds.Top, (int)windowBounds.Width, (int)windowBounds.Height); + + bool areBoundsValid = Screen.AllScreens.Any(screen => Rectangle.Intersect(boundsRect, screen.WorkingArea) is { Width: > 10, Height: > 10 }); + + SetWindowBounds(areBoundsValid ? sessionSettings.WindowBounds : SessionSettings.DefaultWindowBounds); this.WindowState = sessionSettings.WindowState; } @@ -599,281 +144,11 @@ 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 (!cmdArgs.NoActivate && WindowState == WindowState.Minimized) - WindowState = WindowState.Normal; - - 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; - } - #region Update Check + string updateAvailableDownloadUrl; - public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, bool forceCheck = false) + public async Task ShowMessageIfUpdatesAvailableAsync(ISettingsProvider spySettings, bool forceCheck = false) { string downloadUrl; if (forceCheck) @@ -889,21 +164,21 @@ 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; } - async void downloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) + async void DownloadOrCheckUpdateButtonClick(object sender, RoutedEventArgs e) { if (updateAvailableDownloadUrl != null) { - MainWindow.OpenLink(updateAvailableDownloadUrl); + OpenLink(updateAvailableDownloadUrl); } else { updatePanel.Visibility = Visibility.Collapsed; - string downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(ILSpySettings.Load()); + string downloadUrl = await NotifyOfUpdatesStrategy.CheckForUpdatesAsync(SettingsService.Instance.SpySettings); AdjustUpdateUIAfterCheck(downloadUrl, true); } } @@ -923,327 +198,8 @@ void AdjustUpdateUIAfterCheck(string downloadUrl, bool displayMessage) downloadOrCheckUpdateButton.Content = Properties.Resources.CheckAgain; } } - #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; - } + #endregion public static void OpenLink(string link) { @@ -1274,356 +230,44 @@ public static void ExecuteCommand(string fileName, string arguments) // just ignore all of them. } } - #endregion - - #region Open/Refresh - void OpenCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - e.Handled = true; - OpenFileDialog dlg = new OpenFileDialog(); - dlg.Filter = ".NET assemblies|*.dll;*.exe;*.winmd;*.wasm|Nuget Packages (*.nupkg)|*.nupkg|Portable Program Database (*.pdb)|*.pdb|All files|*.*"; - dlg.Multiselect = true; - dlg.RestoreDirectory = true; - if (dlg.ShowDialog() == true) - { - OpenFiles(dlg.FileNames); - } - } - - public void OpenFiles(string[] fileNames, bool focusNode = true) - { - if (fileNames == null) - throw new ArgumentNullException(nameof(fileNames)); - - if (focusNode) - AssemblyTreeView.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); - } - } - - 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; - } - } - - void SearchCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - DockWorkspace.Instance.ShowToolPane(SearchPaneModel.PaneContentId); - } - #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()); - } - - 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(); - } - } - #endregion - - #region Back/Forward navigation - void BackCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) - { - e.Handled = true; - e.CanExecute = history.CanNavigateBack; - } - - void BackCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - if (history.CanNavigateBack) - { - e.Handled = true; - NavigateHistory(false); - } - } - - void ForwardCommandCanExecute(object sender, CanExecuteRoutedEventArgs e) - { - e.Handled = true; - e.CanExecute = history.CanNavigateForward; - } - - void ForwardCommandExecuted(object sender, ExecutedRoutedEventArgs e) - { - if (history.CanNavigateForward) - { - e.Handled = true; - NavigateHistory(true); - } - } - - void NavigateHistory(bool forward) - { - TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; - var state = tabPage.GetState(); - if (state != null) - 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); - } - #endregion - - internal void NavigateTo(RequestNavigateEventArgs e, bool recordHistory = true, bool inNewTabPage = false) - { - if (e.Uri.Scheme == "resource") - { - if (inNewTabPage) - { - DockWorkspace.Instance.TabPages.Add(); - } - - if (e.Uri.Host == "aboutpage") - { - RecordHistory(); - DockWorkspace.Instance.ActiveTabPage.ShowTextView(AboutPage.Display); - e.Handled = true; - return; - } - AvalonEditTextOutput output = new AvalonEditTextOutput { - Address = e.Uri, - Title = e.Uri.AbsolutePath, - EnableHyperlinks = true - }; - using (Stream s = typeof(App).Assembly.GetManifestResourceStream(typeof(App), e.Uri.AbsolutePath)) - { - using (StreamReader r = new StreamReader(s)) - { - string line; - while ((line = r.ReadLine()) != null) - { - output.Write(line); - output.WriteLine(); - } - } - } - RecordHistory(); - DockWorkspace.Instance.ShowText(output); - e.Handled = true; - } - - void RecordHistory() - { - if (!recordHistory) - return; - TabPageModel tabPage = DockWorkspace.Instance.ActiveTabPage; - var currentState = tabPage.GetState(); - if (currentState != null) - history.UpdateCurrent(new NavigationState(tabPage, currentState)); - ignoreDecompilationRequests = true; - UnselectAll(); - ignoreDecompilationRequests = false; - history.Record(new NavigationState(tabPage, new ViewState { ViewedUri = e.Uri })); - } - } 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; } 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.WindowBounds = this.RestoreBounds; - sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); - sessionSettings.Save(); - } + var snapshot = SettingsService.Instance.CreateSnapshot(); - private string GetAutoLoadedAssemblyNode(SharpTreeNode node) - { - if (node == null) - return null; - while (!(node is TreeNodes.AssemblyTreeNode) && node.Parent != null) - { - node = node.Parent; - } - //this should be an assembly node - var assyNode = node as TreeNodes.AssemblyTreeNode; - var loadedAssy = assyNode.LoadedAssembly; - if (!(loadedAssy.IsLoaded && loadedAssy.IsAutoLoaded)) - return null; + var sessionSettings = snapshot.GetSettings(); - return loadedAssy.FileName; - } + sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName; + sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; + sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); + sessionSettings.WindowBounds = this.RestoreBounds; + sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); - public void UnselectAll() - { - AssemblyTreeView.UnselectAll(); + snapshot.Save(); } - public void SetStatus(string status, Brush foreground) + private static string GetAutoLoadedAssemblyNode(SharpTreeNode node) { - if (this.statusBar.Visibility == Visibility.Collapsed) - this.statusBar.Visibility = Visibility.Visible; - this.statusLabel.Foreground = foreground; - this.statusLabel.Text = status; - } + var assemblyTreeNode = node? + .AncestorsAndSelf() + .OfType() + .FirstOrDefault(); - public ItemCollection GetMainMenuItems() - { - return mainMenu.Items; - } + var loadedAssembly = assemblyTreeNode?.LoadedAssembly; - public ItemCollection GetToolBarItems() - { - return toolBar.Items; + return loadedAssembly is not { IsLoaded: true, IsAutoLoaded: true } + ? null + : loadedAssembly.FileName; } } } diff --git a/ILSpy/MainWindowViewModel.cs b/ILSpy/MainWindowViewModel.cs index 16b2cc0fd8..270f007ec8 100644 --- a/ILSpy/MainWindowViewModel.cs +++ b/ILSpy/MainWindowViewModel.cs @@ -19,12 +19,14 @@ using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpyX; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpy { - class MainWindowViewModel + class MainWindowViewModel : ObservableObject { - public DockWorkspace Workspace { get; set; } - public SessionSettings SessionSettings { get; set; } - public AssemblyListManager AssemblyListManager { get; set; } + public DockWorkspace Workspace => DockWorkspace.Instance; + public SessionSettings SessionSettings => SettingsService.Instance.SessionSettings; + public AssemblyListManager AssemblyListManager => SettingsService.Instance.AssemblyListManager; } } 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/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/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/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..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)] @@ -38,7 +39,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) @@ -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(); } } } 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/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/Options/DecompilerSettings.cs b/ILSpy/Options/DecompilerSettings.cs new file mode 100644 index 0000000000..14f1d32e9a --- /dev/null +++ b/ILSpy/Options/DecompilerSettings.cs @@ -0,0 +1,52 @@ +using System.ComponentModel; + +using ICSharpCode.ILSpyX.Settings; +using System.Linq; +using System.Reflection; +using System.Xml.Linq; + +#nullable enable + +namespace ICSharpCode.ILSpy.Options +{ + public class DecompilerSettings : Decompiler.DecompilerSettings, ISettingsSection + { + static readonly PropertyInfo[] properties = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute()?.Browsable != false) + .ToArray(); + + public XName SectionName => "DecompilerSettings"; + + public XElement SaveToXml() + { + var section = new XElement(SectionName); + + foreach (var p in properties) + { + section.SetAttributeValue(p.Name, p.GetValue(this)); + } + + return section; + } + + public void LoadFromXml(XElement section) + { + foreach (var p in properties) + { + var value = (bool?)section.Attribute(p.Name); + if (value.HasValue) + p.SetValue(this, value.Value); + } + } + + public new DecompilerSettings Clone() + { + var section = SaveToXml(); + + var newSettings = new DecompilerSettings(); + newSettings.LoadFromXml(section); + + return newSettings; + } + } +} diff --git a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs index d530079dcf..e65f72087c 100644 --- a/ILSpy/Options/DecompilerSettingsPanel.xaml.cs +++ b/ILSpy/Options/DecompilerSettingsPanel.xaml.cs @@ -19,47 +19,22 @@ using System.ComponentModel.Composition; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; +using TomsToolbox.Wpf.Composition.Mef; + namespace ICSharpCode.ILSpy.Options { /// /// Interaction logic for DecompilerSettingsPanel.xaml /// - [ExportOptionPage(Title = nameof(Properties.Resources.Decompiler), Order = 10)] + [DataTemplate(typeof(DecompilerSettingsViewModel))] [PartCreationPolicy(CreationPolicy.NonShared)] - internal partial class DecompilerSettingsPanel : IOptionPage + internal partial class DecompilerSettingsPanel { public DecompilerSettingsPanel() { InitializeComponent(); } - - public static Decompiler.DecompilerSettings LoadDecompilerSettings(ILSpySettings settings) - { - return ISettingsProvider.LoadDecompilerSettings(settings); - } - - public void Load(ILSpySettings settings) - { - this.DataContext = new DecompilerSettingsViewModel(LoadDecompilerSettings(settings)); - } - - public void Save(XElement root) - { - var newSettings = ((DecompilerSettingsViewModel)this.DataContext).ToDecompilerSettings(); - ISettingsProvider.SaveDecompilerSettings(root, newSettings); - - SettingsService.Instance.DecompilerSettings = newSettings; - SettingsService.Instance.AssemblyListManager.ApplyWinRTProjections = newSettings.ApplyWindowsRuntimeProjections; - SettingsService.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols; - } - - public void LoadDefaults() - { - SettingsService.Instance.DecompilerSettings = new(); - this.DataContext = new DecompilerSettingsViewModel(SettingsService.Instance.DecompilerSettings); - } } } diff --git a/ILSpy/Options/DecompilerSettingsViewModel.cs b/ILSpy/Options/DecompilerSettingsViewModel.cs index 3a574cca90..27cc520de5 100644 --- a/ILSpy/Options/DecompilerSettingsViewModel.cs +++ b/ILSpy/Options/DecompilerSettingsViewModel.cs @@ -18,6 +18,7 @@ using System.Collections.Generic; using System.ComponentModel; +using System.ComponentModel.Composition; using System.Linq; using System.Reflection; @@ -28,44 +29,63 @@ namespace ICSharpCode.ILSpy.Options { - public sealed class DecompilerSettingsViewModel : ObservableObjectBase + [ExportOptionPage(Order = 10)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public sealed class DecompilerSettingsViewModel : ObservableObjectBase, IOptionPage { - public DecompilerSettingsGroupViewModel[] Settings { get; } + private static readonly PropertyInfo[] propertyInfos = typeof(Decompiler.DecompilerSettings).GetProperties() + .Where(p => p.GetCustomAttribute()?.Browsable != false) + .ToArray(); - public DecompilerSettingsViewModel(Decompiler.DecompilerSettings settings) + public string Title => Resources.Decompiler; + + private DecompilerSettingsGroupViewModel[] settings; + public DecompilerSettingsGroupViewModel[] Settings { + get => settings; + set => SetProperty(ref settings, value); + } + + private DecompilerSettings decompilerSettings; + + public void Load(SettingsSnapshot snapshot) { - Settings = typeof(Decompiler.DecompilerSettings).GetProperties() - .Where(p => p.GetCustomAttribute()?.Browsable != false) - .Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(settings) is true }) + decompilerSettings = snapshot.GetSettings(); + LoadSettings(); + } + + private void LoadSettings() + { + this.Settings = propertyInfos + .Select(p => new DecompilerSettingsItemViewModel(p, decompilerSettings)) .OrderBy(item => item.Category, NaturalStringComparer.Instance) .GroupBy(p => p.Category) .Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray())) .ToArray(); } - public Decompiler.DecompilerSettings ToDecompilerSettings() + public void LoadDefaults() { - var settings = new Decompiler.DecompilerSettings(); + var defaults = new Decompiler.DecompilerSettings(); - foreach (var item in Settings.SelectMany(group => group.Settings)) + foreach (var propertyInfo in propertyInfos) { - item.Property.SetValue(settings, item.IsEnabled); + propertyInfo.SetValue(decompilerSettings, propertyInfo.GetValue(defaults)); } - return settings; + LoadSettings(); } } public sealed class DecompilerSettingsGroupViewModel : ObservableObjectBase { - private bool? _areAllItemsChecked; + private bool? areAllItemsChecked; public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemViewModel[] settings) { Settings = settings; Category = category; - _areAllItemsChecked = GetAreAllItemsChecked(Settings); + areAllItemsChecked = GetAreAllItemsChecked(Settings); foreach (DecompilerSettingsItemViewModel viewModel in settings) { @@ -74,9 +94,9 @@ public DecompilerSettingsGroupViewModel(string category, DecompilerSettingsItemV } public bool? AreAllItemsChecked { - get => _areAllItemsChecked; + get => areAllItemsChecked; set { - SetProperty(ref _areAllItemsChecked, value); + SetProperty(ref areAllItemsChecked, value); if (!value.HasValue) return; @@ -114,15 +134,20 @@ private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e) } } - public sealed class DecompilerSettingsItemViewModel(PropertyInfo property) : ObservableObjectBase + public sealed class DecompilerSettingsItemViewModel(PropertyInfo property, DecompilerSettings decompilerSettings) : ObservableObjectBase { - private bool _isEnabled; + private bool isEnabled = property.GetValue(decompilerSettings) is true; - public PropertyInfo Property { get; } = property; + public PropertyInfo Property => property; public bool IsEnabled { - get => _isEnabled; - set => SetProperty(ref _isEnabled, value); + get => isEnabled; + set { + if (SetProperty(ref isEnabled, value)) + { + property.SetValue(decompilerSettings, value); + } + } } public string Description { get; set; } = GetResourceString(property.GetCustomAttribute()?.Description ?? property.Name); diff --git a/ILSpy/Options/DisplaySettings.cs b/ILSpy/Options/DisplaySettings.cs index f59c31a955..9eb07b3942 100644 --- a/ILSpy/Options/DisplaySettings.cs +++ b/ILSpy/Options/DisplaySettings.cs @@ -16,341 +16,190 @@ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -using System.ComponentModel; -using System.Runtime.CompilerServices; using System.Windows.Media; +using System.Xml.Linq; -using ICSharpCode.ILSpy.Themes; +using TomsToolbox.Wpf; namespace ICSharpCode.ILSpy.Options { /// /// Description of DisplaySettings. /// - public class DisplaySettings : INotifyPropertyChanged + public class DisplaySettings : ObservableObject, ISettingsSection { - public DisplaySettings() - { - this.theme = ThemeManager.Current.DefaultTheme; - this.selectedFont = new FontFamily("Consolas"); - this.selectedFontSize = 10.0 * 4 / 3; - this.sortResults = true; - this.indentationUseTabs = true; - this.indentationSize = 4; - this.indentationTabSize = 4; - this.highlightMatchingBraces = true; - } - - #region INotifyPropertyChanged implementation - public event PropertyChangedEventHandler PropertyChanged; - - protected virtual void OnPropertyChanged(PropertyChangedEventArgs e) - { - PropertyChanged?.Invoke(this, e); - } - - protected void OnPropertyChanged([CallerMemberName] string propertyName = null) - { - OnPropertyChanged(new PropertyChangedEventArgs(propertyName)); - } - #endregion - - string theme; - - public string Theme { - get { return theme; } - set { - if (theme != value) - { - theme = value; - OnPropertyChanged(); - } - } - } - FontFamily selectedFont; - public FontFamily SelectedFont { - get { return selectedFont; } - set { - if (selectedFont != value) - { - selectedFont = value; - OnPropertyChanged(); - } - } + get => selectedFont; + set => SetProperty(ref selectedFont, value); } double selectedFontSize; - public double SelectedFontSize { - get { return selectedFontSize; } - set { - if (selectedFontSize != value) - { - selectedFontSize = value; - OnPropertyChanged(); - } - } + get => selectedFontSize; + set => SetProperty(ref selectedFontSize, value); } bool showLineNumbers; - public bool ShowLineNumbers { - get { return showLineNumbers; } - set { - if (showLineNumbers != value) - { - showLineNumbers = value; - OnPropertyChanged(); - } - } + get => showLineNumbers; + set => SetProperty(ref showLineNumbers, value); } bool showMetadataTokens; - public bool ShowMetadataTokens { - get { return showMetadataTokens; } - set { - if (showMetadataTokens != value) - { - showMetadataTokens = value; - OnPropertyChanged(); - } - } + get => showMetadataTokens; + set => SetProperty(ref showMetadataTokens, value); } bool showMetadataTokensInBase10; - public bool ShowMetadataTokensInBase10 { - get { return showMetadataTokensInBase10; } - set { - if (showMetadataTokensInBase10 != value) - { - showMetadataTokensInBase10 = value; - OnPropertyChanged(); - } - } + get => showMetadataTokensInBase10; + set => SetProperty(ref showMetadataTokensInBase10, value); } bool enableWordWrap; - public bool EnableWordWrap { - get { return enableWordWrap; } - set { - if (enableWordWrap != value) - { - enableWordWrap = value; - OnPropertyChanged(); - } - } + get => enableWordWrap; + set => SetProperty(ref enableWordWrap, value); } - bool sortResults = true; - + bool sortResults; public bool SortResults { - get { return sortResults; } - set { - if (sortResults != value) - { - sortResults = value; - OnPropertyChanged(); - } - } + get => sortResults; + set => SetProperty(ref sortResults, value); } - bool foldBraces = false; - + bool foldBraces; public bool FoldBraces { - get { return foldBraces; } - set { - if (foldBraces != value) - { - foldBraces = value; - OnPropertyChanged(); - } - } + get => foldBraces; + set => SetProperty(ref foldBraces, value); } - bool expandMemberDefinitions = false; - + bool expandMemberDefinitions; public bool ExpandMemberDefinitions { - get { return expandMemberDefinitions; } - set { - if (expandMemberDefinitions != value) - { - expandMemberDefinitions = value; - OnPropertyChanged(); - } - } + get => expandMemberDefinitions; + set => SetProperty(ref expandMemberDefinitions, value); } - bool expandUsingDeclarations = false; - + bool expandUsingDeclarations; public bool ExpandUsingDeclarations { - get { return expandUsingDeclarations; } - set { - if (expandUsingDeclarations != value) - { - expandUsingDeclarations = value; - OnPropertyChanged(); - } - } + get => expandUsingDeclarations; + set => SetProperty(ref expandUsingDeclarations, value); } bool showDebugInfo; - public bool ShowDebugInfo { - get { return showDebugInfo; } - set { - if (showDebugInfo != value) - { - showDebugInfo = value; - OnPropertyChanged(); - } - } + get => showDebugInfo; + set => SetProperty(ref showDebugInfo, value); } - bool indentationUseTabs = true; - + bool indentationUseTabs; public bool IndentationUseTabs { - get { return indentationUseTabs; } - set { - if (indentationUseTabs != value) - { - indentationUseTabs = value; - OnPropertyChanged(); - } - } + get => indentationUseTabs; + set => SetProperty(ref indentationUseTabs, value); } - int indentationTabSize = 4; - + int indentationTabSize; public int IndentationTabSize { - get { return indentationTabSize; } - set { - if (indentationTabSize != value) - { - indentationTabSize = value; - OnPropertyChanged(); - } - } + get => indentationTabSize; + set => SetProperty(ref indentationTabSize, value); } - int indentationSize = 4; - + int indentationSize; public int IndentationSize { - get { return indentationSize; } - set { - if (indentationSize != value) - { - indentationSize = value; - OnPropertyChanged(); - } - } + get => indentationSize; + set => SetProperty(ref indentationSize, value); } - bool highlightMatchingBraces = true; - + bool highlightMatchingBraces; public bool HighlightMatchingBraces { - get { return highlightMatchingBraces; } - set { - if (highlightMatchingBraces != value) - { - highlightMatchingBraces = value; - OnPropertyChanged(); - } - } + get => highlightMatchingBraces; + set => SetProperty(ref highlightMatchingBraces, value); } - bool highlightCurrentLine = false; - + bool highlightCurrentLine; public bool HighlightCurrentLine { - get { return highlightCurrentLine; } - set { - if (highlightCurrentLine != value) - { - highlightCurrentLine = value; - OnPropertyChanged(); - } - } + get => highlightCurrentLine; + set => SetProperty(ref highlightCurrentLine, value); } - bool hideEmptyMetadataTables = true; - + bool hideEmptyMetadataTables; public bool HideEmptyMetadataTables { - get { return hideEmptyMetadataTables; } - set { - if (hideEmptyMetadataTables != value) - { - hideEmptyMetadataTables = value; - OnPropertyChanged(); - } - } + get => hideEmptyMetadataTables; + set => SetProperty(ref hideEmptyMetadataTables, value); } - bool useNestedNamespaceNodes = true; - + bool useNestedNamespaceNodes; public bool UseNestedNamespaceNodes { - get { return useNestedNamespaceNodes; } - set { - if (useNestedNamespaceNodes != value) - { - useNestedNamespaceNodes = value; - OnPropertyChanged(); - } - } + get => useNestedNamespaceNodes; + set => SetProperty(ref useNestedNamespaceNodes, value); } private bool styleWindowTitleBar; - public bool StyleWindowTitleBar { - get { return styleWindowTitleBar; } - set { - if (styleWindowTitleBar != value) - { - styleWindowTitleBar = value; - OnPropertyChanged(); - } - } + get => styleWindowTitleBar; + set => SetProperty(ref styleWindowTitleBar, value); } private bool showRawOffsetsAndBytesBeforeInstruction; - public bool ShowRawOffsetsAndBytesBeforeInstruction { - get { return showRawOffsetsAndBytesBeforeInstruction; } - set { - if (showRawOffsetsAndBytesBeforeInstruction != value) - { - showRawOffsetsAndBytesBeforeInstruction = value; - OnPropertyChanged(); - } - } + get => showRawOffsetsAndBytesBeforeInstruction; + set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value); } - public void CopyValues(DisplaySettings s) + public XName SectionName => "DisplaySettings"; + + public void LoadFromXml(XElement section) + { + SelectedFont = new FontFamily((string)section.Attribute("Font") ?? "Consolas"); + SelectedFontSize = (double?)section.Attribute("FontSize") ?? 10.0 * 4 / 3; + ShowLineNumbers = (bool?)section.Attribute("ShowLineNumbers") ?? false; + ShowMetadataTokens = (bool?)section.Attribute("ShowMetadataTokens") ?? false; + ShowMetadataTokensInBase10 = (bool?)section.Attribute("ShowMetadataTokensInBase10") ?? false; + ShowDebugInfo = (bool?)section.Attribute("ShowDebugInfo") ?? false; + EnableWordWrap = (bool?)section.Attribute("EnableWordWrap") ?? false; + SortResults = (bool?)section.Attribute("SortResults") ?? true; + FoldBraces = (bool?)section.Attribute("FoldBraces") ?? false; + ExpandMemberDefinitions = (bool?)section.Attribute("ExpandMemberDefinitions") ?? false; + ExpandUsingDeclarations = (bool?)section.Attribute("ExpandUsingDeclarations") ?? false; + IndentationUseTabs = (bool?)section.Attribute("IndentationUseTabs") ?? true; + IndentationSize = (int?)section.Attribute("IndentationSize") ?? 4; + IndentationTabSize = (int?)section.Attribute("IndentationTabSize") ?? 4; + HighlightMatchingBraces = (bool?)section.Attribute("HighlightMatchingBraces") ?? true; + HighlightCurrentLine = (bool?)section.Attribute("HighlightCurrentLine") ?? false; + HideEmptyMetadataTables = (bool?)section.Attribute("HideEmptyMetadataTables") ?? true; + UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false; + ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; + StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false; + } + + public XElement SaveToXml() { - this.Theme = s.Theme; - this.SelectedFont = s.selectedFont; - this.SelectedFontSize = s.selectedFontSize; - this.ShowLineNumbers = s.showLineNumbers; - this.ShowMetadataTokens = s.showMetadataTokens; - this.ShowMetadataTokensInBase10 = s.showMetadataTokensInBase10; - this.ShowDebugInfo = s.showDebugInfo; - this.EnableWordWrap = s.enableWordWrap; - this.SortResults = s.sortResults; - this.FoldBraces = s.foldBraces; - this.ExpandMemberDefinitions = s.expandMemberDefinitions; - this.ExpandUsingDeclarations = s.expandUsingDeclarations; - this.IndentationUseTabs = s.indentationUseTabs; - this.IndentationTabSize = s.indentationTabSize; - this.IndentationSize = s.indentationSize; - this.HighlightMatchingBraces = s.highlightMatchingBraces; - this.HighlightCurrentLine = s.highlightCurrentLine; - this.HideEmptyMetadataTables = s.hideEmptyMetadataTables; - this.UseNestedNamespaceNodes = s.useNestedNamespaceNodes; - this.ShowRawOffsetsAndBytesBeforeInstruction = s.showRawOffsetsAndBytesBeforeInstruction; - this.StyleWindowTitleBar = s.styleWindowTitleBar; + var section = new XElement(SectionName); + + section.SetAttributeValue("Font", SelectedFont.Source); + section.SetAttributeValue("FontSize", SelectedFontSize); + section.SetAttributeValue("ShowLineNumbers", ShowLineNumbers); + section.SetAttributeValue("ShowMetadataTokens", ShowMetadataTokens); + section.SetAttributeValue("ShowMetadataTokensInBase10", ShowMetadataTokensInBase10); + section.SetAttributeValue("ShowDebugInfo", ShowDebugInfo); + section.SetAttributeValue("EnableWordWrap", EnableWordWrap); + section.SetAttributeValue("SortResults", SortResults); + section.SetAttributeValue("FoldBraces", FoldBraces); + section.SetAttributeValue("ExpandMemberDefinitions", ExpandMemberDefinitions); + section.SetAttributeValue("ExpandUsingDeclarations", ExpandUsingDeclarations); + section.SetAttributeValue("IndentationUseTabs", IndentationUseTabs); + section.SetAttributeValue("IndentationSize", IndentationSize); + section.SetAttributeValue("IndentationTabSize", IndentationTabSize); + section.SetAttributeValue("HighlightMatchingBraces", HighlightMatchingBraces); + section.SetAttributeValue("HighlightCurrentLine", HighlightCurrentLine); + section.SetAttributeValue("HideEmptyMetadataTables", HideEmptyMetadataTables); + section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes); + section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction); + section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar); + + return section; } } } diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml b/ILSpy/Options/DisplaySettingsPanel.xaml index 1170a8f5bc..9fb50d7a91 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml +++ b/ILSpy/Options/DisplaySettingsPanel.xaml @@ -3,19 +3,15 @@ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" xmlns:local="clr-namespace:ICSharpCode.ILSpy.Options" - xmlns:system="clr-namespace:System;assembly=mscorlib" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" - d:DataContext="{d:DesignInstance local:DisplaySettings}"> - - - + d:DataContext="{d:DesignInstance local:DisplaySettingsViewModel}"> @@ -30,78 +26,58 @@ - + - - - 6 - 7 - 8 - 9 - 10 - 11 - 12 - 13 - 14 - 15 - 16 - 17 - 18 - 19 - 20 - 21 - 22 - 23 - 24 - - - + + + + - + - + - + - - - - - - - - - + + + + + + + + + - - - - + + + + - - + + diff --git a/ILSpy/Options/DisplaySettingsPanel.xaml.cs b/ILSpy/Options/DisplaySettingsPanel.xaml.cs index 9645dacc7d..3b51a16ba3 100644 --- a/ILSpy/Options/DisplaySettingsPanel.xaml.cs +++ b/ILSpy/Options/DisplaySettingsPanel.xaml.cs @@ -27,17 +27,19 @@ using System.Windows.Threading; using System.Xml.Linq; -using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpyX.Settings; +using TomsToolbox.Wpf.Composition.Mef; +using TomsToolbox.Wpf.Converters; + namespace ICSharpCode.ILSpy.Options { /// /// Interaction logic for DisplaySettingsPanel.xaml /// - [ExportOptionPage(Title = nameof(Properties.Resources.Display), Order = 20)] [PartCreationPolicy(CreationPolicy.NonShared)] - public partial class DisplaySettingsPanel : UserControl, IOptionPage + [DataTemplate(typeof(DisplaySettingsViewModel))] + public partial class DisplaySettingsPanel { public DisplaySettingsPanel() { @@ -45,132 +47,6 @@ public DisplaySettingsPanel() DataObject.AddPastingHandler(tabSizeTextBox, OnPaste); DataObject.AddPastingHandler(indentSizeTextBox, OnPaste); - - Task task = new Task(FontLoader); - task.Start(); - task.ContinueWith( - delegate (Task continuation) { - App.Current.Dispatcher.Invoke( - DispatcherPriority.Normal, - (Action)( - () => { - fontSelector.ItemsSource = task.Result; - if (continuation.Exception != null) - { - foreach (var ex in continuation.Exception.InnerExceptions) - { - MessageBox.Show(ex.ToString()); - } - } - }) - ); - } - ); - } - - public void Load(ILSpySettings settings) - { - this.DataContext = LoadDisplaySettings(settings); - } - - static bool IsSymbolFont(FontFamily fontFamily) - { - foreach (var tf in fontFamily.GetTypefaces()) - { - GlyphTypeface glyph; - try - { - if (tf.TryGetGlyphTypeface(out glyph)) - return glyph.Symbol; - } - catch (Exception) - { - return true; - } - } - return false; - } - - static FontFamily[] FontLoader() - { - return (from ff in Fonts.SystemFontFamilies - where !IsSymbolFont(ff) - orderby ff.Source - select ff).ToArray(); - } - - public static DisplaySettings LoadDisplaySettings(ILSpySettings settings, SessionSettings sessionSettings = null) - { - XElement e = settings["DisplaySettings"]; - var s = new DisplaySettings(); - s.SelectedFont = new FontFamily((string)e.Attribute("Font") ?? "Consolas"); - s.SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3; - s.ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false; - s.ShowMetadataTokens = (bool?)e.Attribute("ShowMetadataTokens") ?? false; - s.ShowMetadataTokensInBase10 = (bool?)e.Attribute("ShowMetadataTokensInBase10") ?? false; - s.ShowDebugInfo = (bool?)e.Attribute("ShowDebugInfo") ?? false; - s.EnableWordWrap = (bool?)e.Attribute("EnableWordWrap") ?? false; - s.SortResults = (bool?)e.Attribute("SortResults") ?? true; - s.FoldBraces = (bool?)e.Attribute("FoldBraces") ?? false; - s.ExpandMemberDefinitions = (bool?)e.Attribute("ExpandMemberDefinitions") ?? false; - s.ExpandUsingDeclarations = (bool?)e.Attribute("ExpandUsingDeclarations") ?? false; - s.IndentationUseTabs = (bool?)e.Attribute("IndentationUseTabs") ?? true; - s.IndentationSize = (int?)e.Attribute("IndentationSize") ?? 4; - s.IndentationTabSize = (int?)e.Attribute("IndentationTabSize") ?? 4; - s.HighlightMatchingBraces = (bool?)e.Attribute("HighlightMatchingBraces") ?? true; - s.HighlightCurrentLine = (bool?)e.Attribute("HighlightCurrentLine") ?? false; - s.HideEmptyMetadataTables = (bool?)e.Attribute("HideEmptyMetadataTables") ?? true; - s.UseNestedNamespaceNodes = (bool?)e.Attribute("UseNestedNamespaceNodes") ?? false; - s.ShowRawOffsetsAndBytesBeforeInstruction = (bool?)e.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false; - s.StyleWindowTitleBar = (bool?)e.Attribute("StyleWindowTitleBar") ?? false; - - s.Theme = (sessionSettings ?? SettingsService.Instance.SessionSettings).Theme; - - return s; - } - - public void Save(XElement root) - { - var s = (DisplaySettings)this.DataContext; - - var section = new XElement("DisplaySettings"); - section.SetAttributeValue("Font", s.SelectedFont.Source); - section.SetAttributeValue("FontSize", s.SelectedFontSize); - section.SetAttributeValue("ShowLineNumbers", s.ShowLineNumbers); - section.SetAttributeValue("ShowMetadataTokens", s.ShowMetadataTokens); - section.SetAttributeValue("ShowMetadataTokensInBase10", s.ShowMetadataTokensInBase10); - section.SetAttributeValue("ShowDebugInfo", s.ShowDebugInfo); - section.SetAttributeValue("EnableWordWrap", s.EnableWordWrap); - section.SetAttributeValue("SortResults", s.SortResults); - section.SetAttributeValue("FoldBraces", s.FoldBraces); - section.SetAttributeValue("ExpandMemberDefinitions", s.ExpandMemberDefinitions); - section.SetAttributeValue("ExpandUsingDeclarations", s.ExpandUsingDeclarations); - section.SetAttributeValue("IndentationUseTabs", s.IndentationUseTabs); - section.SetAttributeValue("IndentationSize", s.IndentationSize); - section.SetAttributeValue("IndentationTabSize", s.IndentationTabSize); - section.SetAttributeValue("HighlightMatchingBraces", s.HighlightMatchingBraces); - section.SetAttributeValue("HighlightCurrentLine", s.HighlightCurrentLine); - section.SetAttributeValue("HideEmptyMetadataTables", s.HideEmptyMetadataTables); - section.SetAttributeValue("UseNestedNamespaceNodes", s.UseNestedNamespaceNodes); - section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", s.ShowRawOffsetsAndBytesBeforeInstruction); - section.SetAttributeValue("StyleWindowTitleBar", s.StyleWindowTitleBar); - - SettingsService.Instance.SessionSettings.Theme = s.Theme; - var sessionSettings = SettingsService.Instance.SessionSettings.ToXml(); - - SettingsService.Instance.DisplaySettings.CopyValues(s); - - Update(section); - Update(sessionSettings); - - void Update(XElement element) - { - var existingElement = root.Element(element.Name); - if (existingElement != null) - existingElement.ReplaceWith(element); - else - root.Add(element); - } } private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCompositionEventArgs e) @@ -179,44 +55,39 @@ private void TextBox_PreviewTextInput(object sender, System.Windows.Input.TextCo e.Handled = true; } - private void OnPaste(object sender, DataObjectPastingEventArgs e) + private static void OnPaste(object sender, DataObjectPastingEventArgs e) { if (!e.SourceDataObject.GetDataPresent(DataFormats.UnicodeText, true)) return; + var text = (string)e.SourceDataObject.GetData(DataFormats.UnicodeText, true) ?? string.Empty; + if (!text.All(char.IsDigit)) e.CancelCommand(); } - - public void LoadDefaults() - { - SettingsService.Instance.DisplaySettings.CopyValues(new DisplaySettings()); - this.DataContext = SettingsService.Instance.DisplaySettings; - } } - public class FontSizeConverter : IValueConverter + public sealed class FontSizeConverter : ValueConverter { - public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + protected override object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { if (value is double d) { return Math.Round(d / 4 * 3); } - throw new NotImplementedException(); + return DependencyProperty.UnsetValue; } - public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + protected override object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) { - if (value is string s) - { - if (double.TryParse(s, out double d)) - return d * 4 / 3; - return 11.0 * 4 / 3; - } + if (value is not string s) + return DependencyProperty.UnsetValue; + + if (double.TryParse(s, out double d)) + return d * 4 / 3; - throw new NotImplementedException(); + return 11.0 * 4 / 3; } } } \ No newline at end of file diff --git a/ILSpy/Options/DisplaySettingsViewModel.cs b/ILSpy/Options/DisplaySettingsViewModel.cs new file mode 100644 index 0000000000..4d7c53f432 --- /dev/null +++ b/ILSpy/Options/DisplaySettingsViewModel.cs @@ -0,0 +1,94 @@ +using ICSharpCode.ILSpyX.Settings; +using System.Windows.Media; +using System.Xml.Linq; +using System; +using System.ComponentModel.Composition; +using System.Linq; +using System.Threading.Tasks; +using System.Windows; + +using TomsToolbox.Wpf; +using ICSharpCode.ILSpy.Themes; + +namespace ICSharpCode.ILSpy.Options +{ + [ExportOptionPage(Order = 20)] + [PartCreationPolicy(CreationPolicy.NonShared)] + public class DisplaySettingsViewModel : ObservableObject, IOptionPage + { + private DisplaySettings settings = new(); + private FontFamily[] fontFamilies; + private SessionSettings sessionSettings; + + public DisplaySettingsViewModel() + { + fontFamilies = [settings.SelectedFont]; + + Task.Run(FontLoader).ContinueWith(continuation => { + FontFamilies = continuation.Result; + if (continuation.Exception == null) + return; + foreach (var ex in continuation.Exception.InnerExceptions) + { + MessageBox.Show(ex.ToString()); + } + }); + } + + public string Title => Properties.Resources.Display; + + public DisplaySettings Settings { + get => settings; + set => SetProperty(ref settings, value); + } + + public SessionSettings SessionSettings { + get => sessionSettings; + set => SetProperty(ref sessionSettings, value); + } + + public FontFamily[] FontFamilies { + get => fontFamilies; + set => SetProperty(ref fontFamilies, value); + } + + public int[] FontSizes { get; } = Enumerable.Range(6, 24 - 6 + 1).ToArray(); + + public void Load(SettingsSnapshot snapshot) + { + Settings = snapshot.GetSettings(); + SessionSettings = snapshot.GetSettings(); + } + + static bool IsSymbolFont(FontFamily fontFamily) + { + foreach (var tf in fontFamily.GetTypefaces()) + { + try + { + if (tf.TryGetGlyphTypeface(out GlyphTypeface glyph)) + return glyph.Symbol; + } + catch (Exception) + { + return true; + } + } + return false; + } + + static FontFamily[] FontLoader() + { + return Fonts.SystemFontFamilies + .Where(ff => !IsSymbolFont(ff)) + .OrderBy(ff => ff.Source) + .ToArray(); + } + + public void LoadDefaults() + { + Settings.LoadFromXml(new XElement("empty")); + SessionSettings.Theme = ThemeManager.Current.DefaultTheme; + } + } +} diff --git a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs b/ILSpy/Options/MiscSettings.cs similarity index 56% rename from ICSharpCode.ILSpyX/Settings/MiscSettings.cs rename to ILSpy/Options/MiscSettings.cs index d349764d47..75f35e81d7 100644 --- a/ICSharpCode.ILSpyX/Settings/MiscSettings.cs +++ b/ILSpy/Options/MiscSettings.cs @@ -19,25 +19,42 @@ using System; using System.Xml.Linq; +using TomsToolbox.Wpf; + namespace ICSharpCode.ILSpyX.Settings { - public class MiscSettings : IMiscSettings, ISettingsSection + public class MiscSettings : ObservableObject, ISettingsSection { - private MiscSettings() - { + private bool allowMultipleInstances; + private bool loadPreviousAssemblies = true; + + public bool AllowMultipleInstances { + get => allowMultipleInstances; + set => SetProperty(ref allowMultipleInstances, value); + } + + public bool LoadPreviousAssemblies { + get => loadPreviousAssemblies; + set => SetProperty(ref loadPreviousAssemblies, value); } - public bool AllowMultipleInstances { get; set; } - public bool LoadPreviousAssemblies { get; set; } + public XName SectionName => "MiscSettings"; - public static MiscSettings Load(ISettingsProvider settingsProvider) + public void LoadFromXml(XElement e) { - XElement e = settingsProvider["MiscSettings"]; - var s = new MiscSettings(); - s.AllowMultipleInstances = (bool?)e.Attribute(nameof(s.AllowMultipleInstances)) ?? false; - s.LoadPreviousAssemblies = (bool?)e.Attribute(nameof(s.LoadPreviousAssemblies)) ?? true; + AllowMultipleInstances = (bool?)e.Attribute(nameof(AllowMultipleInstances)) ?? false; + LoadPreviousAssemblies = (bool?)e.Attribute(nameof(LoadPreviousAssemblies)) ?? true; + } + + public XElement SaveToXml() + { + var section = new XElement(SectionName); - return s; + section.SetAttributeValue(nameof(AllowMultipleInstances), AllowMultipleInstances); + section.SetAttributeValue(nameof(LoadPreviousAssemblies), LoadPreviousAssemblies); + + return section; } } } + diff --git a/ILSpy/Options/MiscSettingsPanel.xaml b/ILSpy/Options/MiscSettingsPanel.xaml index 502f65fcc7..c3a984c9b0 100644 --- a/ILSpy/Options/MiscSettingsPanel.xaml +++ b/ILSpy/Options/MiscSettingsPanel.xaml @@ -2,16 +2,17 @@ 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:d="http://schemas.microsoft.com/expression/blend/2008" mc:Ignorable="d" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" - mc:Ignorable="d" + xmlns:options="clr-namespace:ICSharpCode.ILSpy.Options" + d:DataContext="{d:DesignInstance options:MiscSettingsViewModel}" d:DesignHeight="300" d:DesignWidth="300"> - - -