diff --git a/ILSpy/App.xaml.cs b/ILSpy/App.xaml.cs index c96e21dac0..d00eb42045 100644 --- a/ILSpy/App.xaml.cs +++ b/ILSpy/App.xaml.cs @@ -43,6 +43,9 @@ using TomsToolbox.Composition; using TomsToolbox.Wpf.Composition; +using ICSharpCode.ILSpy.Themes; +using System.Globalization; +using System.Threading; namespace ICSharpCode.ILSpy { @@ -75,18 +78,20 @@ public App() SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected; } - SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider()); - InitializeComponent(); - Resources.RegisterDefaultStyles(); - if (!Debugger.IsAttached) { AppDomain.CurrentDomain.UnhandledException += ShowErrorBox; Dispatcher.CurrentDispatcher.UnhandledException += Dispatcher_UnhandledException; } + TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; + + SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider()); + + Resources.RegisterDefaultStyles(); + InitializeMef().GetAwaiter().GetResult(); // Register the export provider so that it can be accessed from WPF/XAML components. @@ -94,6 +99,13 @@ public App() // Add data templates registered via MEF. Resources.MergedDictionaries.Add(DataTemplateManager.CreateDynamicDataTemplates(ExportProvider)); + var sessionSettings = SettingsService.Instance.SessionSettings; + ThemeManager.Current.Theme = sessionSettings.Theme; + if (!string.IsNullOrEmpty(sessionSettings.CurrentCulture)) + { + Thread.CurrentThread.CurrentUICulture = CultureInfo.DefaultThreadCurrentUICulture = new CultureInfo(sessionSettings.CurrentCulture); + } + EventManager.RegisterClassHandler(typeof(Window), Hyperlink.RequestNavigateEvent, new RequestNavigateEventHandler(Window_RequestNavigate)); @@ -109,9 +121,11 @@ public App() string unknownArguments = string.Join(", ", CommandLineArguments.ArgumentsParser.RemainingArguments); MessageBox.Show(unknownArguments, "ILSpy Unknown Command Line Arguments Passed"); } + + SettingsService.Instance.AssemblyListManager.CreateDefaultAssemblyLists(); } - private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions(); + private static void SingleInstance_NewInstanceDetected(object sender, NewInstanceEventArgs e) => ExportProvider.GetExportedValue().HandleSingleInstanceCommandLineArguments(e.Args).HandleExceptions(); static Assembly ResolvePluginDependencies(AssemblyLoadContext context, AssemblyName assemblyName) { @@ -203,7 +217,7 @@ protected override void OnStartup(StartupEventArgs e) MainWindow = new MainWindow(); MainWindow.Loaded += (sender, args) => { - ExportProvider.GetExportedValue().Initialize(); + ExportProvider.GetExportedValue().Initialize(); }; MainWindow.Show(); } @@ -266,7 +280,7 @@ internal static void UnhandledException(Exception exception) void Window_RequestNavigate(object sender, RequestNavigateEventArgs e) { - ExportProvider.GetExportedValue().NavigateTo(e); + ExportProvider.GetExportedValue().NavigateTo(e); } } } \ No newline at end of file diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml b/ILSpy/AssemblyTree/AssemblyListPane.xaml index d0f8e97333..7222fdabff 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.xaml +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml @@ -8,7 +8,7 @@ xmlns:assemblyTree="clr-namespace:ICSharpCode.ILSpy.AssemblyTree" xmlns:toms="urn:TomsToolbox" mc:Ignorable="d" d:DesignHeight="450" d:DesignWidth="800" - d:DataContext="{d:DesignInstance assemblyTree:AssemblyListPaneModel}" + d:DataContext="{d:DesignInstance assemblyTree:AssemblyTreeModel}" AutomationProperties.Name="Assemblies and Classes" ShowRoot="False" AllowDropOrder="True" diff --git a/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs index 4e4ad88885..9b6e7fb799 100644 --- a/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs +++ b/ILSpy/AssemblyTree/AssemblyListPane.xaml.cs @@ -10,7 +10,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree /// /// Interaction logic for AssemblyListPane.xaml /// - [DataTemplate(typeof(AssemblyListPaneModel))] + [DataTemplate(typeof(AssemblyTreeModel))] [PartCreationPolicy(CreationPolicy.NonShared)] public partial class AssemblyListPane { @@ -27,7 +27,7 @@ protected override void OnPropertyChanged(DependencyPropertyChangedEventArgs e) if (e.Property == DataContextProperty) { - if (e.NewValue is not AssemblyListPaneModel model) + if (e.NewValue is not AssemblyTreeModel model) return; model.SetActiveView(this); diff --git a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs similarity index 98% rename from ILSpy/AssemblyTree/AssemblyListPaneModel.cs rename to ILSpy/AssemblyTree/AssemblyTreeModel.cs index da36d0c977..a62a0ce0f3 100644 --- a/ILSpy/AssemblyTree/AssemblyListPaneModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -45,7 +45,6 @@ using ICSharpCode.Decompiler.TypeSystem.Implementation; using System.Reflection.Metadata; -using ICSharpCode.ILSpyX.Extensions; using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.Search; using ICSharpCode.Decompiler; @@ -60,16 +59,16 @@ namespace ICSharpCode.ILSpy.AssemblyTree [ExportToolPane] [PartCreationPolicy(CreationPolicy.Shared)] [Export] - public class AssemblyListPaneModel : ToolPaneModel + public class AssemblyTreeModel : ToolPaneModel { public const string PaneContentId = "assemblyListPane"; AssemblyListPane activeView; AssemblyListTreeNode assemblyListTreeNode; - readonly NavigationHistoryService history = NavigationHistoryService.Instance; + readonly NavigationHistory history = new(); - public AssemblyListPaneModel() + public AssemblyTreeModel() { Title = Resources.Assemblies; ContentId = PaneContentId; @@ -795,6 +794,10 @@ public void NavigateHistory(bool forward) 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") @@ -914,5 +917,16 @@ static void CollapseChildren(SharpTreeNode node) 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..5dd6de369d 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,32 @@ 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) + { + base.OnExecute(sender, e); + + if (assemblyTreeModel.CanNavigateBack) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(false); + } } } } diff --git a/ILSpy/Commands/BrowseForwardCommand.cs b/ILSpy/Commands/BrowseForwardCommand.cs index 3a82764576..aa43692710 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,33 @@ 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) + { + base.OnExecute(sender, e); + + if (assemblyTreeModel.CanNavigateForward) + { + e.Handled = true; + assemblyTreeModel.NavigateHistory(true); + } } + } } diff --git a/ILSpy/Commands/CommandWrapper.cs b/ILSpy/Commands/CommandWrapper.cs index 42b34ee055..e381f38bac 100644 --- a/ILSpy/Commands/CommandWrapper.cs +++ b/ILSpy/Commands/CommandWrapper.cs @@ -17,6 +17,7 @@ // DEALINGS IN THE SOFTWARE. using System; +using System.Windows; using System.Windows.Input; namespace ICSharpCode.ILSpy @@ -28,15 +29,16 @@ class CommandWrapper : ICommand public 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,14 @@ public bool CanExecute(object parameter) { return wrappedCommand.CanExecute(parameter); } + + protected virtual void OnExecute(object sender, ExecutedRoutedEventArgs e) + { + } + + protected virtual void OnCanExecute(object sender, CanExecuteRoutedEventArgs e) + { + e.CanExecute = true; + } } } diff --git a/ILSpy/Commands/OpenCommand.cs b/ILSpy/Commands/OpenCommand.cs index 7c66ac43a8..bc98e26bb7 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,30 @@ 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) + { + base.OnExecute(sender, 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/RefreshCommand.cs b/ILSpy/Commands/RefreshCommand.cs index cfb03bf34c..cd8c3252ec 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,20 @@ 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) + { + base.OnExecute(sender, e); + + assemblyTreeModel.Refresh(); } } } diff --git a/ILSpy/Commands/SaveCommand.cs b/ILSpy/Commands/SaveCommand.cs index 8b8871e0a8..02dc29d687 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,26 @@ 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) + { + base.OnExecute(sender, e); + + SaveCodeContextMenuEntry.Execute(assemblyTreeModel.SelectedNodes.ToList()); } } } diff --git a/ILSpy/Commands/SetThemeCommand.cs b/ILSpy/Commands/SetThemeCommand.cs index 47b68c1003..1f9a1226c9 100644 --- a/ILSpy/Commands/SetThemeCommand.cs +++ b/ILSpy/Commands/SetThemeCommand.cs @@ -7,12 +7,7 @@ public override void Execute(object parameter) { if (parameter is string theme) { - var snapshot = SettingsService.Instance.CreateSnapshot(); - var sessionSettings = snapshot.GetSettings(); - - sessionSettings.Theme = theme; - - snapshot.Save(); + SettingsService.Instance.SessionSettings.Theme = theme; } } } 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/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index 67965d623f..3748038bd9 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -16,11 +16,8 @@ // 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.ILSpyX; @@ -32,7 +29,7 @@ namespace ICSharpCode.ILSpy /// /// Represents the filters applied to the tree view. /// - public class LanguageSettings : ObservableObject + public class LanguageSettings : ObservableObject, IChildSettings { /// /// This dictionary is necessary to remember language versions across language changes. For example, @@ -41,14 +38,17 @@ public class LanguageSettings : ObservableObject /// 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( diff --git a/ILSpy/Languages/Languages.cs b/ILSpy/Languages/Languages.cs index a41dfa8968..41ef1242b8 100644 --- a/ILSpy/Languages/Languages.cs +++ b/ILSpy/Languages/Languages.cs @@ -33,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 diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 2ff2a0621c..6979e2e8c4 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -35,30 +35,6 @@ - - - - - - - - - @@ -184,7 +160,7 @@ -