From 7329a0864506cf7be642b0810270605ac25514fb Mon Sep 17 00:00:00 2001 From: matt-edmondson Date: Thu, 8 Oct 2026 13:28:33 +0000 Subject: [PATCH] Activate an existing default profile when none is active [patch] CreateDefaultProfile returned null as soon as the profile existed and never honoured its activation argument. Once the active profile was deleted, every later launch following the README startup sequence ran with no active profile, so BindChord returned false and ExecuteChord null, with no way back. With ProfileActivation.Activate, an existing profile is now activated when no profile is active. An active profile the user chose is left alone, and the return value keeps its documented meaning (null when the profile already existed). Fixes #174 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01T4yQx7YuowTGorXJRkhe8x --- .../DefaultProfileReactivationTests.cs | 76 +++++++++++++++++++ Keybinding/KeybindingManager.cs | 11 ++- 2 files changed, 86 insertions(+), 1 deletion(-) create mode 100644 Keybinding.Test/DefaultProfileReactivationTests.cs diff --git a/Keybinding.Test/DefaultProfileReactivationTests.cs b/Keybinding.Test/DefaultProfileReactivationTests.cs new file mode 100644 index 0000000..e56adc2 --- /dev/null +++ b/Keybinding.Test/DefaultProfileReactivationTests.cs @@ -0,0 +1,76 @@ +// Copyright (c) 2023-2026 ktsu-dev contributors + +namespace ktsu.Keybinding.Test; + +using ktsu.Keybinding.Core; +using ktsu.Keybinding.Core.Models; + +[TestClass] +public class DefaultProfileReactivationTests +{ + private string _testDataDirectory = null!; + + [TestInitialize] + public void Setup() + { + _testDataDirectory = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + Directory.CreateDirectory(_testDataDirectory); + } + + [TestCleanup] + public void Cleanup() + { + if (Directory.Exists(_testDataDirectory)) + { + Directory.Delete(_testDataDirectory, recursive: true); + } + } + + [TestMethod] + public async Task CreateDefaultProfile_AfterActiveProfileWasDeleted_ActivatesExistingDefaultOnNextLaunch() + { + { + using KeybindingManager manager = new(_testDataDirectory); + await manager.InitializeAsync().ConfigureAwait(false); + manager.CreateDefaultProfile(); + manager.Profiles.CreateProfile("vim", "Vim"); + Assert.IsTrue(manager.Profiles.SetActiveProfile("vim")); + Assert.IsTrue(manager.Profiles.DeleteProfile("vim")); + await manager.SaveAsync().ConfigureAwait(false); + } + + { + using KeybindingManager manager = new(_testDataDirectory); + await manager.InitializeAsync().ConfigureAwait(false); + manager.CreateDefaultProfile(); + manager.RegisterCommands([new Command("file.save", "Save")]); + + Assert.AreEqual("default", manager.Profiles.GetActiveProfile()?.Id); + Assert.IsTrue(manager.Keybindings.BindChord("file.save", Chord.Parse("Ctrl+S"))); + Assert.AreEqual("file.save", manager.Keybindings.ExecuteChord(Chord.Parse("Ctrl+S"))); + } + } + + [TestMethod] + public void CreateDefaultProfile_ExistingDefaultWhileAnotherIsActive_LeavesActiveProfileAlone() + { + using KeybindingManager manager = new(_testDataDirectory); + manager.CreateDefaultProfile(); + manager.Profiles.CreateProfile("vim", "Vim"); + Assert.IsTrue(manager.Profiles.SetActiveProfile("vim")); + + Assert.IsNull(manager.CreateDefaultProfile()); + Assert.AreEqual("vim", manager.Profiles.GetActiveProfile()?.Id); + } + + [TestMethod] + public void CreateDefaultProfile_ExistingDefaultWithDoNotActivate_LeavesNoActiveProfile() + { + using KeybindingManager manager = new(_testDataDirectory); + manager.CreateDefaultProfile(); + manager.Profiles.ClearActiveProfile(); + + Assert.IsNull(manager.CreateDefaultProfile(activation: ProfileActivation.DoNotActivate)); + Assert.IsNull(manager.Profiles.GetActiveProfile()); + } +} diff --git a/Keybinding/KeybindingManager.cs b/Keybinding/KeybindingManager.cs index b73c08d..e1b2a6a 100644 --- a/Keybinding/KeybindingManager.cs +++ b/Keybinding/KeybindingManager.cs @@ -150,7 +150,11 @@ public async Task SaveAsync() /// /// The ID for the default profile /// The name for the default profile - /// Whether to set as the active profile + /// + /// Whether to set as the active profile. When the profile already exists, it is activated only if no + /// profile is active, so a startup call recovers from a deleted or unset active profile without + /// overriding the one the user chose. + /// /// The created profile, or null if a profile already exists with the given ID public Profile? CreateDefaultProfile(string profileId = "default", string profileName = "Default", ProfileActivation activation = ProfileActivation.Activate) { @@ -158,6 +162,11 @@ public async Task SaveAsync() if (Profiles.ProfileExists(profileId)) { + if (activation == ProfileActivation.Activate && Profiles.GetActiveProfile() is null) + { + Profiles.SetActiveProfile(profileId); + } + return null; }