Browse Source

Consolidate all options/settings to use a consistent WFP MVVM pattern.

pull/3274/head
tom-englert 10 months ago committed by tom-englert
parent
commit
2454cd3a99
  1. 4
      ICSharpCode.ILSpyX/AssemblyListManager.cs
  2. 14
      ICSharpCode.ILSpyX/Settings/ILSpySettings.cs
  3. 42
      ICSharpCode.ILSpyX/Settings/IMiscSettings.cs
  4. 2
      ICSharpCode.ILSpyX/Settings/ISettingsFilePathProvider.cs
  5. 36
      ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs
  6. 32
      ICSharpCode.ILSpyX/Settings/ISettingsSection.cs
  7. 14
      ILSpy.ReadyToRun/ReadyToRunDisassembler.cs
  8. 16
      ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml
  9. 103
      ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs
  10. 104
      ILSpy.ReadyToRun/ReadyToRunOptions.cs
  11. 20
      ILSpy/App.xaml.cs
  12. 34
      ILSpy/AssemblyTree/AssemblyListPaneModel.cs
  13. 1
      ILSpy/Commands/CheckForUpdatesCommand.cs
  14. 9
      ILSpy/Commands/SetThemeCommand.cs
  15. 4
      ILSpy/DecompilationOptions.cs
  16. 18
      ILSpy/LanguageSettings.cs
  17. 4
      ILSpy/MainWindow.xaml
  18. 12
      ILSpy/MainWindow.xaml.cs
  19. 48
      ILSpy/Options/DecompilerSettings.cs
  20. 56
      ILSpy/Options/DecompilerSettingsViewModel.cs
  21. 183
      ILSpy/Options/DisplaySettings.cs
  22. 3
      ILSpy/Options/DisplaySettingsPanel.xaml
  23. 20
      ILSpy/Options/DisplaySettingsViewModel.cs
  24. 35
      ILSpy/Options/MiscSettings.cs
  25. 11
      ILSpy/Options/MiscSettingsPanel.xaml
  26. 31
      ILSpy/Options/MiscSettingsPanel.xaml.cs
  27. 47
      ILSpy/Options/MiscSettingsViewModel.cs
  28. 5
      ILSpy/Options/OptionsDialog.xaml.cs
  29. 13
      ILSpy/Options/OptionsDialogViewModel.cs
  30. 8
      ILSpy/Search/SearchPane.xaml.cs
  31. 120
      ILSpy/SessionSettings.cs
  32. 10
      ILSpy/Themes/ThemeManager.cs
  33. 11
      ILSpy/TreeNodes/ILSpyTreeNode.cs
  34. 4
      ILSpy/Updates/NotifyOfUpdatesStrategy.cs
  35. 2
      ILSpy/Updates/UpdateSettings.cs
  36. 4
      ILSpy/Util/MessageBus.cs
  37. 157
      ILSpy/Util/SettingsService.cs
  38. 9
      ILSpy/Views/DebugSteps.xaml.cs
  39. 11
      TestPlugin/CustomOptionPage.xaml
  40. 90
      TestPlugin/CustomOptionPage.xaml.cs

4
ICSharpCode.ILSpyX/AssemblyListManager.cs

@ -59,9 +59,9 @@ namespace ICSharpCode.ILSpyX
public bool UseDebugSymbols { get; set; } public bool UseDebugSymbols { get; set; }
public ObservableCollection<string> AssemblyLists { get; } = new ObservableCollection<string>(); public ObservableCollection<string> AssemblyLists { get; } = [];
public FileLoaderRegistry LoaderRegistry { get; } = new FileLoaderRegistry(); public FileLoaderRegistry LoaderRegistry { get; } = new();
/// <summary> /// <summary>
/// Loads an assembly list from the ILSpySettings. /// Loads an assembly list from the ILSpySettings.

14
ICSharpCode.ILSpyX/Settings/ILSpySettings.cs

@ -123,11 +123,6 @@ namespace ICSharpCode.ILSpyX.Settings
} }
} }
void ISettingsProvider.Update(Action<XElement> action)
{
Update(action);
}
static string GetConfigFile() static string GetConfigFile()
{ {
if (null != SettingsFilePathProvider) if (null != SettingsFilePathProvider)
@ -148,10 +143,10 @@ namespace ICSharpCode.ILSpyX.Settings
public MutexProtector(string name) public MutexProtector(string name)
{ {
bool createdNew; this.mutex = new Mutex(true, name, out bool createdNew);
this.mutex = new Mutex(true, name, out createdNew); if (createdNew)
if (!createdNew) return;
{
try try
{ {
mutex.WaitOne(); mutex.WaitOne();
@ -160,7 +155,6 @@ namespace ICSharpCode.ILSpyX.Settings
{ {
} }
} }
}
public void Dispose() public void Dispose()
{ {

42
ICSharpCode.ILSpyX/Settings/IMiscSettings.cs

@ -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);
}
}
}

2
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 // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.ILSpyX.Settings namespace ICSharpCode.ILSpyX.Settings
{ {
public interface ISettingsFilePathProvider public interface ISettingsFilePathProvider

36
ICSharpCode.ILSpyX/Settings/ISettingsProvider.cs

@ -17,9 +17,6 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Xml.Linq; using System.Xml.Linq;
namespace ICSharpCode.ILSpyX.Settings namespace ICSharpCode.ILSpyX.Settings
@ -30,37 +27,6 @@ namespace ICSharpCode.ILSpyX.Settings
void Update(Action<XElement> action); void Update(Action<XElement> action);
public static Decompiler.DecompilerSettings LoadDecompilerSettings(ISettingsProvider settingsProvider) void SaveSettings(XElement section);
{
XElement e = settingsProvider["DecompilerSettings"];
var newSettings = new Decompiler.DecompilerSettings();
var properties = typeof(Decompiler.DecompilerSettings).GetProperties()
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.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, Decompiler.DecompilerSettings newSettings)
{
var properties = typeof(Decompiler.DecompilerSettings).GetProperties()
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.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);
}
} }
} }

32
ICSharpCode.ILSpyX/Settings/ISettingsSection.cs

@ -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<TSelf>
{
// This should be abstract, but that needs C# 11.0 (see IParseable<TSelf>)
// Keep it to be enabled in the future
public static TSelf Load(ISettingsProvider settingsProvider)
{
throw new NotImplementedException();
}
}
}

14
ILSpy.ReadyToRun/ReadyToRunDisassembler.cs

@ -51,11 +51,13 @@ namespace ICSharpCode.ILSpy.ReadyToRun
ReadyToRunMethod readyToRunMethod = runtimeFunction.Method; ReadyToRunMethod readyToRunMethod = runtimeFunction.Method;
WriteCommentLine(readyToRunMethod.SignatureString); WriteCommentLine(readyToRunMethod.SignatureString);
if (ReadyToRunOptions.GetIsShowGCInfo(null)) var options = SettingsService.Instance.GetSettings<ReadyToRunOptions>();
if (options.IsShowGCInfo)
{ {
if (readyToRunMethod.GcInfo != null) if (readyToRunMethod.GcInfo != null)
{ {
string[] lines = readyToRunMethod.GcInfo.ToString().Split(Environment.NewLine); string[] lines = readyToRunMethod.GcInfo.ToString()?.Split(Environment.NewLine) ?? [];
WriteCommentLine("GC info:"); WriteCommentLine("GC info:");
foreach (string line in lines) foreach (string line in lines)
{ {
@ -69,12 +71,12 @@ namespace ICSharpCode.ILSpy.ReadyToRun
} }
Dictionary<ulong, UnwindCode> unwindInfo = null; Dictionary<ulong, UnwindCode> unwindInfo = null;
if (ReadyToRunOptions.GetIsShowUnwindInfo(null) && bitness == 64) if (options.IsShowUnwindInfo && bitness == 64)
{ {
unwindInfo = WriteUnwindInfo(); unwindInfo = WriteUnwindInfo();
} }
bool isShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(null); bool isShowDebugInfo = options.IsShowDebugInfo;
DebugInfoHelper debugInfo = null; DebugInfoHelper debugInfo = null;
if (isShowDebugInfo) if (isShowDebugInfo)
{ {
@ -98,7 +100,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun
decoder.Decode(out instructions.AllocUninitializedElement()); decoder.Decode(out instructions.AllocUninitializedElement());
} }
string disassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(null); string disassemblyFormat = options.DisassemblyFormat;
Formatter formatter = null; Formatter formatter = null;
if (disassemblyFormat.Equals(ReadyToRunOptions.intel)) if (disassemblyFormat.Equals(ReadyToRunOptions.intel))
{ {
@ -145,7 +147,7 @@ namespace ICSharpCode.ILSpy.ReadyToRun
} }
} }
} }
if (ReadyToRunOptions.GetIsShowGCInfo(null)) if (options.IsShowGCInfo)
{ {
DecorateGCInfo(instr, baseInstrIP, readyToRunMethod.GcInfo); DecorateGCInfo(instr, baseInstrIP, readyToRunMethod.GcInfo);
} }

16
ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml

@ -1,7 +1,11 @@
<UserControl x:Class="ICSharpCode.ILSpy.ReadyToRun.ReadyToRunOptionPage" <UserControl x:Class="ICSharpCode.ILSpy.ReadyToRun.ReadyToRunOptionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:properties="clr-namespace:ILSpy.ReadyToRun.Properties" xmlns:properties="clr-namespace:ILSpy.ReadyToRun.Properties"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> 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}">
<Grid> <Grid>
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition /> <ColumnDefinition />
@ -14,12 +18,12 @@
<RowDefinition Height="Auto" /> <RowDefinition Height="Auto" />
</Grid.RowDefinitions> </Grid.RowDefinitions>
<TextBlock Margin="3" Text="{x:Static properties:Resources.DisassemblyFormat}" /> <TextBlock Margin="3" Text="{x:Static properties:Resources.DisassemblyFormat}" />
<ComboBox Grid.Column="1" Margin="3" ItemsSource="{Binding DisassemblyFormats}" SelectedItem="{Binding DisassemblyFormat}" /> <ComboBox Grid.Row="0" Grid.Column="1" Margin="3" ItemsSource="{Binding Options.DisassemblyFormats}" SelectedItem="{Binding Options.DisassemblyFormat}" />
<TextBlock Grid.Row="1" Margin="3" Text="{x:Static properties:Resources.ShowStackUnwindInfo}"/> <TextBlock Grid.Row="1" Grid.Column="0" Margin="3" Text="{x:Static properties:Resources.ShowStackUnwindInfo}"/>
<CheckBox Grid.Row="1" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowUnwindInfo}" /> <CheckBox Grid.Row="1" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowUnwindInfo}" />
<TextBlock Grid.Row="2" Margin="3" Text="{x:Static properties:Resources.ShowDebugInfo}"/> <TextBlock Grid.Row="2" Margin="3" Text="{x:Static properties:Resources.ShowDebugInfo}"/>
<CheckBox Grid.Row="2" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowDebugInfo}" /> <CheckBox Grid.Row="2" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowDebugInfo}" />
<TextBlock Grid.Row="3" Margin="3" Text="{x:Static properties:Resources.ShowGCInfo}"/> <TextBlock Grid.Row="3" Margin="3" Text="{x:Static properties:Resources.ShowGCInfo}"/>
<CheckBox Grid.Row="3" Grid.Column="1" Margin="3" IsChecked="{Binding IsShowGCInfo}" /> <CheckBox Grid.Row="3" Grid.Column="1" Margin="3" IsChecked="{Binding Options.IsShowGCInfo}" />
</Grid> </Grid>
</UserControl> </UserControl>

