From 0fc303c5ab48f14ea4e10b211927e152f84fa082 Mon Sep 17 00:00:00 2001 From: Lars Wallenborn Date: Sat, 8 Aug 2026 21:40:34 +0200 Subject: [PATCH] Remember last Start Debugging dialog options across restarts (#487) The Debug Program dialog kept its MRU only in memory, so the executable, arguments, working directory etc. were lost when dnSpy was restarted. The last used options are now saved to the settings file and restored the next time the dialog is opened. Since StartDebuggingOptions is polymorphic and the concrete types live in per-engine contract assemblies, serialization is implemented via two new virtual methods on StartDebuggingOptionsPage (SerializeOptions/DeserializeOptions) that each engine's page overrides. Pages that don't override them simply aren't persisted. Environment variables are intentionally not persisted: DbgEnvironment pre-loads the entire current process environment, so serializing it would bloat the settings file and replay a stale environment. --- .../DotNetCommonStartDebuggingOptionsPage.cs | 20 +++++++++ ...otNetFrameworkStartDebuggingOptionsPage.cs | 17 ++++++++ .../DotNetStartDebuggingOptionsPage.cs | 24 +++++++++++ .../MonoConnectStartDebuggingOptionsPage.cs | 14 +++++++ ...onoConnectStartDebuggingOptionsPageBase.cs | 19 +++++++++ .../MonoStartDebuggingOptionsPage.cs | 19 +++++++++ .../MonoStartDebuggingOptionsPageBase.cs | 24 +++++++++++ .../UnityConnectStartDebuggingOptionsPage.cs | 14 +++++++ .../UnityStartDebuggingOptionsPage.cs | 14 +++++++ .../DbgUI/StartDebuggingOptionsMru.cs | 6 +++ .../DbgUI/StartDebuggingOptionsProvider.cs | 42 ++++++++++++++++++- .../Dialog/StartDebuggingOptionsPage.cs | 19 +++++++++ 12 files changed, 231 insertions(+), 1 deletion(-) diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetCommonStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetCommonStartDebuggingOptionsPage.cs index 9f6f0a7ec7..4bc593adf3 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetCommonStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetCommonStartDebuggingOptionsPage.cs @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.DotNet.CorDebug; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.CorDebug.Properties; namespace dnSpy.Debugger.DotNet.CorDebug.Dialogs.DebugProgram { @@ -173,6 +174,25 @@ protected T InitializeDefault(T options, string breakKind) where T : CorDebug return options; } + protected static void SerializeCorDebugOptions(ISettingsSection section, CorDebugStartDebuggingOptions options) { + if (options.BreakKind is not null) + section.Attribute(nameof(options.BreakKind), options.BreakKind); + if (options.Filename is not null) + section.Attribute(nameof(options.Filename), options.Filename); + if (options.CommandLine is not null) + section.Attribute(nameof(options.CommandLine), options.CommandLine); + if (options.WorkingDirectory is not null) + section.Attribute(nameof(options.WorkingDirectory), options.WorkingDirectory); + // The environment isn't serialized, it always contains all of the current process' environment variables + } + + protected static void DeserializeCorDebugOptions(ISettingsSection section, CorDebugStartDebuggingOptions options) { + options.BreakKind = section.Attribute(nameof(options.BreakKind)) ?? options.BreakKind; + options.Filename = section.Attribute(nameof(options.Filename)); + options.CommandLine = section.Attribute(nameof(options.CommandLine)); + options.WorkingDirectory = section.Attribute(nameof(options.WorkingDirectory)); + } + protected T GetOptions(T options) where T : CorDebugStartDebuggingOptions { options.Filename = Filename; options.CommandLine = CommandLine; diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetFrameworkStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetFrameworkStartDebuggingOptionsPage.cs index e104948d5a..be8e2d6726 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetFrameworkStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetFrameworkStartDebuggingOptionsPage.cs @@ -29,6 +29,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.StartDebugging; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.CorDebug.Properties; using dnSpy.Debugger.DotNet.CorDebug.Utilities; @@ -121,6 +122,22 @@ public override bool SupportsDebugEngine(Guid engineGuid, out double order) { return false; } + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not DotNetFrameworkStartDebuggingOptions dnfOptions) + return false; + SerializeCorDebugOptions(section, dnfOptions); + if (dnfOptions.DebuggeeVersion is not null) + section.Attribute(nameof(dnfOptions.DebuggeeVersion), dnfOptions.DebuggeeVersion); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new DotNetFrameworkStartDebuggingOptions(); + DeserializeCorDebugOptions(section, options); + options.DebuggeeVersion = section.Attribute(nameof(options.DebuggeeVersion)); + return options; + } + protected override bool CalculateIsValid() => string.IsNullOrEmpty(Verify(nameof(Filename))); protected override string Verify(string columnName) { diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetStartDebuggingOptionsPage.cs index 58468b1604..92c5b6f3a1 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.CorDebug/Dialogs/DebugProgram/DotNetStartDebuggingOptionsPage.cs @@ -28,6 +28,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.StartDebugging; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.CorDebug.Utilities; namespace dnSpy.Debugger.DotNet.CorDebug.Dialogs.DebugProgram { @@ -162,6 +163,29 @@ public override bool SupportsDebugEngine(Guid engineGuid, out double order) { return false; } + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not DotNetStartDebuggingOptions dncOptions) + return false; + SerializeCorDebugOptions(section, dncOptions); + section.Attribute(nameof(dncOptions.UseHost), dncOptions.UseHost); + if (dncOptions.Host is not null) + section.Attribute(nameof(dncOptions.Host), dncOptions.Host); + if (dncOptions.HostArguments is not null) + section.Attribute(nameof(dncOptions.HostArguments), dncOptions.HostArguments); + section.Attribute(nameof(dncOptions.ConnectionTimeout), dncOptions.ConnectionTimeout); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new DotNetStartDebuggingOptions(); + DeserializeCorDebugOptions(section, options); + options.UseHost = section.Attribute(nameof(options.UseHost)) ?? options.UseHost; + options.Host = section.Attribute(nameof(options.Host)); + options.HostArguments = section.Attribute(nameof(options.HostArguments)); + options.ConnectionTimeout = section.Attribute(nameof(options.ConnectionTimeout)) ?? options.ConnectionTimeout; + return options; + } + protected override bool CalculateIsValid() => string.IsNullOrEmpty(Verify(nameof(HostFilename))) && string.IsNullOrEmpty(Verify(nameof(Filename))) && !ConnectionTimeout.HasError; diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPage.cs index 76a0603a4a..e2c1b584da 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPage.cs @@ -21,6 +21,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger; using dnSpy.Contracts.Debugger.DotNet.Mono; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.Mono.Properties; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { @@ -56,5 +57,18 @@ public override StartDebuggingOptionsInfo GetOptions() { var options = GetOptions(new MonoConnectStartDebuggingOptions()); return new StartDebuggingOptionsInfo(options, null, StartDebuggingOptionsInfoFlags.None); } + + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not MonoConnectStartDebuggingOptions connectOptions) + return false; + SerializeMonoConnectOptions(section, connectOptions); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new MonoConnectStartDebuggingOptions(); + DeserializeMonoConnectOptions(section, options); + return options; + } } } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPageBase.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPageBase.cs index 2b6ff818fe..807260bd77 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPageBase.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoConnectStartDebuggingOptionsPageBase.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.DotNet.Mono; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { abstract class MonoConnectStartDebuggingOptionsPageBase : StartDebuggingOptionsPage, IDataErrorInfo { @@ -108,6 +109,24 @@ protected T GetOptions(T options) where T : MonoConnectStartDebuggingOptionsB return options; } + protected static void SerializeMonoConnectOptions(ISettingsSection section, MonoConnectStartDebuggingOptionsBase options) { + if (options.BreakKind is not null) + section.Attribute(nameof(options.BreakKind), options.BreakKind); + if (options.Address is not null) + section.Attribute(nameof(options.Address), options.Address); + section.Attribute(nameof(options.Port), options.Port); + section.Attribute(nameof(options.ConnectionTimeout), options.ConnectionTimeout); + section.Attribute(nameof(options.ProcessIsSuspended), options.ProcessIsSuspended); + } + + protected static void DeserializeMonoConnectOptions(ISettingsSection section, MonoConnectStartDebuggingOptionsBase options) { + options.BreakKind = section.Attribute(nameof(options.BreakKind)) ?? options.BreakKind; + options.Address = section.Attribute(nameof(options.Address)); + options.Port = section.Attribute(nameof(options.Port)) ?? options.Port; + options.ConnectionTimeout = section.Attribute(nameof(options.ConnectionTimeout)) ?? options.ConnectionTimeout; + options.ProcessIsSuspended = section.Attribute(nameof(options.ProcessIsSuspended)) ?? options.ProcessIsSuspended; + } + string IDataErrorInfo.Error => throw new NotImplementedException(); string IDataErrorInfo.this[string columnName] => Verify(columnName); diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPage.cs index 36bb5ecadb..1394ec8dd7 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPage.cs @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.StartDebugging; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { sealed class MonoStartDebuggingOptionsPage : MonoStartDebuggingOptionsPageBase { @@ -126,6 +127,24 @@ public override bool SupportsDebugEngine(Guid engineGuid, out double order) { return false; } + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not MonoStartDebuggingOptions msdOptions) + return false; + SerializeMonoOptions(section, msdOptions); + if (msdOptions.MonoExePath is not null) + section.Attribute(nameof(msdOptions.MonoExePath), msdOptions.MonoExePath); + section.Attribute(nameof(msdOptions.MonoExeOptions), msdOptions.MonoExeOptions); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new MonoStartDebuggingOptions(); + DeserializeMonoOptions(section, options); + options.MonoExePath = section.Attribute(nameof(options.MonoExePath)); + options.MonoExeOptions = section.Attribute(nameof(options.MonoExeOptions)) ?? options.MonoExeOptions; + return options; + } + protected override bool CalculateIsValidCore() => string.IsNullOrEmpty(Verify(nameof(MonoExePath))); protected override string VerifyCore(string columnName) { diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPageBase.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPageBase.cs index 0168f536e1..80d2590cef 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPageBase.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/MonoStartDebuggingOptionsPageBase.cs @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.DotNet.Mono; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.Mono.Properties; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { @@ -194,6 +195,29 @@ protected void GetOptions(MonoStartDebuggingOptionsBase options) { options.BreakKind = FilterBreakKind(BreakKind); } + protected static void SerializeMonoOptions(ISettingsSection section, MonoStartDebuggingOptionsBase options) { + if (options.BreakKind is not null) + section.Attribute(nameof(options.BreakKind), options.BreakKind); + if (options.Filename is not null) + section.Attribute(nameof(options.Filename), options.Filename); + if (options.CommandLine is not null) + section.Attribute(nameof(options.CommandLine), options.CommandLine); + if (options.WorkingDirectory is not null) + section.Attribute(nameof(options.WorkingDirectory), options.WorkingDirectory); + section.Attribute(nameof(options.ConnectionPort), options.ConnectionPort); + section.Attribute(nameof(options.ConnectionTimeout), options.ConnectionTimeout); + // The environment isn't serialized, it always contains all of the current process' environment variables + } + + protected static void DeserializeMonoOptions(ISettingsSection section, MonoStartDebuggingOptionsBase options) { + options.BreakKind = section.Attribute(nameof(options.BreakKind)) ?? options.BreakKind; + options.Filename = section.Attribute(nameof(options.Filename)); + options.CommandLine = section.Attribute(nameof(options.CommandLine)); + options.WorkingDirectory = section.Attribute(nameof(options.WorkingDirectory)); + options.ConnectionPort = section.Attribute(nameof(options.ConnectionPort)) ?? options.ConnectionPort; + options.ConnectionTimeout = section.Attribute(nameof(options.ConnectionTimeout)) ?? options.ConnectionTimeout; + } + string IDataErrorInfo.Error => throw new NotImplementedException(); string IDataErrorInfo.this[string columnName] => Verify(columnName); diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityConnectStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityConnectStartDebuggingOptionsPage.cs index f3fcf3a4fa..73b8347410 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityConnectStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityConnectStartDebuggingOptionsPage.cs @@ -23,6 +23,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.DotNet.Mono; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.DotNet.Mono.Properties; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { @@ -68,5 +69,18 @@ public override StartDebuggingOptionsInfo GetOptions() { var options = GetOptions(new UnityConnectStartDebuggingOptions()); return new StartDebuggingOptionsInfo(options, null, StartDebuggingOptionsInfoFlags.None); } + + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not UnityConnectStartDebuggingOptions connectOptions) + return false; + SerializeMonoConnectOptions(section, connectOptions); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new UnityConnectStartDebuggingOptions { Port = DEFAULT_PORT }; + DeserializeMonoConnectOptions(section, options); + return options; + } } } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityStartDebuggingOptionsPage.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityStartDebuggingOptionsPage.cs index d2087b862e..1fe05ef965 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityStartDebuggingOptionsPage.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger.DotNet.Mono/Dialogs/DebugProgram/UnityStartDebuggingOptionsPage.cs @@ -26,6 +26,7 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.StartDebugging; using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.MVVM; +using dnSpy.Contracts.Settings; namespace dnSpy.Debugger.DotNet.Mono.Dialogs.DebugProgram { sealed class UnityStartDebuggingOptionsPage : MonoStartDebuggingOptionsPageBase { @@ -104,6 +105,19 @@ public override bool SupportsDebugEngine(Guid engineGuid, out double order) { return false; } + public override bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) { + if (options is not UnityStartDebuggingOptions usdOptions) + return false; + SerializeMonoOptions(section, usdOptions); + return true; + } + + public override StartDebuggingOptions? DeserializeOptions(ISettingsSection section) { + var options = new UnityStartDebuggingOptions(); + DeserializeMonoOptions(section, options); + return options; + } + protected override bool CalculateIsValidCore() => true; protected override string VerifyCore(string columnName) => string.Empty; } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsMru.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsMru.cs index f49b93f69a..055e22d771 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsMru.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsMru.cs @@ -79,6 +79,12 @@ public void Add(string filename, StartDebuggingOptions options, Guid pageGuid) { return (info.Options, info.PageGuid); } + public void SetLastOptions(StartDebuggingOptions options, Guid pageGuid) { + if (options is null) + throw new ArgumentNullException(nameof(options)); + lastOptions = ((StartDebuggingOptions)options.Clone(), pageGuid); + } + public (StartDebuggingOptions options, Guid pageGuid)? TryGetLastOptions() => lastOptions; } } diff --git a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsProvider.cs b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsProvider.cs index 9a051710db..b79e66cf08 100644 --- a/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsProvider.cs +++ b/Extensions/dnSpy.Debugger/dnSpy.Debugger/DbgUI/StartDebuggingOptionsProvider.cs @@ -32,23 +32,31 @@ You should have received a copy of the GNU General Public License using dnSpy.Contracts.Debugger.StartDebugging.Dialog; using dnSpy.Contracts.Documents.Tabs; using dnSpy.Contracts.Documents.TreeView; +using dnSpy.Contracts.Settings; using dnSpy.Debugger.Dialogs.DebugProgram; using dnSpy.Debugger.Properties; namespace dnSpy.Debugger.DbgUI { [Export(typeof(StartDebuggingOptionsProvider))] sealed class StartDebuggingOptionsProvider { + static readonly Guid SETTINGS_GUID = new Guid("1D06F2C9-BC3C-4D44-93B3-729F939F3F97"); + const string PageGuidAttr = "PageGuid"; + const string OptionsSectionName = "Options"; + readonly IAppWindow appWindow; readonly IDocumentTabService documentTabService; + readonly ISettingsService settingsService; readonly Lazy[] startDebuggingOptionsPageProviders; readonly Lazy dbgProcessStarterService; readonly Lazy[] genericDebugEngineGuidProviders; readonly StartDebuggingOptionsMru mru; + bool mruLoaded; [ImportingConstructor] - StartDebuggingOptionsProvider(IAppWindow appWindow, IDocumentTabService documentTabService, Lazy dbgProcessStarterService, [ImportMany] IEnumerable> startDebuggingOptionsPageProviders, [ImportMany] IEnumerable> genericDebugEngineGuidProviders) { + StartDebuggingOptionsProvider(IAppWindow appWindow, IDocumentTabService documentTabService, ISettingsService settingsService, Lazy dbgProcessStarterService, [ImportMany] IEnumerable> startDebuggingOptionsPageProviders, [ImportMany] IEnumerable> genericDebugEngineGuidProviders) { this.appWindow = appWindow; this.documentTabService = documentTabService; + this.settingsService = settingsService; this.dbgProcessStarterService = dbgProcessStarterService; this.startDebuggingOptionsPageProviders = startDebuggingOptionsPageProviders.ToArray(); this.genericDebugEngineGuidProviders = genericDebugEngineGuidProviders.OrderBy(a => a.Metadata.Order).ToArray(); @@ -85,6 +93,11 @@ string GetCurrentFilename() { if (pages.Length == 0) return default; + if (!mruLoaded) { + mruLoaded = true; + LoadLastOptions(pages); + } + var oldOptions = mru.TryGetOptions(filename); var lastOptions = mru.TryGetLastOptions(); foreach (var page in pages) { @@ -130,9 +143,36 @@ string GetCurrentFilename() { } mru.Add(info.Filename!, info.Options, vm.SelectedPageGuid); + SaveLastOptions(pages, info.Options, vm.SelectedPageGuid); return (info.Options, info.Flags); } + void LoadLastOptions(StartDebuggingOptionsPage[] pages) { + var section = settingsService.GetOrCreateSection(SETTINGS_GUID); + var pageGuid = section.Attribute(PageGuidAttr); + if (pageGuid is null) + return; + var optionsSection = section.TryGetSection(OptionsSectionName); + if (optionsSection is null) + return; + // The page could be missing, eg. if its extension got uninstalled or disabled + var page = pages.FirstOrDefault(a => a.Guid == pageGuid.Value); + var options = page?.DeserializeOptions(optionsSection); + if (options is null) + return; + mru.SetLastOptions(options, pageGuid.Value); + } + + void SaveLastOptions(StartDebuggingOptionsPage[] pages, StartDebuggingOptions options, Guid pageGuid) { + var page = pages.FirstOrDefault(a => a.Guid == pageGuid); + if (page is null) + return; + var section = settingsService.RecreateSection(SETTINGS_GUID); + section.Attribute(PageGuidAttr, pageGuid); + if (!page.SerializeOptions(section.CreateSection(OptionsSectionName), options)) + settingsService.RemoveSection(SETTINGS_GUID); + } + static bool? IsCompatibleWithCurrentArchitecture(string fileName) { int? bitness = null; try { diff --git a/dnSpy/dnSpy.Contracts.Debugger/StartDebugging/Dialog/StartDebuggingOptionsPage.cs b/dnSpy/dnSpy.Contracts.Debugger/StartDebugging/Dialog/StartDebuggingOptionsPage.cs index 011509650b..43974af671 100644 --- a/dnSpy/dnSpy.Contracts.Debugger/StartDebugging/Dialog/StartDebuggingOptionsPage.cs +++ b/dnSpy/dnSpy.Contracts.Debugger/StartDebugging/Dialog/StartDebuggingOptionsPage.cs @@ -19,6 +19,7 @@ You should have received a copy of the GNU General Public License using System; using System.ComponentModel; +using dnSpy.Contracts.Settings; namespace dnSpy.Contracts.Debugger.StartDebugging.Dialog { /// @@ -100,6 +101,24 @@ public abstract class StartDebuggingOptionsPage : INotifyPropertyChanged { /// Called when the dialog box gets closed /// public virtual void OnClose() { } + + /// + /// Serializes to so they can be + /// restored after dnSpy is restarted. Returns false if the options couldn't be serialized, + /// eg. it's an unsupported options type. The default implementation returns false. + /// + /// Destination section + /// Options created by this page, see + /// + public virtual bool SerializeOptions(ISettingsSection section, StartDebuggingOptions options) => false; + + /// + /// Deserializes options that were serialized by + /// or returns null if they couldn't be deserialized. The default implementation returns null. + /// + /// Section + /// + public virtual StartDebuggingOptions? DeserializeOptions(ISettingsSection section) => null; } ///