diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs
index f215ba5e94..4a0604a995 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileAsAdminCommand.cs
@@ -20,13 +20,15 @@ internal sealed partial class LaunchProfileAsAdminCommand : InvokableCommand
private readonly string _profile;
private readonly bool _openNewTab;
private readonly bool _openQuake;
+ private readonly AppSettingsManager _appSettingsManager;
- internal LaunchProfileAsAdminCommand(string id, string profile, bool openNewTab, bool openQuake)
+ internal LaunchProfileAsAdminCommand(string id, string profile, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
{
this._id = id;
this._profile = profile;
this._openNewTab = openNewTab;
this._openQuake = openQuake;
+ this._appSettingsManager = appSettingsManager;
this.Name = Resources.launch_profile_as_admin;
this.Icon = Icons.AdminIcon;
@@ -59,6 +61,17 @@ internal sealed partial class LaunchProfileAsAdminCommand : InvokableCommand
//_context.API.ShowMsg(name, message, string.Empty);
Logger.LogError($"Failed to open Windows Terminal: {ex.Message}");
}
+
+ try
+ {
+ _appSettingsManager.Current.AddRecentlyUsedProfile(id, profile);
+ _appSettingsManager.Save();
+ }
+ catch (Exception ex)
+ {
+ // We don't want to fail the whole operation if we can't save the recently used profile
+ Logger.LogError($"Failed to save recently used profile: {ex.Message}");
+ }
}
#pragma warning restore IDE0059, CS0168, SA1005
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs
index 9951ad3d68..0ea01b191d 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Commands/LaunchProfileCommand.cs
@@ -20,13 +20,15 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
private readonly string _profile;
private readonly bool _openNewTab;
private readonly bool _openQuake;
+ private readonly AppSettingsManager _appSettingsManager;
- internal LaunchProfileCommand(string id, string profile, string iconPath, bool openNewTab, bool openQuake)
+ internal LaunchProfileCommand(string id, string profile, string iconPath, bool openNewTab, bool openQuake, AppSettingsManager appSettingsManager)
{
this._id = id;
this._profile = profile;
this._openNewTab = openNewTab;
this._openQuake = openQuake;
+ this._appSettingsManager = appSettingsManager;
this.Name = Resources.launch_profile;
this.Icon = new IconInfo(iconPath);
@@ -62,6 +64,17 @@ internal sealed partial class LaunchProfileCommand : InvokableCommand
// _context.API.ShowMsg(name, message, string.Empty);
Logger.LogError($"Failed to open Windows Terminal: {ex.Message}");
}
+
+ try
+ {
+ _appSettingsManager.Current.AddRecentlyUsedProfile(id, profile);
+ _appSettingsManager.Save();
+ }
+ catch (Exception ex)
+ {
+ // We don't want to fail the whole operation if we can't save the recently used profile
+ Logger.LogError($"Failed to save recently used profile: {ex.Message}");
+ }
}
#pragma warning restore IDE0059, CS0168
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettings.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettings.cs
index fecf798215..9bc8349e6c 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettings.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettings.cs
@@ -4,7 +4,9 @@
#nullable enable
+using System.Collections.Generic;
using System.Text.Json.Serialization;
+using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
@@ -15,10 +17,30 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
///
public sealed class AppSettings
{
+ private const int MaxRecentProfilesCount = 64;
+
///
/// Gets or sets the last selected channel identifier for the Windows Terminal extension.
/// Empty string when no channel has been selected yet.
///
[JsonPropertyName("lastSelectedChannel")]
public string LastSelectedChannel { get; set; } = string.Empty;
+
+ ///
+ /// Gets the list of recently used profile identifiers.
+ ///
+ [JsonPropertyName("recentlyUsedProfiles")]
+ public List RecentlyUsedProfiles { get; init; } = [];
+
+ public void AddRecentlyUsedProfile(string appId, string profileName)
+ {
+ var key = new TerminalProfileKey(appId, profileName);
+ RecentlyUsedProfiles.Remove(key);
+ RecentlyUsedProfiles.Insert(0, key);
+
+ if (RecentlyUsedProfiles.Count > MaxRecentProfilesCount)
+ {
+ RecentlyUsedProfiles.RemoveRange(MaxRecentProfilesCount, RecentlyUsedProfiles.Count - MaxRecentProfilesCount);
+ }
+ }
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsJsonContext.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsJsonContext.cs
index 712970c3eb..50fb9e8dc1 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsJsonContext.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsJsonContext.cs
@@ -10,6 +10,4 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
[JsonSourceGenerationOptions(WriteIndented = true)]
[JsonSerializable(typeof(AppSettings))]
-internal sealed partial class AppSettingsJsonContext : JsonSerializerContext
-{
-}
+internal sealed partial class AppSettingsJsonContext : JsonSerializerContext;
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsManager.cs
index 2d5cbbd30c..6310f1123a 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/AppSettingsManager.cs
@@ -12,8 +12,6 @@ using Microsoft.CommandPalette.Extensions.Toolkit;
namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
-#nullable enable
-
public sealed class AppSettingsManager
{
private const string FileName = "appsettings.json";
@@ -42,7 +40,7 @@ public sealed class AppSettingsManager
if (File.Exists(_filePath))
{
var json = File.ReadAllText(_filePath);
- var loaded = JsonSerializer.Deserialize(json, AppSettingsJsonContext.Default.AppSettings);
+ var loaded = JsonSerializer.Deserialize(json, AppSettingsJsonContext.Default.AppSettings!);
if (loaded is not null)
{
Current = loaded;
@@ -60,7 +58,7 @@ public sealed class AppSettingsManager
{
try
{
- var json = JsonSerializer.Serialize(Current, AppSettingsJsonContext.Default.AppSettings);
+ var json = JsonSerializer.Serialize(Current, AppSettingsJsonContext.Default.AppSettings!);
File.WriteAllText(_filePath, json);
}
catch (Exception ex)
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ProfileSortOrder.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ProfileSortOrder.cs
new file mode 100644
index 0000000000..60be4a573e
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/ProfileSortOrder.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
+
+public enum ProfileSortOrder
+{
+ Default = 0,
+ Alphabetical = 1,
+ MostRecentlyUsed = 2,
+}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/SettingsManager.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/SettingsManager.cs
index 38ee476bad..ba4ee3f600 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/SettingsManager.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/SettingsManager.cs
@@ -40,6 +40,16 @@ public class SettingsManager : JsonSettingsManager
Resources.save_last_selected_channel_description!,
false);
+ private readonly ChoiceSetSetting _profileSortOrder = new(
+ Namespaced(nameof(ProfileSortOrder)),
+ Resources.profile_sort_order!,
+ Resources.profile_sort_order_description!,
+ [
+ new ChoiceSetSetting.Choice(Resources.profile_sort_order_item_default!, ProfileSortOrder.Default.ToString("G")),
+ new ChoiceSetSetting.Choice(Resources.profile_sort_order_item_alphabetical!, ProfileSortOrder.Alphabetical.ToString("G")),
+ new ChoiceSetSetting.Choice(Resources.profile_sort_order_item_mru!, ProfileSortOrder.MostRecentlyUsed.ToString("G")),
+ ]);
+
public bool ShowHiddenProfiles => _showHiddenProfiles.Value;
public bool OpenNewTab => _openNewTab.Value;
@@ -48,6 +58,8 @@ public class SettingsManager : JsonSettingsManager
public bool SaveLastSelectedChannel => _saveLastSelectedChannel.Value;
+ public ProfileSortOrder ProfileSortOrder => System.Enum.TryParse(_profileSortOrder.Value, out var result) ? result : ProfileSortOrder.Default;
+
private static string SettingsJsonPath()
{
var directory = Utilities.BaseSettingsPath("Microsoft.CmdPal");
@@ -65,6 +77,7 @@ public class SettingsManager : JsonSettingsManager
Settings.Add(_openNewTab);
Settings.Add(_openQuake);
Settings.Add(_saveLastSelectedChannel);
+ Settings.Add(_profileSortOrder);
// Load settings from file upon initialization
LoadSettings();
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalProfileKey.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalProfileKey.cs
new file mode 100644
index 0000000000..ab0d520a32
--- /dev/null
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalProfileKey.cs
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft Corporation
+// The Microsoft Corporation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+#nullable enable
+namespace Microsoft.CmdPal.Ext.WindowsTerminal.Helpers;
+
+public sealed record TerminalProfileKey(string AppId, string ProfileName);
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs
index 39b3680470..f3e35e8af3 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Helpers/TerminalQuery.cs
@@ -58,7 +58,7 @@ public class TerminalQuery : ITerminalQuery
profiles.AddRange(TerminalHelper.ParseSettings(terminal, settingsJson));
}
- return profiles.OrderBy(p => p.Name);
+ return profiles;
}
public IEnumerable GetTerminals()
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs
index 89bb98ca22..e968b9848b 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Pages/ProfilesListPage.cs
@@ -51,9 +51,16 @@ internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
Icon = Icons.TerminalIcon;
Name = Resources.profiles_list_page_name;
_terminalSettings = terminalSettings;
+ _terminalSettings.Settings.SettingsChanged += Settings_SettingsChanged;
_appSettingsManager = appSettingsManager;
}
+ private void Settings_SettingsChanged(object sender, Settings args)
+ {
+ EnsureInitialized();
+ RaiseItemsChanged();
+ }
+
private List Query()
{
EnsureInitialized();
@@ -62,7 +69,27 @@ internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
openNewTab = _terminalSettings.OpenNewTab;
openQuake = _terminalSettings.OpenQuake;
- var profiles = _terminalQuery.GetProfiles();
+ var profiles = _terminalQuery.GetProfiles()!;
+
+ switch (_terminalSettings.ProfileSortOrder)
+ {
+ case ProfileSortOrder.MostRecentlyUsed:
+ var mru = _appSettingsManager.Current.RecentlyUsedProfiles ?? [];
+ profiles = profiles.OrderBy(p =>
+ {
+ var key = new TerminalProfileKey(p.Terminal?.AppUserModelId ?? string.Empty, p.Name ?? string.Empty);
+ var index = mru.IndexOf(key);
+ return index == -1 ? int.MaxValue : index;
+ })
+ .ThenBy(static p => p.Name, StringComparer.CurrentCultureIgnoreCase)
+ .ToList();
+ break;
+ case ProfileSortOrder.Default:
+ case ProfileSortOrder.Alphabetical:
+ default:
+ profiles = profiles.OrderBy(static p => p.Name, StringComparer.CurrentCultureIgnoreCase);
+ break;
+ }
if (terminalFilters?.IsAllSelected == false)
{
@@ -78,12 +105,12 @@ internal sealed partial class ProfilesListPage : ListPage, INotifyItemsChanged
continue;
}
- result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, profile.Terminal.LogoPath, openNewTab, openQuake))
+ result.Add(new ListItem(new LaunchProfileCommand(profile.Terminal.AppUserModelId, profile.Name, profile.Terminal.LogoPath, openNewTab, openQuake, _appSettingsManager))
{
Title = profile.Name,
Subtitle = profile.Terminal.DisplayName,
MoreCommands = [
- new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake)),
+ new CommandContextItem(new LaunchProfileAsAdminCommand(profile.Terminal.AppUserModelId, profile.Name, openNewTab, openQuake, _appSettingsManager)),
],
});
}
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.Designer.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.Designer.cs
index 21faa322ec..81d82638f0 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.Designer.cs
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.Designer.cs
@@ -159,6 +159,60 @@ namespace Microsoft.CmdPal.Ext.WindowsTerminal.Properties {
}
}
+ ///
+ /// Looks up a localized string similar to Profiles order.
+ ///
+ internal static string profile_sort_order {
+ get {
+ return ResourceManager.GetString("profile_sort_order", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Profiles order.
+ ///
+ internal static string profile_sort_order_description {
+ get {
+ return ResourceManager.GetString("profile_sort_order_description", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Alphabetical.
+ ///
+ internal static string profile_sort_order_item_alphabetical {
+ get {
+ return ResourceManager.GetString("profile_sort_order_item_alphabetical", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to As defined in Terminal.
+ ///
+ internal static string profile_sort_order_item_as_in_terminal {
+ get {
+ return ResourceManager.GetString("profile_sort_order_item_as_in_terminal", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Default (alphabetical).
+ ///
+ internal static string profile_sort_order_item_default {
+ get {
+ return ResourceManager.GetString("profile_sort_order_item_default", resourceCulture);
+ }
+ }
+
+ ///
+ /// Looks up a localized string similar to Most recently used.
+ ///
+ internal static string profile_sort_order_item_mru {
+ get {
+ return ResourceManager.GetString("profile_sort_order_item_mru", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Windows Terminal Profiles.
///
diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.resx b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.resx
index 774a021c02..23a1533fab 100644
--- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.resx
+++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.WindowsTerminal/Properties/Resources.resx
@@ -173,4 +173,22 @@
Remember the last selected channel instead of resetting to All Channels.
+
+ Profiles order
+
+
+ Profiles order
+
+
+ Default (alphabetical)
+
+
+ Most recently used
+
+
+ As defined in Terminal
+
+
+ Alphabetical
+
\ No newline at end of file