103
ILSpy.ReadyToRun/ReadyToRunOptionPage.xaml.cs

@ -16,114 +16,47 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.ComponentModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Windows.Controls;
using System.Xml.Linq;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpy.Util;
using TomsToolbox.Wpf;
using TomsToolbox.Wpf.Composition.Mef;
namespace ICSharpCode.ILSpy.ReadyToRun namespace ICSharpCode.ILSpy.ReadyToRun
{ {
[ExportOptionPage(Order = 40)] [DataTemplate(typeof(ReadyToRunOptionsViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)] [PartCreationPolicy(CreationPolicy.NonShared)]
partial class ReadyToRunOptionPage : UserControl, IOptionPage partial class ReadyToRunOptionPage
{ {
public ReadyToRunOptionPage() public ReadyToRunOptionPage()
{ {
InitializeComponent(); InitializeComponent();
} }
public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun;
public void Load(ILSpySettings spySettings)
{
Options s = new Options();
s.DisassemblyFormat = ReadyToRunOptions.GetDisassemblyFormat(spySettings);
s.IsShowUnwindInfo = ReadyToRunOptions.GetIsShowUnwindInfo(spySettings);
s.IsShowDebugInfo = ReadyToRunOptions.GetIsShowDebugInfo(spySettings);
s.IsShowGCInfo = ReadyToRunOptions.GetIsShowGCInfo(spySettings);
this.DataContext = s;
} }
public void LoadDefaults() [ExportOptionPage(Order = 40)]
{ [PartCreationPolicy(CreationPolicy.NonShared)]
this.DataContext = new Options(); class ReadyToRunOptionsViewModel : ObservableObject, IOptionPage
}
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
{ {
public string[] DisassemblyFormats { private ReadyToRunOptions options;
get {
return ReadyToRunOptions.disassemblyFormats;
}
}
private bool isShowUnwindInfo; public ReadyToRunOptions Options {
public bool IsShowUnwindInfo { get => options;
get { set => SetProperty(ref options, value);
return isShowUnwindInfo;
} }
set {
isShowUnwindInfo = value;
OnPropertyChanged(nameof(IsShowUnwindInfo));
}
}
private bool isShowDebugInfo;
public bool IsShowDebugInfo {
get {
return isShowDebugInfo;
}
set {
isShowDebugInfo = value;
OnPropertyChanged(nameof(IsShowDebugInfo));
}
}
private bool isShowGCInfo;
public bool IsShowGCInfo { public string Title => global::ILSpy.ReadyToRun.Properties.Resources.ReadyToRun;
get {
return isShowGCInfo;
}
set {
isShowGCInfo = value;
OnPropertyChanged(nameof(IsShowGCInfo));
}
}
private string disassemblyFormat;
public string DisassemblyFormat { public void Load(SettingsSnapshot snapshot)
get { return disassemblyFormat; }
set {
if (disassemblyFormat != value)
{ {
disassemblyFormat = value; Options = snapshot.GetSettings<ReadyToRunOptions>();
OnPropertyChanged(nameof(DisassemblyFormat));
}
}
} }
public event PropertyChangedEventHandler PropertyChanged; public void LoadDefaults()
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{ {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); Options.LoadFromSection(new("empty"));
}
} }
} }
} }

104
ILSpy.ReadyToRun/ReadyToRunOptions.cs

@ -19,102 +19,72 @@
using System.Xml.Linq; using System.Xml.Linq;
using ICSharpCode.ILSpy.Util; using ICSharpCode.ILSpy.Util;
using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy.ReadyToRun 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"; private static readonly XNamespace ns = "http://www.ilspy.net/ready-to-run";
internal static string intel = "Intel"; internal static string intel = "Intel";
internal static string gas = "AT & T"; 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)
{
settings ??= SettingsService.Instance.SpySettings;
XElement e = settings[ns + "ReadyToRunOptions"]; public string[] DisassemblyFormats {
XAttribute a = e.Attribute("DisassemblyFormat"); get {
if (a == null) return disassemblyFormats;
{
return ReadyToRunOptions.intel;
}
else
{
return (string)a;
} }
} }
public static bool GetIsShowUnwindInfo(ILSpySettings settings) private bool isShowUnwindInfo;
{ public bool IsShowUnwindInfo {
settings ??= SettingsService.Instance.SpySettings; get => isShowUnwindInfo;
set => SetProperty(ref isShowUnwindInfo, value);
XElement e = settings[ns + "ReadyToRunOptions"];
XAttribute a = e.Attribute("IsShowUnwindInfo");
if (a == null)
{
return false;
} }
else
{ private bool isShowDebugInfo;
return (bool)a; public bool IsShowDebugInfo {
get => isShowDebugInfo;
set => SetProperty(ref isShowDebugInfo, value);
} }
private bool isShowGCInfo;
public bool IsShowGCInfo {
get => isShowGCInfo;
set => SetProperty(ref isShowGCInfo, value);
} }
public static bool GetIsShowDebugInfo(ILSpySettings settings) private string disassemblyFormat;
{ public string DisassemblyFormat {
settings ??= SettingsService.Instance.SpySettings; get => disassemblyFormat;
set => SetProperty(ref disassemblyFormat, value);
}
XElement e = settings[ns + "ReadyToRunOptions"]; public XName SectionName { get; } = ns + "ReadyToRunOptions";
XAttribute a = e.Attribute("IsShowDebugInfo");
if (a == null) public void LoadFromSection(XElement e)
{
return true;
}
else
{ {
return (bool)a; XAttribute format = e.Attribute("DisassemblyFormat");
} DisassemblyFormat = format == null ? intel : (string)format;
}
public static bool GetIsShowGCInfo(ILSpySettings settings) XAttribute unwind = e.Attribute("IsShowUnwindInfo");
{ IsShowUnwindInfo = unwind != null && (bool)unwind;
settings ??= SettingsService.Instance.SpySettings;
XElement e = settings[ns + "ReadyToRunOptions"]; XAttribute debug = e.Attribute("IsShowDebugInfo");
XAttribute a = e.Attribute("IsShowGCInfo"); IsShowDebugInfo = debug == null || (bool)debug;
if (a == null) XAttribute showGc = e.Attribute("IsShowGCInfo");
{ IsShowGCInfo = showGc != null && (bool)showGc;
return false;
}
else
{
return (bool)a;
}
} }
public static void SetDisassemblyOptions(XElement root, string disassemblyFormat, bool isShowUnwindInfo, bool isShowDebugInfo, bool isShowGCInfo) public void SaveToSection(XElement section)
{ {
XElement section = new XElement(ns + "ReadyToRunOptions");
section.SetAttributeValue("DisassemblyFormat", disassemblyFormat); section.SetAttributeValue("DisassemblyFormat", disassemblyFormat);
section.SetAttributeValue("IsShowUnwindInfo", isShowUnwindInfo); section.SetAttributeValue("IsShowUnwindInfo", isShowUnwindInfo);
section.SetAttributeValue("IsShowDebugInfo", isShowDebugInfo); section.SetAttributeValue("IsShowDebugInfo", isShowDebugInfo);
section.SetAttributeValue("IsShowGCInfo", isShowGCInfo); section.SetAttributeValue("IsShowGCInfo", isShowGCInfo);
XElement existingElement = root.Element(ns + "ReadyToRunOptions");
if (existingElement != null)
{
existingElement.ReplaceWith(section);
}
else
{
root.Add(section);
}
} }
} }
} }

20
ILSpy/App.xaml.cs

@ -32,9 +32,7 @@ using System.Windows.Threading;
using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpy.AssemblyTree; using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX.Analyzers; using ICSharpCode.ILSpyX.Analyzers;
using ICSharpCode.ILSpyX.Settings;
using Medo.Application; using Medo.Application;
@ -66,11 +64,17 @@ namespace ICSharpCode.ILSpy
public App() public App()
{ {
ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider();
var cmdArgs = Environment.GetCommandLineArgs().Skip(1); var cmdArgs = Environment.GetCommandLineArgs().Skip(1);
CommandLineArguments = CommandLineArguments.Create(cmdArgs); CommandLineArguments = CommandLineArguments.Create(cmdArgs);
bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true)
&& !SettingsService.Instance.MiscSettings.AllowMultipleInstances;
if (forceSingleInstance)
{
SingleInstance.Attach(); // will auto-exit for second instance
SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected;
}
SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider()); SharpTreeNode.SetImagesProvider(new WpfWindowsTreeNodeImagesProvider());
InitializeComponent(); InitializeComponent();
@ -85,14 +89,6 @@ namespace ICSharpCode.ILSpy
TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException; TaskScheduler.UnobservedTaskException += DotNet40_UnobservedTaskException;
InitializeMef().GetAwaiter().GetResult(); InitializeMef().GetAwaiter().GetResult();
bool forceSingleInstance = (CommandLineArguments.SingleInstance ?? true)
&& !SettingsService.Instance.MiscSettings.AllowMultipleInstances;
if (forceSingleInstance)
{
SingleInstance.Attach(); // will auto-exit for second instance
SingleInstance.NewInstanceDetected += SingleInstance_NewInstanceDetected;
}
// Register the export provider so that it can be accessed from WPF/XAML components. // Register the export provider so that it can be accessed from WPF/XAML components.
ExportProviderLocator.Register(ExportProvider); ExportProviderLocator.Register(ExportProvider);
// Add data templates registered via MEF. // Add data templates registered via MEF.

34
ILSpy/AssemblyTree/AssemblyListPaneModel.cs

