From cd54ef4ee0d623729bc873be02ac729d82af4939 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 16:36:55 +0000 Subject: [PATCH] Activate a remaining profile at startup when default is gone and none is active [patch] The #189 fix returned early from CreateDefaultProfile whenever any profile existed, before activating anything. Once default was deleted and the active profile was gone, every launch ended with no active profile, so BindChord failed and ExecuteChord returned null. When profiles exist but none is active, activate the first by ordinal ID, still without recreating default or replacing an active profile. Fixes #196 Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_017vfJW6CpCSCzyWrJduFwhr --- .../DefaultProfileReactivationTests.cs | 51 +++++++++++++++++++ Keybinding/KeybindingManager.cs | 16 ++++-- 2 files changed, 63 insertions(+), 4 deletions(-) diff --git a/Keybinding.Test/DefaultProfileReactivationTests.cs b/Keybinding.Test/DefaultProfileReactivationTests.cs index e56adc2..f2b573d 100644 --- a/Keybinding.Test/DefaultProfileReactivationTests.cs +++ b/Keybinding.Test/DefaultProfileReactivationTests.cs @@ -51,6 +51,57 @@ public async Task CreateDefaultProfile_AfterActiveProfileWasDeleted_ActivatesExi } } + [TestMethod] + public async Task CreateDefaultProfile_DefaultAndActiveProfileDeleted_ActivatesRemainingProfileOnNextLaunch() + { + { + using KeybindingManager manager = new(_testDataDirectory); + await manager.InitializeAsync().ConfigureAwait(false); + manager.CreateDefaultProfile(); + manager.Profiles.CreateProfile("vim", "Vim"); + manager.Profiles.CreateProfile("emacs", "Emacs"); + Assert.IsTrue(manager.Profiles.SetActiveProfile("vim")); + Assert.IsTrue(manager.Profiles.DeleteProfile("default")); + Assert.IsTrue(manager.Profiles.DeleteProfile("vim")); + await manager.SaveAsync().ConfigureAwait(false); + } + + { + using KeybindingManager manager = new(_testDataDirectory); + await manager.InitializeAsync().ConfigureAwait(false); + Assert.IsNull(manager.CreateDefaultProfile()); + manager.RegisterCommands([new Command("file.save", "Save")]); + + Assert.AreEqual("emacs", manager.Profiles.GetActiveProfile()?.Id); + Assert.IsFalse(manager.Profiles.ProfileExists("default")); + 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_NoDefaultAndNoneActive_ActivatesFirstProfileByOrdinalId() + { + using KeybindingManager manager = new(_testDataDirectory); + manager.Profiles.CreateProfile("vim", "Vim"); + manager.Profiles.CreateProfile("Emacs", "Emacs"); + manager.Profiles.CreateProfile("helix", "Helix"); + + Assert.IsNull(manager.CreateDefaultProfile()); + Assert.AreEqual("Emacs", manager.Profiles.GetActiveProfile()?.Id); + Assert.IsFalse(manager.Profiles.ProfileExists("default")); + } + + [TestMethod] + public void CreateDefaultProfile_NoDefaultAndNoneActiveWithDoNotActivate_LeavesNoActiveProfile() + { + using KeybindingManager manager = new(_testDataDirectory); + manager.Profiles.CreateProfile("vim", "Vim"); + + Assert.IsNull(manager.CreateDefaultProfile(activation: ProfileActivation.DoNotActivate)); + Assert.IsNull(manager.Profiles.GetActiveProfile()); + } + [TestMethod] public void CreateDefaultProfile_ExistingDefaultWhileAnotherIsActive_LeavesActiveProfileAlone() { diff --git a/Keybinding/KeybindingManager.cs b/Keybinding/KeybindingManager.cs index 1b93caf..268a3bf 100644 --- a/Keybinding/KeybindingManager.cs +++ b/Keybinding/KeybindingManager.cs @@ -202,9 +202,10 @@ .. storedCommands.Where(c => !commandIds.Contains(c.Id.ToString()) && !_persiste /// The ID for the default profile /// The name for the default profile /// - /// Whether to set as the active profile. The profile is activated only if no profile is active, whether it - /// was just created or already existed, so a startup call recovers from a deleted or unset active profile - /// without overriding the one the user chose. + /// Whether to set as the active profile. A profile 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 + /// profile with the given ID is activated if it exists or was just created; otherwise, when other profiles + /// exist, the first of them by ordinal ID is activated. /// /// /// The created profile, or null if it was not created: because a profile with the given ID already exists, @@ -225,8 +226,15 @@ .. storedCommands.Where(c => !commandIds.Contains(c.Id.ToString()) && !_persiste return null; } - if (Profiles.GetAllProfiles().Count > 0) + IReadOnlyCollection existingProfiles = Profiles.GetAllProfiles(); + if (existingProfiles.Count > 0) { + if (activation == ProfileActivation.Activate && Profiles.GetActiveProfile() is null) + { + string firstProfileId = existingProfiles.Select(p => p.Id).OrderBy(id => id, StringComparer.Ordinal).First(); + Profiles.SetActiveProfile(firstProfileId); + } + return null; }