diff --git a/.editorconfig b/.editorconfig index 1ea262ade4..b2021e8c6a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -147,3 +147,5 @@ dotnet_naming_rule.private_fields_rule.symbols = private_fields_symbols # MEF006: No importing constructor dotnet_diagnostic.MEF006.severity = silent +# MEF002: Import attributes, to be removed after final DI migration +dotnet_diagnostic.MEF002.severity = silent diff --git a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs index b201a66089..6b0bff7386 100644 --- a/ILSpy.ReadyToRun/ReadyToRunLanguage.cs +++ b/ILSpy.ReadyToRun/ReadyToRunLanguage.cs @@ -218,7 +218,7 @@ public override void DecompileMethod(IMethod method, ITextOutput output, Decompi public override RichText GetRichTextTooltip(IEntity entity) { - return Languages.ILLanguage.GetRichTextTooltip(entity); + return LanguageService.Instance.ILLanguage.GetRichTextTooltip(entity); } private ReadyToRunReaderCacheEntry GetReader(LoadedAssembly assembly, MetadataFile file) diff --git a/ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs b/ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs index 1149418e86..7b6f77c281 100644 --- a/ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs +++ b/ILSpy.Tests/Analyzers/MemberImplementsInterfaceAnalyzerTests.cs @@ -145,7 +145,7 @@ public void VerifyReturnsOnlyInterfaceMembers() var analyzer = new MemberImplementsInterfaceAnalyzer(); // Act - var results = analyzer.Analyze(symbol, new AnalyzerContext() { AssemblyList = new ILSpyX.AssemblyList(), Language = new CSharpLanguage() }); + var results = analyzer.Analyze(symbol, new AnalyzerContext() { AssemblyList = new ILSpyX.AssemblyList(), Language = new CSharpLanguage([]) }); // Assert Assert.That(results, Is.Not.Null); diff --git a/ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs b/ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs index 033deaa1d1..79bbbee7dc 100644 --- a/ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs +++ b/ILSpy.Tests/Analyzers/MethodUsesAnalyzerTests.cs @@ -30,7 +30,7 @@ public void Setup() testAssembly = assemblyList.OpenAssembly(typeof(MethodUsesAnalyzerTests).Assembly.Location); assemblyList.OpenAssembly(typeof(void).Assembly.Location); testAssemblyTypeSystem = testAssembly.GetTypeSystemOrNull(); - language = new CSharpLanguage(); + language = new CSharpLanguage([]); typeDefinition = testAssemblyTypeSystem.FindType(typeof(TestCases.Main.MainAssembly)).GetDefinition(); } diff --git a/ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs b/ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs index 972cb6fc7c..2a47b84acc 100644 --- a/ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs +++ b/ILSpy.Tests/Analyzers/TypeUsedByAnalyzerTests.cs @@ -41,7 +41,7 @@ public void Setup() assemblyList = new AssemblyList(); testAssembly = assemblyList.OpenAssembly(typeof(MethodUsesAnalyzerTests).Assembly.Location); testAssemblyTypeSystem = new DecompilerTypeSystem(testAssembly.GetMetadataFileOrNull(), testAssembly.GetAssemblyResolver()); - language = new CSharpLanguage(); + language = new CSharpLanguage([]); } [Test] diff --git a/ILSpy/Analyzers/AnalyzerTreeNode.cs b/ILSpy/Analyzers/AnalyzerTreeNode.cs index fb052303ee..d2cffeb49f 100644 --- a/ILSpy/Analyzers/AnalyzerTreeNode.cs +++ b/ILSpy/Analyzers/AnalyzerTreeNode.cs @@ -25,7 +25,7 @@ namespace ICSharpCode.ILSpy.Analyzers { public abstract class AnalyzerTreeNode : SharpTreeNode { - public Language Language => SettingsService.Instance.SessionSettings.LanguageSettings.Language; + public Language Language => LanguageService.Instance.Language; public override bool CanDelete() { diff --git a/ILSpy/AssemblyTree/AssemblyTreeModel.cs b/ILSpy/AssemblyTree/AssemblyTreeModel.cs index 59a77e2a0b..b94668f14e 100644 --- a/ILSpy/AssemblyTree/AssemblyTreeModel.cs +++ b/ILSpy/AssemblyTree/AssemblyTreeModel.cs @@ -108,7 +108,7 @@ private void Settings_PropertyChanged(object? sender, PropertyChangedEventArgs e { switch (e.PropertyName) { - case nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion): + case nameof(LanguageSettings.LanguageId) or nameof(LanguageSettings.LanguageVersionId): RefreshDecompiledView(); break; default: @@ -152,7 +152,7 @@ private bool HandleCommandLineArguments(CommandLineArguments args) { LoadAssemblies(args.AssembliesToLoad, commandLineLoadedAssemblies, focusNode: false); if (args.Language != null) - SettingsService.Instance.SessionSettings.LanguageSettings.Language = Languages.GetLanguage(args.Language); + LanguageService.Instance.Language = LanguageService.Instance.GetLanguage(args.Language); return true; } @@ -787,7 +787,7 @@ public void DecompileSelectedNodes(DecompilerTextViewState? newState = null) return; } - var options = SettingsService.Instance.CreateDecompilationOptions(activeTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(activeTabPage); options.TextViewState = newState; activeTabPage.ShowTextViewAsync(textView => textView.DecompileAsync(this.CurrentLanguage, this.SelectedNodes, options)); } @@ -797,9 +797,9 @@ public void RefreshDecompiledView() DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); } - public Language CurrentLanguage => SettingsService.Instance.SessionSettings.LanguageSettings.Language; + public Language CurrentLanguage => LanguageService.Instance.Language; - public LanguageVersion CurrentLanguageVersion => SettingsService.Instance.SessionSettings.LanguageSettings.LanguageVersion; + public LanguageVersion? CurrentLanguageVersion => LanguageService.Instance.LanguageVersion; public IEnumerable SelectedNodes { get { diff --git a/ILSpy/Commands/DecompileAllCommand.cs b/ILSpy/Commands/DecompileAllCommand.cs index b041bf7a7f..d719fa3c41 100644 --- a/ILSpy/Commands/DecompileAllCommand.cs +++ b/ILSpy/Commands/DecompileAllCommand.cs @@ -20,6 +20,7 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.ComponentModel.Composition; using System.Diagnostics; using System.Linq; @@ -31,14 +32,20 @@ using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpyX; -using TomsToolbox.Essentials; - namespace ICSharpCode.ILSpy { [ExportMainMenuCommand(ParentMenuID = nameof(Resources._File), Header = nameof(Resources.DEBUGDecompile), MenuCategory = nameof(Resources.Open), MenuOrder = 2.5)] [PartCreationPolicy(CreationPolicy.Shared)] sealed class DecompileAllCommand : SimpleCommand { + private readonly IReadOnlyCollection resourceFileHandlers; + + [ImportingConstructor] + public DecompileAllCommand([ImportMany] IEnumerable resourceFileHandlers) + { + this.resourceFileHandlers = resourceFileHandlers.ToArray(); + } + public override bool CanExecute(object parameter) { return System.IO.Directory.Exists("c:\\temp\\decompiled"); @@ -60,10 +67,10 @@ public override void Execute(object parameter) { try { - var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.CancellationToken = ct; options.FullDecompilation = true; - new CSharpLanguage().DecompileAssembly(asm, new PlainTextOutput(writer), options); + new CSharpLanguage(resourceFileHandlers).DecompileAssembly(asm, new PlainTextOutput(writer), options); } catch (Exception ex) { @@ -95,10 +102,10 @@ sealed class Decompile100TimesCommand : SimpleCommand public override void Execute(object parameter) { const int numRuns = 100; - var language = SettingsService.Instance.SessionSettings.LanguageSettings.Language; + var language = LanguageService.Instance.Language; var nodes = MainWindow.Instance.AssemblyTreeModel.SelectedNodes.ToArray(); DockWorkspace dockWorkspace = DockWorkspace.Instance; - var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { options.CancellationToken = ct; Stopwatch w = Stopwatch.StartNew(); diff --git a/ILSpy/Commands/DisassembleAllCommand.cs b/ILSpy/Commands/DisassembleAllCommand.cs index 07afced394..5ef572ee82 100644 --- a/ILSpy/Commands/DisassembleAllCommand.cs +++ b/ILSpy/Commands/DisassembleAllCommand.cs @@ -56,7 +56,7 @@ public override void Execute(object parameter) { try { - var options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; new ILLanguage().DecompileAssembly(asm, new Decompiler.PlainTextOutput(writer), options); diff --git a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs index 4eb7449920..f410e8ada1 100644 --- a/ILSpy/Commands/GeneratePdbContextMenuEntry.cs +++ b/ILSpy/Commands/GeneratePdbContextMenuEntry.cs @@ -75,7 +75,7 @@ internal static void GeneratePdbForAssembly(LoadedAssembly assembly) if (dlg.ShowDialog() != true) return; DockWorkspace dockWorkspace = DockWorkspace.Instance; - DecompilationOptions options = SettingsService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + DecompilationOptions options = LanguageService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); string fileName = dlg.FileName; dockWorkspace.RunWithCancellation(ct => Task.Factory.StartNew(() => { AvalonEditTextOutput output = new AvalonEditTextOutput(); diff --git a/ILSpy/Commands/SaveCodeContextMenuEntry.cs b/ILSpy/Commands/SaveCodeContextMenuEntry.cs index df8c78c773..c931ffe00d 100644 --- a/ILSpy/Commands/SaveCodeContextMenuEntry.cs +++ b/ILSpy/Commands/SaveCodeContextMenuEntry.cs @@ -62,7 +62,7 @@ public static void Execute(IReadOnlyList selectedNodes) var settingsService = SettingsService.Instance; var dockWorkspace = Docking.DockWorkspace.Instance; - var currentLanguage = settingsService.SessionSettings.LanguageSettings.Language; + var currentLanguage = LanguageService.Instance.Language; var tabPage = dockWorkspace.ActiveTabPage; tabPage.ShowTextView(textView => { if (selectedNodes.Count == 1 && selectedNodes[0] is ILSpyTreeNode singleSelection) @@ -88,7 +88,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 = settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage); options.FullDecompilation = true; textView.SaveToDisk(currentLanguage, selectedNodes.OfType(), options); }); diff --git a/ILSpy/LanguageSettings.cs b/ILSpy/LanguageSettings.cs index 3748038bd9..c07b993ec8 100644 --- a/ILSpy/LanguageSettings.cs +++ b/ILSpy/LanguageSettings.cs @@ -17,13 +17,14 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Xml.Linq; using ICSharpCode.ILSpyX; using TomsToolbox.Wpf; +#nullable enable + namespace ICSharpCode.ILSpy { /// @@ -42,9 +43,8 @@ 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(); + this.LanguageId = (string?)element.Element("Language"); + this.LanguageVersionId = (string?)element.Element("LanguageVersion"); } public ISettingsSection Parent { get; } @@ -54,8 +54,8 @@ public XElement SaveAsXml() return new XElement( "FilterSettings", new XElement("ShowAPILevel", (int)this.ShowApiLevel), - new XElement("Language", this.Language.Name), - new XElement("LanguageVersion", this.LanguageVersion?.Version) + new XElement("Language", this.LanguageId), + new XElement("LanguageVersion", this.LanguageVersionId) ); } @@ -111,7 +111,7 @@ public bool ApiVisAll { } } - Language language; + string? languageId; /// /// Gets/Sets the current language. @@ -120,37 +120,12 @@ public bool ApiVisAll { /// While this isn't related to filtering, having it as part of the FilterSettings /// makes it easy to pass it down into all tree nodes. /// - public Language Language { - get { return language; } - set { - if (language != value) - { - if (language != null && language.HasLanguageVersions) - { - languageVersionHistory[language] = languageVersion; - } - language = value; - OnPropertyChanged(); - if (language.HasLanguageVersions) - { - if (languageVersionHistory.TryGetValue(value, out var version)) - { - LanguageVersion = version; - } - else - { - LanguageVersion = Language.LanguageVersions.Last(); - } - } - else - { - LanguageVersion = default; - } - } - } + public string? LanguageId { + get => languageId; + set => SetProperty(ref languageId, value); } - LanguageVersion languageVersion; + string? languageVersionId; /// /// Gets/Sets the current language version. @@ -159,19 +134,9 @@ public Language Language { /// While this isn't related to filtering, having it as part of the FilterSettings /// makes it easy to pass it down into all tree nodes. /// - public LanguageVersion LanguageVersion { - get { return languageVersion; } - set { - if (languageVersion != value) - { - languageVersion = value; - if (language.HasLanguageVersions) - { - languageVersionHistory[language] = languageVersion; - } - OnPropertyChanged(); - } - } + public string? LanguageVersionId { + get { return languageVersionId; } + set => SetProperty(ref languageVersionId, value); } // This class has been initially called FilterSettings, but then has been Hijacked to store language settings as well. diff --git a/ILSpy/Languages/CSharpLanguage.cs b/ILSpy/Languages/CSharpLanguage.cs index f7cf473677..4607877b3b 100644 --- a/ILSpy/Languages/CSharpLanguage.cs +++ b/ILSpy/Languages/CSharpLanguage.cs @@ -38,7 +38,6 @@ using ICSharpCode.Decompiler.Output; using ICSharpCode.Decompiler.Solution; using ICSharpCode.Decompiler.TypeSystem; -using ICSharpCode.Decompiler.Util; using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpyX; @@ -56,18 +55,26 @@ namespace ICSharpCode.ILSpy [PartCreationPolicy(CreationPolicy.Shared)] public class CSharpLanguage : Language { + readonly IReadOnlyCollection resourceFileHandlers; + string name = "C#"; bool showAllMembers = false; int transformCount = int.MaxValue; + [ImportingConstructor] + public CSharpLanguage([ImportMany] IEnumerable resourceFileHandlers) + { + this.resourceFileHandlers = resourceFileHandlers.ToArray(); + } + #if DEBUG - internal static IEnumerable GetDebugLanguages() + internal static IEnumerable GetDebugLanguages(IReadOnlyCollection resourceFileHandlers) { string lastTransformName = "no transforms"; int transformCount = 0; foreach (var transform in CSharpDecompiler.GetAstTransforms()) { - yield return new CSharpLanguage { + yield return new CSharpLanguage(resourceFileHandlers) { transformCount = transformCount, name = "C# - " + lastTransformName, showAllMembers = true @@ -75,7 +82,7 @@ internal static IEnumerable GetDebugLanguages() lastTransformName = "after " + transform.GetType().Name; transformCount++; } - yield return new CSharpLanguage { + yield return new CSharpLanguage(resourceFileHandlers) { name = "C# - " + lastTransformName, showAllMembers = true }; @@ -430,8 +437,9 @@ public override ProjectId DecompileAssembly(LoadedAssembly assembly, ITextOutput { options.DecompilerSettings.UseSdkStyleProjectFormat = false; } - var decompiler = new ILSpyWholeProjectDecompiler(assembly, options); - decompiler.ProgressIndicator = options.Progress; + var decompiler = new ILSpyWholeProjectDecompiler(assembly, options, resourceFileHandlers) { + ProgressIndicator = options.Progress + }; return decompiler.DecompileProject(module, options.SaveAsProjectDirectory, new TextOutputWriter(output), options.CancellationToken); } else @@ -542,18 +550,20 @@ class ILSpyWholeProjectDecompiler : WholeProjectDecompiler { readonly LoadedAssembly assembly; readonly DecompilationOptions options; + private readonly IReadOnlyCollection resourceFileHandlers; - public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options) + public ILSpyWholeProjectDecompiler(LoadedAssembly assembly, DecompilationOptions options, IReadOnlyCollection resourceFileHandlers) : base(options.DecompilerSettings, assembly.GetAssemblyResolver(options.DecompilerSettings.AutoLoadAssemblyReferences, options.DecompilerSettings.ApplyWindowsRuntimeProjections), null, assembly.GetAssemblyReferenceClassifier(options.DecompilerSettings.ApplyWindowsRuntimeProjections), assembly.GetDebugInfoOrNull()) { this.assembly = assembly; this.options = options; + this.resourceFileHandlers = resourceFileHandlers; } protected override IEnumerable WriteResourceToFile(string fileName, string resourceName, Stream entryStream) { var context = new ResourceFileHandlerContext(options); - foreach (var handler in App.ExportProvider.GetExportedValues()) + foreach (var handler in resourceFileHandlers) { if (handler.CanHandle(fileName, context)) { diff --git a/ILSpy/Languages/ILLanguage.cs b/ILSpy/Languages/ILLanguage.cs index dae3031245..5bd8ee4e42 100644 --- a/ILSpy/Languages/ILLanguage.cs +++ b/ILSpy/Languages/ILLanguage.cs @@ -201,7 +201,7 @@ public override RichText GetRichTextTooltip(IEntity entity) var settingsService = SettingsService.Instance; var dockWorkspace = DockWorkspace.Instance; - var disasm = CreateDisassembler(output, settingsService.CreateDecompilationOptions(dockWorkspace.ActiveTabPage)); + var disasm = CreateDisassembler(output, LanguageService.Instance.CreateDecompilationOptions(dockWorkspace.ActiveTabPage)); MetadataFile module = entity.ParentModule?.MetadataFile; if (module == null) { diff --git a/ILSpy/Languages/LanguageService.cs b/ILSpy/Languages/LanguageService.cs new file mode 100644 index 0000000000..e060c117df --- /dev/null +++ b/ILSpy/Languages/LanguageService.cs @@ -0,0 +1,152 @@ +// Copyright (c) 2011 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. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; + +using ICSharpCode.Decompiler; +using ICSharpCode.ILSpy.ViewModels; +using ICSharpCode.ILSpyX; + +using TomsToolbox.Wpf; + +namespace ICSharpCode.ILSpy +{ + public class LanguageService : ObservableObjectBase + { + public static readonly LanguageService Instance = new(App.ExportProvider.GetExportedValues(), App.ExportProvider.GetExportedValues(), SettingsService.Instance); + + private readonly LanguageSettings languageSettings; + private readonly SettingsService settingsService; + + public LanguageService(IEnumerable languages, IEnumerable resourceFileHandlers, SettingsService settingsService) + { + this.settingsService = settingsService; + languageSettings = settingsService.SessionSettings.LanguageSettings; + + var sortedLanguages = languages.ToList(); + + sortedLanguages.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); +#if DEBUG + sortedLanguages.AddRange(ILAstLanguage.GetDebugLanguages()); + sortedLanguages.AddRange(CSharpLanguage.GetDebugLanguages(resourceFileHandlers.ToArray())); +#endif + AllLanguages = sortedLanguages.AsReadOnly(); + + this.language = GetLanguage(languageSettings.LanguageId); + this.languageVersion = Language.LanguageVersions.FirstOrDefault(v => v.Version == languageSettings.LanguageVersionId) ?? Language.LanguageVersions.LastOrDefault(); + } + + /// + /// A list of all languages. + /// + public ReadOnlyCollection AllLanguages { get; } + + /// + /// Gets a language using its name. + /// If the language is not found, C# is returned instead. + /// + public Language GetLanguage(string? name) + { + return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); + } + + ILLanguage? ilLanguage; + + public ILLanguage ILLanguage => ilLanguage ??= (ILLanguage)GetLanguage("IL"); + + /// + /// This dictionary is necessary to remember language versions across language changes. For example, + /// the user first select C# 10, then switches to IL, then switches back to C#. After that we must be + /// able to restore the original selection (i.e., C# 10). + /// + private readonly Dictionary languageVersionHistory = new(); + + Language language; + + /// + /// Gets/Sets the current language. + /// + /// + /// While this isn't related to filtering, having it as part of the FilterSettings + /// makes it easy to pass it down into all tree nodes. + /// + public Language Language { + get => language; + set { + if (language == value) + return; + + if (language is { HasLanguageVersions: true }) + { + languageVersionHistory[language] = languageVersion; + } + + language = value; + OnPropertyChanged(); + + languageSettings.LanguageId = language.Name; + + if (language.HasLanguageVersions) + { + LanguageVersion = languageVersionHistory.TryGetValue(value, out var version) ? version : Language.LanguageVersions.Last(); + } + else + { + LanguageVersion = default; + } + } + } + + LanguageVersion? languageVersion; + + /// + /// Gets/Sets the current language version. + /// + /// + /// While this isn't related to filtering, having it as part of the FilterSettings + /// makes it easy to pass it down into all tree nodes. + /// + public LanguageVersion? LanguageVersion { + get { return languageVersion; } + set { + if (languageVersion == value) + return; + + languageVersion = value; + OnPropertyChanged(); + + if (Language.HasLanguageVersions) + { + languageVersionHistory[Language] = languageVersion; + } + + languageSettings.LanguageVersionId = languageVersion?.Version; + } + } + + public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) + { + return new(LanguageVersion, settingsService.DecompilerSettings, settingsService.DisplaySettings) { Progress = tabPage.Content as IProgress }; + } + } +} diff --git a/ILSpy/Languages/Languages.cs b/ILSpy/Languages/Languages.cs deleted file mode 100644 index 980f90accc..0000000000 --- a/ILSpy/Languages/Languages.cs +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) 2011 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. - -#nullable enable - -using System; -using System.Collections.ObjectModel; -using System.Linq; - -using TomsToolbox.Composition; - -namespace ICSharpCode.ILSpy -{ - public static class Languages - { - /// - /// A list of all languages. - /// - public static ReadOnlyCollection AllLanguages { get; } = Initialize(App.ExportProvider); - - static ReadOnlyCollection Initialize(IExportProvider exportProvider) - { - var languages = exportProvider.GetExportedValues().ToList(); - - languages.Sort((a, b) => string.Compare(a.Name, b.Name, StringComparison.Ordinal)); -#if DEBUG - languages.AddRange(ILAstLanguage.GetDebugLanguages()); - languages.AddRange(CSharpLanguage.GetDebugLanguages()); -#endif - return languages.AsReadOnly(); - } - - /// - /// Gets a language using its name. - /// If the language is not found, C# is returned instead. - /// - public static Language GetLanguage(string name) - { - return AllLanguages.FirstOrDefault(l => l.Name == name) ?? AllLanguages.First(); - } - - static ILLanguage? ilLanguage; - - public static ILLanguage ILLanguage { - get { - if (ilLanguage == null) - { - ilLanguage = (ILLanguage)GetLanguage("IL"); - } - return ilLanguage; - } - } - } -} diff --git a/ILSpy/MainWindow.xaml b/ILSpy/MainWindow.xaml index 0db375e353..bbefc63fc1 100644 --- a/ILSpy/MainWindow.xaml +++ b/ILSpy/MainWindow.xaml @@ -146,12 +146,12 @@ - + + SelectedItem="{Binding LanguageService.Language}" /> @@ -160,7 +160,7 @@ 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}" /> + SelectedItem="{Binding LanguageService.LanguageVersion, UpdateSourceTrigger=PropertyChanged}" /> diff --git a/ILSpy/MainWindowViewModel.cs b/ILSpy/MainWindowViewModel.cs index 270f007ec8..8611f3189c 100644 --- a/ILSpy/MainWindowViewModel.cs +++ b/ILSpy/MainWindowViewModel.cs @@ -27,6 +27,7 @@ class MainWindowViewModel : ObservableObject { public DockWorkspace Workspace => DockWorkspace.Instance; public SessionSettings SessionSettings => SettingsService.Instance.SessionSettings; + public LanguageService LanguageService => LanguageService.Instance; public AssemblyListManager AssemblyListManager => SettingsService.Instance.AssemblyListManager; } } diff --git a/ILSpy/SolutionWriter.cs b/ILSpy/SolutionWriter.cs index dd2361ddcc..ca36873fb9 100644 --- a/ILSpy/SolutionWriter.cs +++ b/ILSpy/SolutionWriter.cs @@ -213,7 +213,7 @@ void WriteProject(LoadedAssembly loadedAssembly, Language language, string targe using (var projectFileWriter = new StreamWriter(projectFileName)) { var projectFileOutput = new PlainTextOutput(projectFileWriter); - var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; options.CancellationToken = ct; options.SaveAsProjectDirectory = targetDirectory; diff --git a/ILSpy/TextView/DecompilerTextView.cs b/ILSpy/TextView/DecompilerTextView.cs index 77a2ee89ad..dc54ddc08e 100644 --- a/ILSpy/TextView/DecompilerTextView.cs +++ b/ILSpy/TextView/DecompilerTextView.cs @@ -403,7 +403,7 @@ void ToolTipClosed(object? sender, EventArgs e) if (segment.Reference is ICSharpCode.Decompiler.Disassembler.OpCodeInfo code) { XmlDocumentationProvider docProvider = XmlDocLoader.MscorlibDocumentation; - DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), SettingsService.Instance.SessionSettings.LanguageSettings.Language.SyntaxHighlighting); + DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), LanguageService.Instance.Language.SyntaxHighlighting); renderer.AddSignatureBlock($"{code.Name} (0x{code.Code:x})"); if (docProvider != null) { @@ -453,7 +453,7 @@ void ToolTipClosed(object? sender, EventArgs e) static FlowDocument? CreateTooltipForEntity(IEntity resolved) { - Language currentLanguage = SettingsService.Instance.SessionSettings.LanguageSettings.Language; + Language currentLanguage = LanguageService.Instance.Language; DocumentationUIBuilder renderer = new DocumentationUIBuilder(new CSharpAmbience(), currentLanguage.SyntaxHighlighting); RichText richText = currentLanguage.GetRichTextTooltip(resolved); if (richText == null) @@ -544,7 +544,7 @@ void HighlightBrackets(object? sender, EventArgs e) { if (SettingsService.Instance.DisplaySettings.HighlightMatchingBraces) { - var result = SettingsService.Instance.SessionSettings.LanguageSettings.Language.BracketSearcher.SearchBracket(textEditor.Document, textEditor.CaretOffset); + var result = LanguageService.Instance.Language.BracketSearcher.SearchBracket(textEditor.Document, textEditor.CaretOffset); bracketHighlightRenderer.SetHighlight(result); } else diff --git a/ILSpy/TreeNodes/AssemblyTreeNode.cs b/ILSpy/TreeNodes/AssemblyTreeNode.cs index 335871fff4..b822a9ced7 100644 --- a/ILSpy/TreeNodes/AssemblyTreeNode.cs +++ b/ILSpy/TreeNodes/AssemblyTreeNode.cs @@ -547,7 +547,7 @@ public override bool Save(TabPageModel tabPage) dlg.Filter = language.Name + " project|*" + language.ProjectFileExtension + "|" + language.Name + " single file|*" + language.FileExtension + "|All files|*.*"; if (dlg.ShowDialog() == true) { - var options = SettingsService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); + var options = LanguageService.Instance.CreateDecompilationOptions(DockWorkspace.Instance.ActiveTabPage); options.FullDecompilation = true; if (dlg.FilterIndex == 1) { diff --git a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs index caea0a5216..bf1366fc11 100644 --- a/ILSpy/TreeNodes/DerivedTypesEntryNode.cs +++ b/ILSpy/TreeNodes/DerivedTypesEntryNode.cs @@ -17,7 +17,6 @@ // DEALINGS IN THE SOFTWARE. using System.Collections.Generic; -using System.Linq; using System.Threading; using ICSharpCode.Decompiler; @@ -56,7 +55,7 @@ public override FilterResult Filter(LanguageSettings settings) return FilterResult.Hidden; if (settings.SearchTermMatches(type.Name)) { - if (type.DeclaringType != null && (settings.ShowApiLevel != ApiVisibility.All || !settings.Language.ShowMember(type))) + if (type.DeclaringType != null && (settings.ShowApiLevel != ApiVisibility.All || !LanguageService.Instance.Language.ShowMember(type))) return FilterResult.Hidden; else return FilterResult.Match; diff --git a/ILSpy/TreeNodes/EventTreeNode.cs b/ILSpy/TreeNodes/EventTreeNode.cs index 30cd704f8f..3770a24245 100644 --- a/ILSpy/TreeNodes/EventTreeNode.cs +++ b/ILSpy/TreeNodes/EventTreeNode.cs @@ -72,7 +72,7 @@ public override FilterResult Filter(LanguageSettings settings) { if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) return FilterResult.Hidden; - if (settings.SearchTermMatches(EventDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || settings.Language.ShowMember(EventDefinition))) + if (settings.SearchTermMatches(EventDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Instance.Language.ShowMember(EventDefinition))) return FilterResult.Match; else return FilterResult.Hidden; diff --git a/ILSpy/TreeNodes/FieldTreeNode.cs b/ILSpy/TreeNodes/FieldTreeNode.cs index 4509f2ca68..913418d7ec 100644 --- a/ILSpy/TreeNodes/FieldTreeNode.cs +++ b/ILSpy/TreeNodes/FieldTreeNode.cs @@ -73,7 +73,7 @@ public override FilterResult Filter(LanguageSettings settings) { if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) return FilterResult.Hidden; - if (settings.SearchTermMatches(FieldDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || settings.Language.ShowMember(FieldDefinition))) + if (settings.SearchTermMatches(FieldDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Instance.Language.ShowMember(FieldDefinition))) return FilterResult.Match; else return FilterResult.Hidden; diff --git a/ILSpy/TreeNodes/ILSpyTreeNode.cs b/ILSpy/TreeNodes/ILSpyTreeNode.cs index 99e63e36ec..a65b883007 100644 --- a/ILSpy/TreeNodes/ILSpyTreeNode.cs +++ b/ILSpy/TreeNodes/ILSpyTreeNode.cs @@ -47,7 +47,7 @@ protected ILSpyTreeNode() LanguageSettings LanguageSettings => SettingsService.Instance.SessionSettings.LanguageSettings; - public Language Language => LanguageSettings.Language; + public Language Language => LanguageService.Instance.Language; public virtual FilterResult Filter(LanguageSettings settings) { @@ -131,7 +131,7 @@ protected virtual void Settings_Changed(object sender, PropertyChangedEventArgs { if (sender is not ILSpy.LanguageSettings) return; - if (e.PropertyName is not (nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion))) + if (e.PropertyName is not (nameof(LanguageSettings.LanguageId) or nameof(LanguageSettings.LanguageVersionId))) return; RaisePropertyChanged(nameof(Text)); diff --git a/ILSpy/TreeNodes/MethodTreeNode.cs b/ILSpy/TreeNodes/MethodTreeNode.cs index 0900551230..e6bef5f10d 100644 --- a/ILSpy/TreeNodes/MethodTreeNode.cs +++ b/ILSpy/TreeNodes/MethodTreeNode.cs @@ -103,7 +103,7 @@ public override FilterResult Filter(LanguageSettings settings) { if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) return FilterResult.Hidden; - if (settings.SearchTermMatches(MethodDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || settings.Language.ShowMember(MethodDefinition))) + if (settings.SearchTermMatches(MethodDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Instance.Language.ShowMember(MethodDefinition))) return FilterResult.Match; else return FilterResult.Hidden; @@ -127,7 +127,7 @@ public override bool IsPublicAPI { public override string ToString() { - return Languages.ILLanguage.MethodToString(MethodDefinition, false, false, false); + return LanguageService.Instance.ILLanguage.MethodToString(MethodDefinition, false, false, false); } } } diff --git a/ILSpy/TreeNodes/PropertyTreeNode.cs b/ILSpy/TreeNodes/PropertyTreeNode.cs index 8374d094f7..e9539c887f 100644 --- a/ILSpy/TreeNodes/PropertyTreeNode.cs +++ b/ILSpy/TreeNodes/PropertyTreeNode.cs @@ -75,7 +75,7 @@ public override FilterResult Filter(LanguageSettings settings) { if (settings.ShowApiLevel == ApiVisibility.PublicOnly && !IsPublicAPI) return FilterResult.Hidden; - if (settings.SearchTermMatches(PropertyDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || settings.Language.ShowMember(PropertyDefinition))) + if (settings.SearchTermMatches(PropertyDefinition.Name) && (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Instance.Language.ShowMember(PropertyDefinition))) return FilterResult.Match; else return FilterResult.Hidden; @@ -104,7 +104,7 @@ public override bool IsPublicAPI { public override string ToString() { - return Languages.ILLanguage.PropertyToString(PropertyDefinition, false, false, false); + return LanguageService.Instance.ILLanguage.PropertyToString(PropertyDefinition, false, false, false); } } } diff --git a/ILSpy/TreeNodes/TypeTreeNode.cs b/ILSpy/TreeNodes/TypeTreeNode.cs index 91f7e11c37..620fe9bf24 100644 --- a/ILSpy/TreeNodes/TypeTreeNode.cs +++ b/ILSpy/TreeNodes/TypeTreeNode.cs @@ -73,7 +73,7 @@ public override FilterResult Filter(LanguageSettings settings) return FilterResult.Hidden; if (settings.SearchTermMatches(TypeDefinition.Name)) { - if (settings.ShowApiLevel == ApiVisibility.All || settings.Language.ShowMember(TypeDefinition)) + if (settings.ShowApiLevel == ApiVisibility.All || LanguageService.Instance.Language.ShowMember(TypeDefinition)) return FilterResult.Match; else return FilterResult.Hidden; diff --git a/ILSpy/Util/SettingsService.cs b/ILSpy/Util/SettingsService.cs index bd2923e2e5..46a132e75b 100644 --- a/ILSpy/Util/SettingsService.cs +++ b/ILSpy/Util/SettingsService.cs @@ -132,11 +132,6 @@ private SettingsService() : base(LoadSettings()) UseDebugSymbols = DecompilerSettings.UseDebugSymbols }; - public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) - { - return new(SessionSettings.LanguageSettings.LanguageVersion, DecompilerSettings, DisplaySettings) { Progress = tabPage.Content as IProgress }; - } - public AssemblyList LoadInitialAssemblyList() { var loadPreviousAssemblies = MiscSettings.LoadPreviousAssemblies; diff --git a/ILSpy/Views/DebugSteps.xaml.cs b/ILSpy/Views/DebugSteps.xaml.cs index b2745089e0..edd16a6d6d 100644 --- a/ILSpy/Views/DebugSteps.xaml.cs +++ b/ILSpy/Views/DebugSteps.xaml.cs @@ -38,7 +38,7 @@ public DebugSteps() writingOptions.PropertyChanged += WritingOptions_PropertyChanged; - if (SettingsService.Instance.SessionSettings.LanguageSettings.Language is ILAstLanguage l) + if (LanguageService.Instance.Language is ILAstLanguage l) { l.StepperUpdated += ILAstStepperUpdated; language = l; @@ -66,13 +66,13 @@ private void Settings_PropertyChanged(object sender, System.ComponentModel.Prope if (sender is not LanguageSettings) return; - if (e.PropertyName == nameof(LanguageSettings.Language)) + if (e.PropertyName == nameof(LanguageSettings.LanguageId)) { if (language != null) { language.StepperUpdated -= ILAstStepperUpdated; } - if (SettingsService.Instance.SessionSettings.LanguageSettings.Language is ILAstLanguage l) + if (LanguageService.Instance.Language is ILAstLanguage l) { l.StepperUpdated += ILAstStepperUpdated; language = l;