@ -77,17 +77,16 @@ namespace ICSharpCode.ILSpy.AssemblyTree
ShortcutKey = new KeyGesture(Key.F6); ShortcutKey = new KeyGesture(Key.F6);
MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference; MessageBus<NavigateToReferenceEventArgs>.Subscribers += JumpToReference;
MessageBus<SessionSettingsChangedEventArgs>.Subscribers += (sender, e) => SessionSettings_PropertyChanged(sender, e); MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e);
MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e);
var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Background, TreeView_SelectionChanged); var selectionChangeThrottle = new DispatcherThrottle(DispatcherPriority.Background, TreeView_SelectionChanged);
SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick(); SelectedItems.CollectionChanged += (_, _) => selectionChangeThrottle.Tick();
} }
private void SessionSettings_PropertyChanged(object sender, PropertyChangedEventArgs e) private void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (sender is SessionSettings sessionSettings)
{ {
var sessionSettings = SettingsService.Instance.SessionSettings;
switch (e.PropertyName) switch (e.PropertyName)
{ {
case nameof(SessionSettings.ActiveAssemblyList): case nameof(SessionSettings.ActiveAssemblyList):
@ -96,13 +95,24 @@ namespace ICSharpCode.ILSpy.AssemblyTree
case nameof(SessionSettings.Theme): case nameof(SessionSettings.Theme):
// update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change) // update syntax highlighting and force reload (AvalonEdit does not automatically refresh on highlighting change)
DecompilerTextView.RegisterHighlighting(); DecompilerTextView.RegisterHighlighting();
DecompileSelectedNodes(DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState); DecompileSelectedNodes(
DockWorkspace.Instance.ActiveTabPage.GetState() as DecompilerTextViewState);
break; break;
case nameof(SessionSettings.CurrentCulture): case nameof(SessionSettings.CurrentCulture):
MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy"); MessageBox.Show(Properties.Resources.SettingsChangeRestartRequired, "ILSpy");
break; 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; } public AssemblyList AssemblyList { get; private set; }
@ -136,7 +146,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
/// Called on startup or when passed arguments via WndProc from a second instance. /// 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. /// In the format case, spySettings is non-null; in the latter it is null.
/// </summary> /// </summary>
public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ILSpySettings spySettings = null) public void HandleCommandLineArgumentsAfterShowList(CommandLineArguments args, ISettingsProvider spySettings = null)
{ {
var sessionSettings = SettingsService.Instance.SessionSettings; var sessionSettings = SettingsService.Instance.SessionSettings;
@ -172,7 +182,7 @@ namespace ICSharpCode.ILSpy.AssemblyTree
}); });
} }
public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ILSpySettings spySettings, List<LoadedAssembly> relevantAssemblies) public async void NavigateOnLaunch(string navigateTo, string[] activeTreeViewPath, ISettingsProvider spySettings, List<LoadedAssembly> relevantAssemblies)
{ {
var initialSelection = SelectedItem; var initialSelection = SelectedItem;
if (navigateTo != null) if (navigateTo != null)
@ -458,14 +468,6 @@ namespace ICSharpCode.ILSpy.AssemblyTree
AssemblyList.OpenAssembly(asm.Location); AssemblyList.OpenAssembly(asm.Location);
} }
void LanguageSettings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion))
{
DecompileSelectedNodes(recordHistory: false);
}
}
public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm) public AssemblyTreeNode FindAssemblyNode(LoadedAssembly asm)
{ {
return assemblyListTreeNode.FindAssemblyNode(asm); return assemblyListTreeNode.FindAssemblyNode(asm);

1
ILSpy/Commands/CheckForUpdatesCommand.cs

@ -20,7 +20,6 @@
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpyX.Settings;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {

9
ILSpy/Commands/SetThemeCommand.cs

@ -6,7 +6,14 @@ namespace ICSharpCode.ILSpy.Commands
public override void Execute(object parameter) public override void Execute(object parameter)
{ {
if (parameter is string theme) if (parameter is string theme)
SettingsService.Instance.SessionSettings.Theme = theme; {
var snapshot = SettingsService.Instance.CreateSnapshot();
var sessionSettings = snapshot.GetSettings<SessionSettings>();
sessionSettings.Theme = theme;
snapshot.Save();
}
} }
} }
} }

4
ILSpy/DecompilationOptions.cs

@ -23,6 +23,8 @@ using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
/// <summary> /// <summary>
@ -87,7 +89,9 @@ namespace ICSharpCode.ILSpy
{ {
if (!Enum.TryParse(version?.Version, out Decompiler.CSharp.LanguageVersion languageVersion)) if (!Enum.TryParse(version?.Version, out Decompiler.CSharp.LanguageVersion languageVersion))
languageVersion = Decompiler.CSharp.LanguageVersion.Latest; languageVersion = Decompiler.CSharp.LanguageVersion.Latest;
var newSettings = this.DecompilerSettings = settings.Clone(); var newSettings = this.DecompilerSettings = settings.Clone();
newSettings.SetLanguageVersion(languageVersion); newSettings.SetLanguageVersion(languageVersion);
newSettings.ExpandMemberDefinitions = displaySettings.ExpandMemberDefinitions; newSettings.ExpandMemberDefinitions = displaySettings.ExpandMemberDefinitions;
newSettings.ExpandUsingDeclarations = displaySettings.ExpandUsingDeclarations; newSettings.ExpandUsingDeclarations = displaySettings.ExpandUsingDeclarations;

18
ILSpy/LanguageSettings.cs

@ -25,12 +25,14 @@ using System.Xml.Linq;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
/// <summary> /// <summary>
/// Represents the filters applied to the tree view. /// Represents the filters applied to the tree view.
/// </summary> /// </summary>
public class LanguageSettings : INotifyPropertyChanged public class LanguageSettings : ObservableObject
{ {
/// <summary> /// <summary>
/// This dictionary is necessary to remember language versions across language changes. For example, /// This dictionary is necessary to remember language versions across language changes. For example,
@ -172,20 +174,6 @@ namespace ICSharpCode.ILSpy
} }
} }
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. // 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. // 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. // 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.

4
ILSpy/MainWindow.xaml

@ -29,8 +29,6 @@
<ContentPresenter Content="{Binding Content}" /> <ContentPresenter Content="{Binding Content}" />
</DataTemplate> </DataTemplate>
<toms:BindingRelay x:Key="WindowBinding" DataContext="{Binding}" />
</Window.Resources> </Window.Resources>
<b:Interaction.Behaviors> <b:Interaction.Behaviors>
@ -93,7 +91,7 @@
<Setter.Value> <Setter.Value>
<MultiBinding Converter="{x:Static toms:BinaryOperationConverter.Equality}" Mode="OneWay"> <MultiBinding Converter="{x:Static toms:BinaryOperationConverter.Equality}" Mode="OneWay">
<Binding /> <Binding />
<Binding Source="{StaticResource WindowBinding}" Path="DataContext.SessionSettings.Theme" /> <Binding Path="DataContext.SessionSettings.Theme" RelativeSource="{RelativeSource FindAncestor, AncestorType=Window}"/>
</MultiBinding> </MultiBinding>
</Setter.Value> </Setter.Value>
</Setter> </Setter>

12
ILSpy/MainWindow.xaml.cs

@ -34,6 +34,7 @@ using ICSharpCode.ILSpy.AssemblyTree;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Search; using ICSharpCode.ILSpy.Search;
using ICSharpCode.ILSpy.TextView; using ICSharpCode.ILSpy.TextView;
using ICSharpCode.ILSpy.Themes;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpy.Updates; using ICSharpCode.ILSpy.Updates;
using ICSharpCode.ILSpyX.FileLoaders; using ICSharpCode.ILSpyX.FileLoaders;
@ -70,6 +71,7 @@ namespace ICSharpCode.ILSpy
instance = this; instance = this;
var sessionSettings = SettingsService.Instance.SessionSettings; var sessionSettings = SettingsService.Instance.SessionSettings;
ThemeManager.Current.Theme = sessionSettings.Theme;
// Make sure Images are initialized on the UI thread. // Make sure Images are initialized on the UI thread.
this.Icon = Images.ILSpyIcon; this.Icon = Images.ILSpyIcon;
@ -165,7 +167,7 @@ namespace ICSharpCode.ILSpy
#region Update Check #region Update Check
string updateAvailableDownloadUrl; string updateAvailableDownloadUrl;
public async Task ShowMessageIfUpdatesAvailableAsync(ILSpySettings spySettings, bool forceCheck = false) public async Task ShowMessageIfUpdatesAvailableAsync(ISettingsProvider spySettings, bool forceCheck = false)
{ {
string downloadUrl; string downloadUrl;
if (forceCheck) if (forceCheck)
@ -337,14 +339,18 @@ namespace ICSharpCode.ILSpy
protected override void OnClosing(CancelEventArgs e) protected override void OnClosing(CancelEventArgs e)
{ {
base.OnClosing(e); base.OnClosing(e);
var sessionSettings = SettingsService.Instance.SessionSettings;
var snapshot = SettingsService.Instance.CreateSnapshot();
var sessionSettings = snapshot.GetSettings<SessionSettings>();
sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName; sessionSettings.ActiveAssemblyList = AssemblyTreeModel.AssemblyList.ListName;
sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath; sessionSettings.ActiveTreeViewPath = AssemblyTreeModel.SelectedPath;
sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem); sessionSettings.ActiveAutoLoadedAssembly = GetAutoLoadedAssemblyNode(AssemblyTreeModel.SelectedItem);
sessionSettings.WindowBounds = this.RestoreBounds; sessionSettings.WindowBounds = this.RestoreBounds;
sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager)); sessionSettings.DockLayout.Serialize(new XmlLayoutSerializer(dockManager));
sessionSettings.Save();
snapshot.Save();
} }
private static string GetAutoLoadedAssemblyNode(SharpTreeNode node) private static string GetAutoLoadedAssemblyNode(SharpTreeNode node)

48
ILSpy/Options/DecompilerSettings.cs

@ -0,0 +1,48 @@
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<BrowsableAttribute>()?.Browsable != false)
.ToArray();
public XName SectionName => "DecompilerSettings";
public void SaveToSection(XElement section)
{
foreach (var p in properties)
{
section.SetAttributeValue(p.Name, p.GetValue(this));
}
}
public void LoadFromSection(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 = new XElement("DecompilerSettings");
SaveToSection(section);
var newSettings = new DecompilerSettings();
newSettings.LoadFromSection(section);
return newSettings;
}
}
}

56
ILSpy/Options/DecompilerSettingsViewModel.cs

@ -21,12 +21,9 @@ using System.ComponentModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Xml.Linq;
using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Properties; using ICSharpCode.ILSpy.Properties;
using ICSharpCode.ILSpy.TreeNodes; using ICSharpCode.ILSpy.TreeNodes;
using ICSharpCode.ILSpyX.Settings;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
@ -36,56 +33,46 @@ namespace ICSharpCode.ILSpy.Options
[PartCreationPolicy(CreationPolicy.NonShared)] [PartCreationPolicy(CreationPolicy.NonShared)]
public sealed class DecompilerSettingsViewModel : ObservableObjectBase, IOptionPage public sealed class DecompilerSettingsViewModel : ObservableObjectBase, IOptionPage
{ {
private DecompilerSettingsGroupViewModel[] settings; private static readonly PropertyInfo[] propertyInfos = typeof(Decompiler.DecompilerSettings).GetProperties()
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false)
.ToArray();
public string Title => Resources.Decompiler; public string Title => Resources.Decompiler;
private DecompilerSettingsGroupViewModel[] settings;
public DecompilerSettingsGroupViewModel[] Settings { public DecompilerSettingsGroupViewModel[] Settings {
get => settings; get => settings;
set => SetProperty(ref settings, value); set => SetProperty(ref settings, value);
} }
public void Load(ILSpySettings spySettings) private DecompilerSettings decompilerSettings;
public void Load(SettingsSnapshot snapshot)
{ {
Load(ISettingsProvider.LoadDecompilerSettings(spySettings)); decompilerSettings = snapshot.GetSettings<DecompilerSettings>();
LoadSettings();
} }
private void Load(DecompilerSettings decompilerSettings) private void LoadSettings()
{ {
this.Settings = typeof(Decompiler.DecompilerSettings).GetProperties() this.Settings = propertyInfos
.Where(p => p.GetCustomAttribute<BrowsableAttribute>()?.Browsable != false) .Select(p => new DecompilerSettingsItemViewModel(p, decompilerSettings))
.Select(p => new DecompilerSettingsItemViewModel(p) { IsEnabled = p.GetValue(decompilerSettings) is true })
.OrderBy(item => item.Category, NaturalStringComparer.Instance) .OrderBy(item => item.Category, NaturalStringComparer.Instance)
.GroupBy(p => p.Category) .GroupBy(p => p.Category)
.Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray())) .Select(g => new DecompilerSettingsGroupViewModel(g.Key, g.OrderBy(i => i.Description).ToArray()))
.ToArray(); .ToArray();
} }
public void Save(XElement root)
{
var newSettings = ToDecompilerSettings();
ISettingsProvider.SaveDecompilerSettings(root, newSettings);
SettingsService.Instance.DecompilerSettings = newSettings;
SettingsService.Instance.AssemblyListManager.ApplyWinRTProjections = newSettings.ApplyWindowsRuntimeProjections;
SettingsService.Instance.AssemblyListManager.UseDebugSymbols = newSettings.UseDebugSymbols;
}
public void LoadDefaults() public void LoadDefaults()
{ {
Load(new DecompilerSettings()); var defaults = new Decompiler.DecompilerSettings();
}
private DecompilerSettings ToDecompilerSettings()
{
var newSettings = new DecompilerSettings();
foreach (var item in Settings.SelectMany(group => group.Settings)) foreach (var propertyInfo in propertyInfos)
{ {
item.Property.SetValue(newSettings, item.IsEnabled); propertyInfo.SetValue(decompilerSettings, propertyInfo.GetValue(defaults));
} }
return newSettings; LoadSettings();
} }
} }
@ -147,15 +134,20 @@ namespace ICSharpCode.ILSpy.Options
} }
} }
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 { get; } = property;
public bool IsEnabled { public bool IsEnabled {
get => isEnabled; get => isEnabled;
set => SetProperty(ref isEnabled, value); set {
if (SetProperty(ref isEnabled, value))
{
property.SetValue(decompilerSettings, value);
}
}
} }
public string Description { get; set; } = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name); public string Description { get; set; } = GetResourceString(property.GetCustomAttribute<DescriptionAttribute>()?.Description ?? property.Name);

183
ILSpy/Options/DisplaySettings.cs

@ -19,9 +19,6 @@
using System.Windows.Media; using System.Windows.Media;
using System.Xml.Linq; using System.Xml.Linq;
using ICSharpCode.ILSpy.Themes;
using ICSharpCode.ILSpyX.Settings;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy.Options namespace ICSharpCode.ILSpy.Options
@ -29,264 +26,176 @@ namespace ICSharpCode.ILSpy.Options
/// <summary> /// <summary>
/// Description of DisplaySettings. /// Description of DisplaySettings.
/// </summary> /// </summary>
public class DisplaySettings : ObservableObject 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;
}
string theme;
public string Theme {
get => theme;
set => SetProperty(ref theme, value);
}
FontFamily selectedFont; FontFamily selectedFont;
public FontFamily SelectedFont { public FontFamily SelectedFont {
get => selectedFont; get => selectedFont;
set => SetProperty(ref selectedFont, value); set => SetProperty(ref selectedFont, value);
} }
double selectedFontSize; double selectedFontSize;
public double SelectedFontSize { public double SelectedFontSize {
get => selectedFontSize; get => selectedFontSize;
set => SetProperty(ref selectedFontSize, value); set => SetProperty(ref selectedFontSize, value);
} }
bool showLineNumbers; bool showLineNumbers;
public bool ShowLineNumbers { public bool ShowLineNumbers {
get => showLineNumbers; get => showLineNumbers;
set => SetProperty(ref showLineNumbers, value); set => SetProperty(ref showLineNumbers, value);
} }
bool showMetadataTokens; bool showMetadataTokens;
public bool ShowMetadataTokens { public bool ShowMetadataTokens {
get => showMetadataTokens; get => showMetadataTokens;
set => SetProperty(ref showMetadataTokens, value); set => SetProperty(ref showMetadataTokens, value);
} }
bool showMetadataTokensInBase10; bool showMetadataTokensInBase10;
public bool ShowMetadataTokensInBase10 { public bool ShowMetadataTokensInBase10 {
get => showMetadataTokensInBase10; get => showMetadataTokensInBase10;
set => SetProperty(ref showMetadataTokensInBase10, value); set => SetProperty(ref showMetadataTokensInBase10, value);
} }
bool enableWordWrap; bool enableWordWrap;
public bool EnableWordWrap { public bool EnableWordWrap {
get => enableWordWrap; get => enableWordWrap;
set => SetProperty(ref enableWordWrap, value); set => SetProperty(ref enableWordWrap, value);
} }
bool sortResults; bool sortResults;
public bool SortResults { public bool SortResults {
get => sortResults; get => sortResults;
set => SetProperty(ref sortResults, value); set => SetProperty(ref sortResults, value);
} }
bool foldBraces; bool foldBraces;
public bool FoldBraces { public bool FoldBraces {
get => foldBraces; get => foldBraces;
set => SetProperty(ref foldBraces, value); set => SetProperty(ref foldBraces, value);
} }
bool expandMemberDefinitions; bool expandMemberDefinitions;
public bool ExpandMemberDefinitions { public bool ExpandMemberDefinitions {
get => expandMemberDefinitions; get => expandMemberDefinitions;
set => SetProperty(ref expandMemberDefinitions, value); set => SetProperty(ref expandMemberDefinitions, value);
} }
bool expandUsingDeclarations; bool expandUsingDeclarations;
public bool ExpandUsingDeclarations { public bool ExpandUsingDeclarations {
get => expandUsingDeclarations; get => expandUsingDeclarations;
set => SetProperty(ref expandUsingDeclarations, value); set => SetProperty(ref expandUsingDeclarations, value);
} }
bool showDebugInfo; bool showDebugInfo;
public bool ShowDebugInfo { public bool ShowDebugInfo {
get => showDebugInfo; get => showDebugInfo;
set => SetProperty(ref showDebugInfo, value); set => SetProperty(ref showDebugInfo, value);
} }
bool indentationUseTabs; bool indentationUseTabs;
public bool IndentationUseTabs { public bool IndentationUseTabs {
get => indentationUseTabs; get => indentationUseTabs;
set => SetProperty(ref indentationUseTabs, value); set => SetProperty(ref indentationUseTabs, value);
} }
int indentationTabSize; int indentationTabSize;
public int IndentationTabSize { public int IndentationTabSize {
get => indentationTabSize; get => indentationTabSize;
set => SetProperty(ref indentationTabSize, value); set => SetProperty(ref indentationTabSize, value);
} }
int indentationSize; int indentationSize;
public int IndentationSize { public int IndentationSize {
get => indentationSize; get => indentationSize;
set => SetProperty(ref indentationSize, value); set => SetProperty(ref indentationSize, value);
} }
bool highlightMatchingBraces; bool highlightMatchingBraces;
public bool HighlightMatchingBraces { public bool HighlightMatchingBraces {
get => highlightMatchingBraces; get => highlightMatchingBraces;
set => SetProperty(ref highlightMatchingBraces, value); set => SetProperty(ref highlightMatchingBraces, value);
} }
bool highlightCurrentLine; bool highlightCurrentLine;
public bool HighlightCurrentLine { public bool HighlightCurrentLine {
get => highlightCurrentLine; get => highlightCurrentLine;
set => SetProperty(ref highlightCurrentLine, value); set => SetProperty(ref highlightCurrentLine, value);
} }
bool hideEmptyMetadataTables; bool hideEmptyMetadataTables;
public bool HideEmptyMetadataTables { public bool HideEmptyMetadataTables {
get => hideEmptyMetadataTables; get => hideEmptyMetadataTables;
set => SetProperty(ref hideEmptyMetadataTables, value); set => SetProperty(ref hideEmptyMetadataTables, value);
} }
bool useNestedNamespaceNodes; bool useNestedNamespaceNodes;
public bool UseNestedNamespaceNodes { public bool UseNestedNamespaceNodes {
get => useNestedNamespaceNodes; get => useNestedNamespaceNodes;
set => SetProperty(ref useNestedNamespaceNodes, value); set => SetProperty(ref useNestedNamespaceNodes, value);
} }
private bool styleWindowTitleBar; private bool styleWindowTitleBar;
public bool StyleWindowTitleBar { public bool StyleWindowTitleBar {
get => styleWindowTitleBar; get => styleWindowTitleBar;
set => SetProperty(ref styleWindowTitleBar, value); set => SetProperty(ref styleWindowTitleBar, value);
} }
private bool showRawOffsetsAndBytesBeforeInstruction; private bool showRawOffsetsAndBytesBeforeInstruction;
public bool ShowRawOffsetsAndBytesBeforeInstruction { public bool ShowRawOffsetsAndBytesBeforeInstruction {
get => showRawOffsetsAndBytesBeforeInstruction; get => showRawOffsetsAndBytesBeforeInstruction;
set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value); set => SetProperty(ref showRawOffsetsAndBytesBeforeInstruction, value);
} }
public void CopyValues(DisplaySettings s) public XName SectionName => "DisplaySettings";
{
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;
}
public static DisplaySettings Load(ILSpySettings settings, SessionSettings sessionSettings = null) public void LoadFromSection(XElement section)
{ {
XElement e = settings["DisplaySettings"]; SelectedFont = new FontFamily((string)section.Attribute("Font") ?? "Consolas");
var s = new DisplaySettings { SelectedFontSize = (double?)section.Attribute("FontSize") ?? 10.0 * 4 / 3;
SelectedFont = new FontFamily((string)e.Attribute("Font") ?? "Consolas"), ShowLineNumbers = (bool?)section.Attribute("ShowLineNumbers") ?? false;
SelectedFontSize = (double?)e.Attribute("FontSize") ?? 10.0 * 4 / 3, ShowMetadataTokens = (bool?)section.Attribute("ShowMetadataTokens") ?? false;
ShowLineNumbers = (bool?)e.Attribute("ShowLineNumbers") ?? false, ShowMetadataTokensInBase10 = (bool?)section.Attribute("ShowMetadataTokensInBase10") ?? false;
ShowMetadataTokens = (bool?)e.Attribute("ShowMetadataTokens") ?? false, ShowDebugInfo = (bool?)section.Attribute("ShowDebugInfo") ?? false;
ShowMetadataTokensInBase10 = (bool?)e.Attribute("ShowMetadataTokensInBase10") ?? false, EnableWordWrap = (bool?)section.Attribute("EnableWordWrap") ?? false;
ShowDebugInfo = (bool?)e.Attribute("ShowDebugInfo") ?? false, SortResults = (bool?)section.Attribute("SortResults") ?? true;
EnableWordWrap = (bool?)e.Attribute("EnableWordWrap") ?? false, FoldBraces = (bool?)section.Attribute("FoldBraces") ?? false;
SortResults = (bool?)e.Attribute("SortResults") ?? true, ExpandMemberDefinitions = (bool?)section.Attribute("ExpandMemberDefinitions") ?? false;
FoldBraces = (bool?)e.Attribute("FoldBraces") ?? false, ExpandUsingDeclarations = (bool?)section.Attribute("ExpandUsingDeclarations") ?? false;
ExpandMemberDefinitions = (bool?)e.Attribute("ExpandMemberDefinitions") ?? false, IndentationUseTabs = (bool?)section.Attribute("IndentationUseTabs") ?? true;
ExpandUsingDeclarations = (bool?)e.Attribute("ExpandUsingDeclarations") ?? false, IndentationSize = (int?)section.Attribute("IndentationSize") ?? 4;
IndentationUseTabs = (bool?)e.Attribute("IndentationUseTabs") ?? true, IndentationTabSize = (int?)section.Attribute("IndentationTabSize") ?? 4;
IndentationSize = (int?)e.Attribute("IndentationSize") ?? 4, HighlightMatchingBraces = (bool?)section.Attribute("HighlightMatchingBraces") ?? true;
IndentationTabSize = (int?)e.Attribute("IndentationTabSize") ?? 4, HighlightCurrentLine = (bool?)section.Attribute("HighlightCurrentLine") ?? false;
HighlightMatchingBraces = (bool?)e.Attribute("HighlightMatchingBraces") ?? true, HideEmptyMetadataTables = (bool?)section.Attribute("HideEmptyMetadataTables") ?? true;
HighlightCurrentLine = (bool?)e.Attribute("HighlightCurrentLine") ?? false, UseNestedNamespaceNodes = (bool?)section.Attribute("UseNestedNamespaceNodes") ?? false;
HideEmptyMetadataTables = (bool?)e.Attribute("HideEmptyMetadataTables") ?? true, ShowRawOffsetsAndBytesBeforeInstruction = (bool?)section.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false;
UseNestedNamespaceNodes = (bool?)e.Attribute("UseNestedNamespaceNodes") ?? false, StyleWindowTitleBar = (bool?)section.Attribute("StyleWindowTitleBar") ?? false;
ShowRawOffsetsAndBytesBeforeInstruction = (bool?)e.Attribute("ShowRawOffsetsAndBytesBeforeInstruction") ?? false, }
StyleWindowTitleBar = (bool?)e.Attribute("StyleWindowTitleBar") ?? false,
Theme = (sessionSettings ?? SettingsService.Instance.SessionSettings).Theme public void SaveToSection(XElement section)
};
return s;
}
public void Save(XElement root)
{ {
var s = this; section.SetAttributeValue("Font", SelectedFont.Source);
section.SetAttributeValue("FontSize", SelectedFontSize);
var section = new XElement("DisplaySettings"); section.SetAttributeValue("ShowLineNumbers", ShowLineNumbers);
section.SetAttributeValue("Font", s.SelectedFont.Source); section.SetAttributeValue("ShowMetadataTokens", ShowMetadataTokens);
section.SetAttributeValue("FontSize", s.SelectedFontSize); section.SetAttributeValue("ShowMetadataTokensInBase10", ShowMetadataTokensInBase10);
section.SetAttributeValue("ShowLineNumbers", s.ShowLineNumbers); section.SetAttributeValue("ShowDebugInfo", ShowDebugInfo);
section.SetAttributeValue("ShowMetadataTokens", s.ShowMetadataTokens); section.SetAttributeValue("EnableWordWrap", EnableWordWrap);
section.SetAttributeValue("ShowMetadataTokensInBase10", s.ShowMetadataTokensInBase10); section.SetAttributeValue("SortResults", SortResults);
section.SetAttributeValue("ShowDebugInfo", s.ShowDebugInfo); section.SetAttributeValue("FoldBraces", FoldBraces);
section.SetAttributeValue("EnableWordWrap", s.EnableWordWrap); section.SetAttributeValue("ExpandMemberDefinitions", ExpandMemberDefinitions);
section.SetAttributeValue("SortResults", s.SortResults); section.SetAttributeValue("ExpandUsingDeclarations", ExpandUsingDeclarations);
section.SetAttributeValue("FoldBraces", s.FoldBraces); section.SetAttributeValue("IndentationUseTabs", IndentationUseTabs);
section.SetAttributeValue("ExpandMemberDefinitions", s.ExpandMemberDefinitions); section.SetAttributeValue("IndentationSize", IndentationSize);
section.SetAttributeValue("ExpandUsingDeclarations", s.ExpandUsingDeclarations); section.SetAttributeValue("IndentationTabSize", IndentationTabSize);
section.SetAttributeValue("IndentationUseTabs", s.IndentationUseTabs); section.SetAttributeValue("HighlightMatchingBraces", HighlightMatchingBraces);
section.SetAttributeValue("IndentationSize", s.IndentationSize); section.SetAttributeValue("HighlightCurrentLine", HighlightCurrentLine);
section.SetAttributeValue("IndentationTabSize", s.IndentationTabSize); section.SetAttributeValue("HideEmptyMetadataTables", HideEmptyMetadataTables);
section.SetAttributeValue("HighlightMatchingBraces", s.HighlightMatchingBraces); section.SetAttributeValue("UseNestedNamespaceNodes", UseNestedNamespaceNodes);
section.SetAttributeValue("HighlightCurrentLine", s.HighlightCurrentLine); section.SetAttributeValue("ShowRawOffsetsAndBytesBeforeInstruction", ShowRawOffsetsAndBytesBeforeInstruction);
section.SetAttributeValue("HideEmptyMetadataTables", s.HideEmptyMetadataTables); section.SetAttributeValue("StyleWindowTitleBar", StyleWindowTitleBar);
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);
}
} }
} }
} }

3
ILSpy/Options/DisplaySettingsPanel.xaml

@ -3,7 +3,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties" xmlns:properties="clr-namespace:ICSharpCode.ILSpy.Properties"
xmlns:local="clr-namespace:ICSharpCode.ILSpy.Options" 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:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes" xmlns:themes="clr-namespace:ICSharpCode.ILSpy.Themes"
@ -12,7 +11,7 @@
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<DockPanel> <DockPanel>
<Label DockPanel.Dock="Left" Content="{x:Static properties:Resources.DisplaySettingsPanel_Theme}" /> <Label DockPanel.Dock="Left" Content="{x:Static properties:Resources.DisplaySettingsPanel_Theme}" />
<ComboBox ItemsSource="{x:Static themes:ThemeManager.AllThemes}" SelectedItem="{Binding Settings.Theme}" VerticalContentAlignment="Center" Margin="0,0,8,0" /> <ComboBox ItemsSource="{x:Static themes:ThemeManager.AllThemes}" SelectedItem="{Binding SessionSettings.Theme}" VerticalContentAlignment="Center" Margin="0,0,8,0" />
</DockPanel> </DockPanel>
<GroupBox Header="{x:Static properties:Resources.Font}"> <GroupBox Header="{x:Static properties:Resources.Font}">
<Grid> <Grid>

20
ILSpy/Options/DisplaySettingsViewModel.cs

@ -8,6 +8,7 @@ using System.Threading.Tasks;
using System.Windows; using System.Windows;
using TomsToolbox.Wpf; using TomsToolbox.Wpf;
using ICSharpCode.ILSpy.Themes;
namespace ICSharpCode.ILSpy.Options namespace ICSharpCode.ILSpy.Options
{ {
@ -17,6 +18,7 @@ namespace ICSharpCode.ILSpy.Options
{ {
private DisplaySettings settings = new(); private DisplaySettings settings = new();
private FontFamily[] fontFamilies; private FontFamily[] fontFamilies;
private SessionSettings sessionSettings;
public DisplaySettingsViewModel() public DisplaySettingsViewModel()
{ {
@ -40,6 +42,11 @@ namespace ICSharpCode.ILSpy.Options
set => SetProperty(ref settings, value); set => SetProperty(ref settings, value);
} }
public SessionSettings SessionSettings {
get => sessionSettings;
set => SetProperty(ref sessionSettings, value);
}
public FontFamily[] FontFamilies { public FontFamily[] FontFamilies {
get => fontFamilies; get => fontFamilies;
set => SetProperty(ref fontFamilies, value); set => SetProperty(ref fontFamilies, value);
@ -47,9 +54,10 @@ namespace ICSharpCode.ILSpy.Options
public int[] FontSizes { get; } = Enumerable.Range(6, 24 - 6 + 1).ToArray(); public int[] FontSizes { get; } = Enumerable.Range(6, 24 - 6 + 1).ToArray();
public void Load(ILSpySettings spySettings) public void Load(SettingsSnapshot snapshot)
{ {
Settings = DisplaySettings.Load(spySettings); Settings = snapshot.GetSettings<DisplaySettings>();
SessionSettings = snapshot.GetSettings<SessionSettings>();
} }
static bool IsSymbolFont(FontFamily fontFamily) static bool IsSymbolFont(FontFamily fontFamily)
@ -77,14 +85,10 @@ namespace ICSharpCode.ILSpy.Options
.ToArray(); .ToArray();
} }
public void Save(XElement root)
{
Settings.Save(root);
}
public void LoadDefaults() public void LoadDefaults()
{ {
Settings = new(); Settings.LoadFromSection(new XElement("empty"));
SessionSettings.Theme = ThemeManager.Current.DefaultTheme;
} }
} }
} }

35
ICSharpCode.ILSpyX/Settings/MiscSettings.cs → ILSpy/Options/MiscSettings.cs

@ -19,25 +19,38 @@
using System; using System;
using System.Xml.Linq; using System.Xml.Linq;
using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpyX.Settings namespace ICSharpCode.ILSpyX.Settings
{ {
public class MiscSettings : IMiscSettings, ISettingsSection<MiscSettings> public class MiscSettings : ObservableObject, ISettingsSection
{
public 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 XName SectionName => "MiscSettings";
public bool LoadPreviousAssemblies { get; set; } = true;
public static MiscSettings Load(ISettingsProvider settingsProvider) public void LoadFromSection(XElement e)
{ {
XElement e = settingsProvider["MiscSettings"]; AllowMultipleInstances = (bool?)e.Attribute(nameof(AllowMultipleInstances)) ?? false;
var s = new MiscSettings(); LoadPreviousAssemblies = (bool?)e.Attribute(nameof(LoadPreviousAssemblies)) ?? true;
s.AllowMultipleInstances = (bool?)e.Attribute(nameof(s.AllowMultipleInstances)) ?? false; }
s.LoadPreviousAssemblies = (bool?)e.Attribute(nameof(s.LoadPreviousAssemblies)) ?? true;
return s; public void SaveToSection(XElement section)
{
section.SetAttributeValue(nameof(AllowMultipleInstances), AllowMultipleInstances);
section.SetAttributeValue(nameof(LoadPreviousAssemblies), LoadPreviousAssemblies);
} }
} }
} }

11
ILSpy/Options/MiscSettingsPanel.xaml

@ -2,16 +2,17 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 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" 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"> d:DesignHeight="300" d:DesignWidth="300">
<StackPanel Orientation="Vertical"> <StackPanel Orientation="Vertical">
<GroupBox Header="{x:Static properties:Resources.Misc}"> <GroupBox Header="{x:Static properties:Resources.Misc}">
<StackPanel Margin="10"> <StackPanel Margin="10">
<CheckBox IsChecked="{Binding AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" /> <CheckBox IsChecked="{Binding Settings.AllowMultipleInstances}" Content="{x:Static properties:Resources.AllowMultipleInstances}" />
<CheckBox IsChecked="{Binding LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/> <CheckBox IsChecked="{Binding Settings.LoadPreviousAssemblies}" Content="{x:Static properties:Resources.LoadAssembliesThatWereLoadedInTheLastInstance}"/>
<Button Command="{Binding AddRemoveShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText}" Margin="3" /> <Button Command="{Binding AddRemoveShellIntegrationCommand}" Content="{Binding AddRemoveShellIntegrationText, FallbackValue=temp}" Margin="3" />
</StackPanel> </StackPanel>
</GroupBox> </GroupBox>
</StackPanel> </StackPanel>

31
ILSpy/Options/MiscSettingsPanel.xaml.cs

@ -20,45 +20,20 @@ using System.ComponentModel.Composition;
using System.Windows.Controls; using System.Windows.Controls;
using System.Xml.Linq; using System.Xml.Linq;
using ICSharpCode.ILSpyX.Settings; using TomsToolbox.Wpf.Composition.Mef;
namespace ICSharpCode.ILSpy.Options namespace ICSharpCode.ILSpy.Options
{ {
/// <summary> /// <summary>
/// Interaction logic for MiscSettingsPanel.xaml /// Interaction logic for MiscSettingsPanel.xaml
/// </summary> /// </summary>
[ExportOptionPage(Order = 30)] [DataTemplate(typeof(MiscSettingsViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)] [PartCreationPolicy(CreationPolicy.NonShared)]
public partial class MiscSettingsPanel : UserControl, IOptionPage public partial class MiscSettingsPanel
{ {
public MiscSettingsPanel() public MiscSettingsPanel()
{ {
InitializeComponent(); InitializeComponent();
} }
public string Title => Properties.Resources.Misc;
public void Load(ILSpySettings spySettings)
{
this.DataContext = new MiscSettingsViewModel(SettingsService.Instance.MiscSettings);
}
public void Save(XElement root)
{
if (DataContext is not IMiscSettings miscSettings)
return;
IMiscSettings.Save(root, miscSettings);
SettingsService.Instance.MiscSettings = new() {
AllowMultipleInstances = miscSettings.AllowMultipleInstances,
LoadPreviousAssemblies = miscSettings.LoadPreviousAssemblies
};
}
public void LoadDefaults()
{
this.DataContext = new MiscSettingsViewModel(new());
}
} }
} }

47
ILSpy/Options/MiscSettingsViewModel.cs

@ -16,12 +16,12 @@
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System.ComponentModel; using System.ComponentModel.Composition;
using System.IO; using System.IO;
using System.Reflection; using System.Reflection;
using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Input; using System.Windows.Input;
using System.Xml.Linq;
using ICSharpCode.ILSpy.AppEnv; using ICSharpCode.ILSpy.AppEnv;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.Settings;
@ -32,31 +32,14 @@ using TomsToolbox.Wpf;
namespace ICSharpCode.ILSpy.Options namespace ICSharpCode.ILSpy.Options
{ {
public class MiscSettingsViewModel : ObservableObject, IMiscSettings [ExportOptionPage(Order = 30)]
[PartCreationPolicy(CreationPolicy.NonShared)]
public class MiscSettingsViewModel : ObservableObject, IOptionPage
{ {
bool allowMultipleInstances; private MiscSettings settings;
bool loadPreviousAssemblies = true; public MiscSettings Settings {
get => settings;
public MiscSettingsViewModel(MiscSettings s) set => SetProperty(ref settings, value);
{
AllowMultipleInstances = s.AllowMultipleInstances;
LoadPreviousAssemblies = s.LoadPreviousAssemblies;
}
/// <summary>
/// Allow multiple instances.
/// </summary>
public bool AllowMultipleInstances {
get => allowMultipleInstances;
set => SetProperty(ref allowMultipleInstances, value);
}
/// <summary>
/// Load assemblies that were loaded in the previous instance
/// </summary>
public bool LoadPreviousAssemblies {
get => loadPreviousAssemblies;
set => SetProperty(ref loadPreviousAssemblies, value);
} }
public ICommand AddRemoveShellIntegrationCommand => new DelegateCommand(() => AppEnvironment.IsWindows, AddRemoveShellIntegration); public ICommand AddRemoveShellIntegrationCommand => new DelegateCommand(() => AppEnvironment.IsWindows, AddRemoveShellIntegration);
@ -105,5 +88,17 @@ namespace ICSharpCode.ILSpy.Options
return RegistryEntriesExist() ? Properties.Resources.RemoveShellIntegration : Properties.Resources.AddShellIntegration; return RegistryEntriesExist() ? Properties.Resources.RemoveShellIntegration : Properties.Resources.AddShellIntegration;
} }
} }
public string Title => Properties.Resources.Misc;
public void Load(SettingsSnapshot settings)
{
Settings = settings.GetSettings<MiscSettings>();
}
public void LoadDefaults()
{
Settings.LoadFromSection(new XElement("dummy"));
}
} }
} }

5
ILSpy/Options/OptionsDialog.xaml.cs

@ -45,8 +45,9 @@ namespace ICSharpCode.ILSpy.Options
public interface IOptionPage public interface IOptionPage
{ {
string Title { get; } string Title { get; }
void Load(ILSpySettings spySettings);
void Save(XElement root); void Load(SettingsSnapshot settings);
void LoadDefaults(); void LoadDefaults();
} }

13
ILSpy/Options/OptionsDialogViewModel.cs

@ -20,6 +20,8 @@ namespace ICSharpCode.ILSpy.Options
{ {
private IOptionPage? selectedPage; private IOptionPage? selectedPage;
private SettingsSnapshot snapshot;
private readonly IOptionPage[] optionPages = App.ExportProvider.GetExports<IOptionPage, IOptionsMetadata>("OptionPages") private readonly IOptionPage[] optionPages = App.ExportProvider.GetExports<IOptionPage, IOptionsMetadata>("OptionPages")
.OrderBy(page => page.Metadata?.Order) .OrderBy(page => page.Metadata?.Order)
.Select(item => item.Value) .Select(item => item.Value)
@ -28,11 +30,11 @@ namespace ICSharpCode.ILSpy.Options
public OptionsDialogViewModel() public OptionsDialogViewModel()
{ {
var settings = SettingsService.Instance.SpySettings; this.snapshot = SettingsService.Instance.CreateSnapshot();
foreach (var optionPage in optionPages) foreach (var optionPage in optionPages)
{ {
optionPage.Load(settings); optionPage.Load(this.snapshot);
} }
OptionPages = optionPages.Select(page => new OptionsItemViewModel(page)).ToArray(); OptionPages = optionPages.Select(page => new OptionsItemViewModel(page)).ToArray();
@ -60,12 +62,7 @@ namespace ICSharpCode.ILSpy.Options
private void Commit() private void Commit()
{ {
SettingsService.Instance.SpySettings.Update(root => { snapshot.Save();
foreach (var optionPage in optionPages)
{
optionPage.Save(root);
}
});
} }
} }
} }

8
ILSpy/Search/SearchPane.xaml.cs

@ -20,6 +20,7 @@ using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Diagnostics; using System.Diagnostics;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
@ -66,7 +67,7 @@ namespace ICSharpCode.ILSpy.Search
ContextMenuProvider.Add(listBox); ContextMenuProvider.Add(listBox);
MessageBus<CurrentAssemblyListChangedEventArgs>.Subscribers += (sender, e) => CurrentAssemblyList_Changed(); MessageBus<CurrentAssemblyListChangedEventArgs>.Subscribers += (sender, e) => CurrentAssemblyList_Changed();
MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(); MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e);
CompositionTarget.Rendering += UpdateResults; CompositionTarget.Rendering += UpdateResults;
} }
@ -84,8 +85,11 @@ namespace ICSharpCode.ILSpy.Search
} }
} }
void LanguageSettings_PropertyChanged() void Settings_PropertyChanged(object sender, PropertyChangedEventArgs e)
{ {
if (sender is not LanguageSettings)
return;
UpdateFilter(); UpdateFilter();
} }

120
ILSpy/SessionSettings.cs

@ -17,6 +17,7 @@
// DEALINGS IN THE SOFTWARE. // DEALINGS IN THE SOFTWARE.
using System; using System;
using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Globalization; using System.Globalization;
using System.Linq; using System.Linq;
@ -29,7 +30,6 @@ using System.Xml.Linq;
using ICSharpCode.ILSpy.Docking; using ICSharpCode.ILSpy.Docking;
using ICSharpCode.ILSpy.Themes; using ICSharpCode.ILSpy.Themes;
using ICSharpCode.ILSpyX.Search; using ICSharpCode.ILSpyX.Search;
using ICSharpCode.ILSpyX.Settings;
namespace ICSharpCode.ILSpy namespace ICSharpCode.ILSpy
{ {
@ -37,58 +37,37 @@ namespace ICSharpCode.ILSpy
/// Per-session setting: /// Per-session setting:
/// Loaded at startup; saved at exit. /// Loaded at startup; saved at exit.
/// </summary> /// </summary>
public sealed class SessionSettings : INotifyPropertyChanged public sealed class SessionSettings : ISettingsSection
{ {
public SessionSettings(ILSpySettings spySettings) public XName SectionName => "SessionSettings";
{
XElement doc = spySettings["SessionSettings"];
XElement filterSettings = doc.Element("FilterSettings");
if (filterSettings == null)
filterSettings = new XElement("FilterSettings");
this.LanguageSettings = new LanguageSettings(filterSettings);
this.ActiveAssemblyList = (string)doc.Element("ActiveAssemblyList");
XElement activeTreeViewPath = doc.Element("ActiveTreeViewPath");
if (activeTreeViewPath != null)
{
this.ActiveTreeViewPath = activeTreeViewPath.Elements().Select(e => Unescape((string)e)).ToArray();
}
this.ActiveAutoLoadedAssembly = (string)doc.Element("ActiveAutoLoadedAssembly");
this.WindowState = FromString((string)doc.Element("WindowState"), WindowState.Normal); public void LoadFromSection(XElement section)
this.WindowBounds = FromString((string)doc.Element("WindowBounds"), DefaultWindowBounds);
this.SelectedSearchMode = FromString((string)doc.Element("SelectedSearchMode"), SearchMode.TypeAndMember);
this.Theme = FromString((string)doc.Element(nameof(Theme)), ThemeManager.Current.DefaultTheme);
var culture = (string)doc.Element(nameof(CurrentCulture));
this.CurrentCulture = string.IsNullOrEmpty(culture) ? null : culture;
this.DockLayout = new DockLayoutSettings(doc.Element("DockLayout"));
}
public event PropertyChangedEventHandler PropertyChanged;
void OnPropertyChanged([CallerMemberName] string propertyName = null)
{ {
var args = new PropertyChangedEventArgs(propertyName); XElement filterSettings = section.Element("FilterSettings") ?? new XElement("FilterSettings");
PropertyChanged?.Invoke(this, args); LanguageSettings = new(filterSettings);
LanguageSettings.PropertyChanged += (sender, e) => PropertyChanged?.Invoke(sender, e);
MessageBus.Send(this, new SessionSettingsChangedEventArgs(args)); ActiveAssemblyList = (string)section.Element("ActiveAssemblyList");
ActiveTreeViewPath = section.Element("ActiveTreeViewPath")?.Elements().Select(e => Unescape((string)e)).ToArray();
ActiveAutoLoadedAssembly = (string)section.Element("ActiveAutoLoadedAssembly");
WindowState = FromString((string)section.Element("WindowState"), WindowState.Normal);
WindowBounds = FromString((string)section.Element("WindowBounds"), DefaultWindowBounds);
SelectedSearchMode = FromString((string)section.Element("SelectedSearchMode"), SearchMode.TypeAndMember);
Theme = FromString((string)section.Element(nameof(Theme)), ThemeManager.Current.DefaultTheme);
var culture = (string)section.Element(nameof(CurrentCulture));
CurrentCulture = string.IsNullOrEmpty(culture) ? null : culture;
DockLayout = new(section.Element("DockLayout"));
} }
public LanguageSettings LanguageSettings { get; } public LanguageSettings LanguageSettings { get; set; }
public SearchMode SelectedSearchMode { get; set; } public SearchMode SelectedSearchMode { get; set; }
private string theme;
public string Theme { public string Theme {
get => ThemeManager.Current.Theme; get => theme;
set { set => SetProperty(ref theme, value);
ThemeManager.Current.Theme = value;
OnPropertyChanged();
}
} }
public string[] ActiveTreeViewPath; public string[] ActiveTreeViewPath;
@ -120,56 +99,49 @@ namespace ICSharpCode.ILSpy
public WindowState WindowState; public WindowState WindowState;
public Rect WindowBounds; public Rect WindowBounds;
internal static Rect DefaultWindowBounds = new Rect(10, 10, 750, 550); internal static Rect DefaultWindowBounds = new(10, 10, 750, 550);
public DockLayoutSettings DockLayout { get; } public DockLayoutSettings DockLayout { get; set; }
public XElement ToXml() public void SaveToSection(XElement section)
{ {
XElement doc = new XElement("SessionSettings"); section.RemoveAll();
doc.Add(this.LanguageSettings.SaveAsXml());
section.Add(this.LanguageSettings.SaveAsXml());
if (this.ActiveAssemblyList != null) if (this.ActiveAssemblyList != null)
{ {
doc.Add(new XElement("ActiveAssemblyList", this.ActiveAssemblyList)); section.Add(new XElement("ActiveAssemblyList", this.ActiveAssemblyList));
} }
if (this.ActiveTreeViewPath != null) if (this.ActiveTreeViewPath != null)
{ {
doc.Add(new XElement("ActiveTreeViewPath", ActiveTreeViewPath.Select(p => new XElement("Node", Escape(p))))); section.Add(new XElement("ActiveTreeViewPath", ActiveTreeViewPath.Select(p => new XElement("Node", Escape(p)))));
} }
if (this.ActiveAutoLoadedAssembly != null) if (this.ActiveAutoLoadedAssembly != null)
{ {
doc.Add(new XElement("ActiveAutoLoadedAssembly", this.ActiveAutoLoadedAssembly)); section.Add(new XElement("ActiveAutoLoadedAssembly", this.ActiveAutoLoadedAssembly));
} }
doc.Add(new XElement("WindowState", ToString(this.WindowState))); section.Add(new XElement("WindowState", ToString(this.WindowState)));
doc.Add(new XElement("WindowBounds", ToString(this.WindowBounds))); section.Add(new XElement("WindowBounds", ToString(this.WindowBounds)));
doc.Add(new XElement("SelectedSearchMode", ToString(this.SelectedSearchMode))); section.Add(new XElement("SelectedSearchMode", ToString(this.SelectedSearchMode)));
doc.Add(new XElement(nameof(Theme), ToString(this.Theme))); section.Add(new XElement(nameof(Theme), ToString(this.Theme)));
if (this.CurrentCulture != null) if (this.CurrentCulture != null)
{ {
doc.Add(new XElement(nameof(CurrentCulture), this.CurrentCulture)); section.Add(new XElement(nameof(CurrentCulture), this.CurrentCulture));
} }
var dockLayoutElement = new XElement("DockLayout"); var dockLayoutElement = new XElement("DockLayout");
if (DockLayout.Valid) if (DockLayout.Valid)
{ {
dockLayoutElement.Add(DockLayout.SaveAsXml()); dockLayoutElement.Add(DockLayout.SaveAsXml());
} }
doc.Add(dockLayoutElement); section.Add(dockLayoutElement);
return doc;
} }
public void Save() static Regex regex = new("\\\\x(?<num>[0-9A-f]{4})");
{
var doc = ToXml();
SettingsService.Instance.SpySettings.SaveSettings(doc);
}
static Regex regex = new Regex("\\\\x(?<num>[0-9A-f]{4})");
private string activeAssemblyList; private string activeAssemblyList;
static string Escape(string p) static string Escape(string p)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new();
foreach (char ch in p) foreach (char ch in p)
{ {
if (char.IsLetterOrDigit(ch)) if (char.IsLetterOrDigit(ch))
@ -205,5 +177,21 @@ namespace ICSharpCode.ILSpy
TypeConverter c = TypeDescriptor.GetConverter(typeof(T)); TypeConverter c = TypeDescriptor.GetConverter(typeof(T));
return c.ConvertToInvariantString(obj); return c.ConvertToInvariantString(obj);
} }
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new(propertyName));
}
private bool SetProperty<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
} }
} }

10
ILSpy/Themes/ThemeManager.cs

@ -20,6 +20,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Linq; using System.Linq;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
@ -42,6 +43,7 @@ namespace ICSharpCode.ILSpy.Themes
private ThemeManager() private ThemeManager()
{ {
Application.Current.Resources.MergedDictionaries.Add(_themeDictionaryContainer); Application.Current.Resources.MergedDictionaries.Add(_themeDictionaryContainer);
MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_Changed(sender, e);
} }
public string DefaultTheme => "Light"; public string DefaultTheme => "Light";
@ -199,5 +201,13 @@ namespace ICSharpCode.ILSpy.Themes
var b = (byte)((b1 + m) * 255f); var b = (byte)((b1 + m) * 255f);
return (r, g, b); return (r, g, b);
} }
private void Settings_Changed(object? sender, PropertyChangedEventArgs e)
{
if (sender is not SessionSettings settings || e.PropertyName != nameof(SessionSettings.Theme))
return;
Theme = settings.Theme;
}
} }
} }

11
ILSpy/TreeNodes/ILSpyTreeNode.cs

@ -40,9 +40,9 @@ namespace ICSharpCode.ILSpy.TreeNodes
{ {
bool childrenNeedFiltering; bool childrenNeedFiltering;
public ILSpyTreeNode() protected ILSpyTreeNode()
{ {
MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += LanguageSettings_Changed; MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_Changed(sender, e);
} }
LanguageSettings LanguageSettings => SettingsService.Instance.SessionSettings.LanguageSettings; LanguageSettings LanguageSettings => SettingsService.Instance.SessionSettings.LanguageSettings;
@ -127,8 +127,13 @@ namespace ICSharpCode.ILSpy.TreeNodes
} }
} }
protected virtual void LanguageSettings_Changed(object sender, EventArgs e) protected virtual void Settings_Changed(object sender, PropertyChangedEventArgs e)
{ {
if (sender is not ILSpy.LanguageSettings)
return;
if (e.PropertyName is not (nameof(LanguageSettings.Language) or nameof(LanguageSettings.LanguageVersion)))
return;
RaisePropertyChanged(nameof(Text)); RaisePropertyChanged(nameof(Text));
if (IsVisible) if (IsVisible)
{ {

4
ILSpy/Updates/NotifyOfUpdatesStrategy.cs

@ -61,7 +61,7 @@ namespace ICSharpCode.ILSpy.Updates
/// Returns the download URL if an update is available. /// Returns the download URL if an update is available.
/// Returns null if no update is available, or if no check was performed. /// Returns null if no update is available, or if no check was performed.
/// </summary> /// </summary>
public static async Task<string> CheckForUpdatesIfEnabledAsync(ILSpySettings spySettings) public static async Task<string> CheckForUpdatesIfEnabledAsync(ISettingsProvider spySettings)
{ {
UpdateSettings s = new UpdateSettings(spySettings); UpdateSettings s = new UpdateSettings(spySettings);
@ -87,7 +87,7 @@ namespace ICSharpCode.ILSpy.Updates
} }
} }
public static Task<string> CheckForUpdatesAsync(ILSpySettings spySettings) public static Task<string> CheckForUpdatesAsync(ISettingsProvider spySettings)
{ {
UpdateSettings s = new UpdateSettings(spySettings); UpdateSettings s = new UpdateSettings(spySettings);
return CheckForUpdateInternal(s); return CheckForUpdateInternal(s);

2
ILSpy/Updates/UpdateSettings.cs

@ -26,7 +26,7 @@ namespace ICSharpCode.ILSpy.Updates
{ {
sealed class UpdateSettings : INotifyPropertyChanged sealed class UpdateSettings : INotifyPropertyChanged
{ {
public UpdateSettings(ILSpySettings spySettings) public UpdateSettings(ISettingsProvider spySettings)
{ {
XElement s = spySettings["UpdateSettings"]; XElement s = spySettings["UpdateSettings"];
this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true; this.automaticUpdateCheckEnabled = (bool?)s.Element("AutomaticUpdateCheckEnabled") ?? true;

4
ILSpy/Util/MessageBus.cs

@ -52,9 +52,7 @@ namespace ICSharpCode.ILSpy.Util
public class CurrentAssemblyListChangedEventArgs(NotifyCollectionChangedEventArgs e) : WrappedEventArgs<NotifyCollectionChangedEventArgs>(e); public class CurrentAssemblyListChangedEventArgs(NotifyCollectionChangedEventArgs e) : WrappedEventArgs<NotifyCollectionChangedEventArgs>(e);
public class LanguageSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e); public class SettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e);
public class SessionSettingsChangedEventArgs(PropertyChangedEventArgs e) : WrappedEventArgs<PropertyChangedEventArgs>(e);
public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs public class NavigateToReferenceEventArgs(object reference, bool inNewTabPage = false) : EventArgs
{ {

157
ILSpy/Util/SettingsService.cs

@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using System.Xml.Linq;
using ICSharpCode.Decompiler; using ICSharpCode.Decompiler;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
@ -6,36 +9,102 @@ using ICSharpCode.ILSpy.ViewModels;
using ICSharpCode.ILSpyX; using ICSharpCode.ILSpyX;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpyX.Settings;
using DecompilerSettings = ICSharpCode.ILSpy.Options.DecompilerSettings;
#nullable enable
namespace ICSharpCode.ILSpy.Util namespace ICSharpCode.ILSpy.Util
{ {
public class SettingsService public interface ISettingsSection : INotifyPropertyChanged
{ {
public static readonly SettingsService Instance = new(); XName SectionName { get; }
void LoadFromSection(XElement section);
void SaveToSection(XElement section);
}
private SettingsService() public abstract class SettingsServiceBase
{ {
SpySettings = ILSpySettings.Load(); protected readonly ConcurrentDictionary<Type, ISettingsSection> sections = new();
SessionSettings = new(SpySettings);
DecompilerSettings = ISettingsProvider.LoadDecompilerSettings(SpySettings); public ISettingsProvider SpySettings;
DisplaySettings = DisplaySettings.Load(SpySettings, SessionSettings);
MiscSettings = MiscSettings.Load(SpySettings); protected SettingsServiceBase(ISettingsProvider spySettings)
AssemblyListManager = new(SpySettings) { {
ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections, SpySettings = spySettings;
UseDebugSymbols = DecompilerSettings.UseDebugSymbols }
};
public T GetSettings<T>() where T : ISettingsSection, new()
{
return (T)sections.GetOrAdd(typeof(T), _ => {
T section = new T();
var sectionElement = SpySettings[section.SectionName];
section.LoadFromSection(sectionElement);
section.PropertyChanged += Section_PropertyChanged;
return section;
});
}
protected virtual void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
}
}
public class SettingsSnapshot : SettingsServiceBase
{
private readonly SettingsService parent;
public SettingsSnapshot(SettingsService parent) : base(parent.SpySettings)
{
this.parent = parent;
}
public void Save()
{
SpySettings.Update(root => {
foreach (var section in sections.Values)
{
var element = SpySettings[section.SectionName];
section.SaveToSection(element);
var existingElement = root.Element(section.SectionName);
if (existingElement != null)
existingElement.ReplaceWith(element);
else
root.Add(element);
}
});
parent.Reload();
}
} }
public ILSpySettings SpySettings { get; } public class SettingsService : SettingsServiceBase
{
public static readonly SettingsService Instance = new();
private SettingsService() : base(LoadSettings())
{
}
public SessionSettings SessionSettings { get; } public SessionSettings SessionSettings => GetSettings<SessionSettings>();
public DecompilerSettings DecompilerSettings { get; set; } public DecompilerSettings DecompilerSettings => GetSettings<DecompilerSettings>();
public DisplaySettings DisplaySettings { get; } public DisplaySettings DisplaySettings => GetSettings<DisplaySettings>();
public MiscSettings MiscSettings { get; set; } public MiscSettings MiscSettings => GetSettings<MiscSettings>();
public AssemblyListManager AssemblyListManager { get; } private AssemblyListManager? assemblyListManager;
public AssemblyListManager AssemblyListManager => assemblyListManager ??= new(SpySettings) {
ApplyWinRTProjections = DecompilerSettings.ApplyWindowsRuntimeProjections,
UseDebugSymbols = DecompilerSettings.UseDebugSymbols
};
public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage) public DecompilationOptions CreateDecompilationOptions(TabPageModel tabPage)
{ {
@ -56,5 +125,57 @@ namespace ICSharpCode.ILSpy.Util
return AssemblyListManager.CreateList(AssemblyListManager.DefaultListName); return AssemblyListManager.CreateList(AssemblyListManager.DefaultListName);
} }
} }
private bool reloading;
public void Reload()
{
reloading = true;
try
{
SpySettings = ILSpySettings.Load();
foreach (var section in sections.Values)
{
var element = SpySettings[section.SectionName];
section.LoadFromSection(element);
}
}
finally
{
reloading = false;
}
}
public SettingsSnapshot CreateSnapshot()
{
return new(this);
}
protected override void Section_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
base.Section_PropertyChanged(sender, e);
if (!reloading)
{
throw new InvalidOperationException("Settings are read only, use a snapshot to modify.");
}
if (sender is DecompilerSettings decompilerSettings && assemblyListManager != null)
{
assemblyListManager.ApplyWinRTProjections = decompilerSettings.ApplyWindowsRuntimeProjections;
assemblyListManager.UseDebugSymbols = decompilerSettings.UseDebugSymbols;
}
MessageBus.Send(sender, new SettingsChangedEventArgs(e));
}
private static ILSpySettings LoadSettings()
{
ILSpySettings.SettingsFilePathProvider = new ILSpySettingsFilePathProvider();
return ILSpySettings.Load();
}
} }
} }

9
ILSpy/Views/DebugSteps.xaml.cs

@ -33,7 +33,7 @@ namespace ICSharpCode.ILSpy
InitializeComponent(); InitializeComponent();
#if DEBUG #if DEBUG
MessageBus<LanguageSettingsChangedEventArgs>.Subscribers += (sender, e) => LanguageSettings_PropertyChanged(sender, e); MessageBus<SettingsChangedEventArgs>.Subscribers += (sender, e) => Settings_PropertyChanged(sender, e);
MessageBus<AssemblyTreeSelectionChangedEventArgs>.Subscribers += SelectionChanged; MessageBus<AssemblyTreeSelectionChangedEventArgs>.Subscribers += SelectionChanged;
writingOptions.PropertyChanged += WritingOptions_PropertyChanged; writingOptions.PropertyChanged += WritingOptions_PropertyChanged;
@ -60,10 +60,13 @@ namespace ICSharpCode.ILSpy
}); });
} }
private void LanguageSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) private void Settings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
{ {
#if DEBUG #if DEBUG
if (e.PropertyName == "Language") if (sender is not LanguageSettings)
return;
if (e.PropertyName == nameof(LanguageSettings.Language))
{ {
if (language != null) if (language != null)
{ {

11
TestPlugin/CustomOptionPage.xaml

@ -1,8 +1,13 @@
<UserControl x:Class="TestPlugin.CustomOptionPage" <UserControl x:Class="TestPlugin.CustomOptionPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" d:DesignHeight="500" d:DesignWidth="500"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d"
xmlns:testPlugin="clr-namespace:TestPlugin"
d:DataContext="{d:DesignInstance testPlugin:CustomOptionsViewModel}"
>
<StackPanel> <StackPanel>
<CheckBox IsChecked="{Binding UselessOption1}">Useless option 1</CheckBox> <CheckBox IsChecked="{Binding Options.UselessOption1}">Useless option 1</CheckBox>
<Slider Minimum="0" Maximum="100" Value="{Binding UselessOption2}"/> <Slider Minimum="0" Maximum="100" Value="{Binding Options.UselessOption2}"/>
</StackPanel> </StackPanel>
</UserControl> </UserControl>

90
TestPlugin/CustomOptionPage.xaml.cs

@ -1,100 +1,80 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt) // Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under MIT X11 license (for details please see \doc\license.txt) // This code is distributed under MIT X11 license (for details please see \doc\license.txt)
using System.ComponentModel;
using System.ComponentModel.Composition; using System.ComponentModel.Composition;
using System.Windows.Controls;
using System.Xml.Linq; using System.Xml.Linq;
using ICSharpCode.ILSpy.Options; using ICSharpCode.ILSpy.Options;
using ICSharpCode.ILSpyX.Settings; using ICSharpCode.ILSpy.Util;
using TomsToolbox.Wpf;
using TomsToolbox.Wpf.Composition.Mef;
namespace TestPlugin namespace TestPlugin
{ {
[ExportOptionPage(Order = 0)] [DataTemplate(typeof(CustomOptionsViewModel))]
[PartCreationPolicy(CreationPolicy.NonShared)] [PartCreationPolicy(CreationPolicy.NonShared)]
partial class CustomOptionPage : UserControl, IOptionPage partial class CustomOptionPage
{ {
static readonly XNamespace ns = "http://www.ilspy.net/testplugin";
public CustomOptionPage() public CustomOptionPage()
{ {
InitializeComponent(); InitializeComponent();
} }
}
[ExportOptionPage(Order = 0)]
class CustomOptionsViewModel : ObservableObject, IOptionPage
{
private Options options;
public string Title => "TestPlugin"; public string Title => "TestPlugin";
public void Load(ILSpySettings spySettings) public Options Options {
{ get => options;
// For loading options, use ILSpySetting's indexer. set => SetProperty(ref options, value);
// If the specified section does exist, the indexer will return a new empty element.
XElement e = spySettings[ns + "CustomOptions"];
// Now load the options from the XML document:
Options s = new Options();
s.UselessOption1 = (bool?)e.Attribute("useless1") ?? s.UselessOption1;
s.UselessOption2 = (double?)e.Attribute("useless2") ?? s.UselessOption2;
this.DataContext = s;
} }
public void LoadDefaults() public void Load(SettingsSnapshot snapshot)
{ {
this.DataContext = new Options(); this.Options = snapshot.GetSettings<Options>();
} }
public void Save(XElement root) public void LoadDefaults()
{ {
Options s = (Options)this.DataContext; Options.LoadFromSection(new XElement("dummy"));
// Save the options back into XML:
XElement section = new XElement(ns + "CustomOptions");
section.SetAttributeValue("useless1", s.UselessOption1);
section.SetAttributeValue("useless2", s.UselessOption2);
// Replace the existing section in the settings file, or add a new section,
// if required.
XElement existingElement = root.Element(ns + "CustomOptions");
if (existingElement != null)
existingElement.ReplaceWith(section);
else
root.Add(section);
} }
} }
class Options : INotifyPropertyChanged class Options : ObservableObject, ISettingsSection
{ {
static readonly XNamespace ns = "http://www.ilspy.net/testplugin";
bool uselessOption1; bool uselessOption1;
public bool UselessOption1 { public bool UselessOption1 {
get { return uselessOption1; } get => uselessOption1;
set { set => SetProperty(ref uselessOption1, value);
if (uselessOption1 != value)
{
uselessOption1 = value;
OnPropertyChanged("UselessOption1");
}
}
} }
double uselessOption2; double uselessOption2;
public double UselessOption2 { public double UselessOption2 {
get { return uselessOption2; } get => uselessOption2;
set { set => SetProperty(ref uselessOption2, value);
if (uselessOption2 != value)
{
uselessOption2 = value;
OnPropertyChanged("UselessOption2");
}
}
} }
public event PropertyChangedEventHandler PropertyChanged; public XName SectionName { get; } = ns + "CustomOptions";
protected virtual void OnPropertyChanged(string propertyName) public void LoadFromSection(XElement e)
{
if (PropertyChanged != null)
{ {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); UselessOption1 = (bool?)e.Attribute("useless1") ?? false;
UselessOption2 = (double?)e.Attribute("useless2") ?? 50.0;
} }
public void SaveToSection(XElement section)
{
section.SetAttributeValue("useless1", UselessOption1);
section.SetAttributeValue("useless2", UselessOption2);
} }
} }
} }
Loading…
Cancel
Save