diff --git a/src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs b/src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs index 889efcb75d..346c7ca9ac 100644 --- a/src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs +++ b/src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs @@ -36,7 +36,17 @@ internal sealed class RunHistoryService : IRunHistoryService public void AddRunHistoryItem(string item) { - _appStateModel.RunHistory.Add(item); + // insert at the beginning of the list + if (string.IsNullOrWhiteSpace(item)) + { + return; // Do not add empty or whitespace items + } + + _appStateModel.RunHistory.Remove(item); + + // Add the item to the front of the history + _appStateModel.RunHistory.Insert(0, item); + AppStateModel.SaveState(_appStateModel); } } diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs index d6945e6f8f..a6739f546a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/FallbackExecuteItem.cs @@ -16,14 +16,16 @@ namespace Microsoft.CmdPal.Ext.Shell; internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDisposable { + private readonly Action? _addToHistory; private CancellationTokenSource? _cancellationTokenSource; private Task? _currentUpdateTask; - public FallbackExecuteItem(SettingsManager settings) + public FallbackExecuteItem(SettingsManager settings, Action? addToHistory) : base(new NoOpCommand(), Resources.shell_command_display_title) { Title = string.Empty; Icon = Icons.RunV2; + _addToHistory = addToHistory; } public override void UpdateQuery(string query) @@ -152,7 +154,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos if (exeExists) { // TODO we need to probably get rid of the settings for this provider entirely - var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath); + var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, _addToHistory); Title = exeItem.Title; Subtitle = exeItem.Subtitle; Icon = exeItem.Icon; @@ -161,7 +163,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos } else if (pathIsDir) { - var pathItem = new PathListItem(exe, query); + var pathItem = new PathListItem(exe, query, _addToHistory); Title = pathItem.Title; Subtitle = pathItem.Subtitle; Icon = pathItem.Icon; @@ -170,7 +172,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos } else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri)) { - Command = new OpenUrlCommand(searchText) { Result = CommandResult.Dismiss() }; + Command = new OpenUrlWithHistoryCommand(searchText, _addToHistory) { Result = CommandResult.Dismiss() }; Title = searchText; } else diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs index 587496d6de..b9bf090b3b 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Helpers/ShellListPageHelpers.cs @@ -167,7 +167,7 @@ public class ShellListPageHelpers } } - internal static ListItem? ListItemForCommandString(string query) + internal static ListItem? ListItemForCommandString(string query, Action? addToHistory) { var li = new ListItem(); @@ -213,7 +213,7 @@ public class ShellListPageHelpers if (exeExists) { // TODO we need to probably get rid of the settings for this provider entirely - var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath); + var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, addToHistory); li.Command = exeItem.Command; li.Title = exeItem.Title; li.Subtitle = exeItem.Subtitle; @@ -222,7 +222,7 @@ public class ShellListPageHelpers } else if (pathIsDir) { - var pathItem = new PathListItem(exe, query); + var pathItem = new PathListItem(exe, query, addToHistory); li.Command = pathItem.Command; li.Title = pathItem.Title; li.Subtitle = pathItem.Subtitle; @@ -231,7 +231,7 @@ public class ShellListPageHelpers } else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri)) { - li.Command = new OpenUrlCommand(searchText) { Result = CommandResult.Dismiss() }; + li.Command = new OpenUrlWithHistoryCommand(searchText) { Result = CommandResult.Dismiss() }; li.Title = searchText; } else diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/OpenUrlWithHistoryCommand.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/OpenUrlWithHistoryCommand.cs new file mode 100644 index 0000000000..62b4761a34 --- /dev/null +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/OpenUrlWithHistoryCommand.cs @@ -0,0 +1,30 @@ +// 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. + +using System; +using System.IO; +using Microsoft.CommandPalette.Extensions; +using Microsoft.CommandPalette.Extensions.Toolkit; + +namespace Microsoft.CmdPal.Ext.Shell; + +internal sealed partial class OpenUrlWithHistoryCommand : OpenUrlCommand +{ + private readonly Action? _addToHistory; + private readonly string _url; + + public OpenUrlWithHistoryCommand(string url, Action? addToHistory = null) + : base(url) + { + _addToHistory = addToHistory; + _url = url; + } + + public override CommandResult Invoke() + { + _addToHistory?.Invoke(_url); + var result = base.Invoke(); + return result; + } +} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs index 0d07e82751..2b3375446a 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/Pages/ShellListPage.cs @@ -279,14 +279,14 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable cancellationToken.ThrowIfCancellationRequested(); } - private static ListItem PathToListItem(string path, string originalPath, string args = "") + private static ListItem PathToListItem(string path, string originalPath, string args = "", Action? addToHistory = null) { - var pathItem = new PathListItem(path, originalPath); + var pathItem = new PathListItem(path, originalPath, addToHistory); // Is this path an executable? If so, then make a RunExeItem if (IsExecutable(path)) { - var exeItem = new RunExeItem(Path.GetFileName(path), args, path); + var exeItem = new RunExeItem(Path.GetFileName(path), args, path, addToHistory); exeItem.MoreCommands = [ .. exeItem.MoreCommands, @@ -317,12 +317,12 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable .ToArray(); } - internal static RunExeItem CreateExeItem(string exe, string args, string fullExePath) + internal static RunExeItem CreateExeItem(string exe, string args, string fullExePath, Action? addToHistory) { // PathToListItem will return a RunExeItem if it can find a executable. // It will ALSO add the file search commands to the RunExeItem. - return PathToListItem(fullExePath, exe, args) as RunExeItem ?? - new RunExeItem(exe, args, fullExePath); + return PathToListItem(fullExePath, exe, args, addToHistory) as RunExeItem ?? + new RunExeItem(exe, args, fullExePath, addToHistory); } private void CreateAndAddExeItems(string exe, string args, string fullExePath) @@ -334,7 +334,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable } else { - _exeItem = CreateExeItem(exe, args, fullExePath); + _exeItem = CreateExeItem(exe, args, fullExePath, AddToHistory); } } @@ -499,7 +499,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable { var hist = _historyService.GetRunHistory(); var histItems = hist - .Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h))) + .Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h, AddToHistory))) .Where(tuple => tuple.Item2 != null) .Select(tuple => (tuple.h, tuple.Item2!)) .ToList(); @@ -516,6 +516,18 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable _loadedInitialHistory = true; } + internal void AddToHistory(string commandString) + { + if (string.IsNullOrWhiteSpace(commandString)) + { + return; // Do not add empty or whitespace items + } + + _historyService.AddRunHistoryItem(commandString); + + LoadInitialHistory(); + } + public void Dispose() { _cancellationTokenSource?.Cancel(); diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/PathListItem.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/PathListItem.cs index fe4beffbbb..82a231e239 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/PathListItem.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/PathListItem.cs @@ -67,23 +67,3 @@ internal sealed partial class PathListItem : ListItem }); } } - -internal sealed partial class OpenUrlWithHistoryCommand : OpenUrlCommand -{ - private readonly Action? _addToHistory; - private readonly string _url; - - public OpenUrlWithHistoryCommand(string url, Action? addToHistory = null) - : base(url) - { - _addToHistory = addToHistory; - _url = url; - } - - public override CommandResult Invoke() - { - _addToHistory?.Invoke(_url); - var result = base.Invoke(); - return result; - } -} diff --git a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/ShellCommandsProvider.cs b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/ShellCommandsProvider.cs index b9cc08a23a..3a96c11d5f 100644 --- a/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/ShellCommandsProvider.cs +++ b/src/modules/cmdpal/ext/Microsoft.CmdPal.Ext.Shell/ShellCommandsProvider.cs @@ -29,10 +29,10 @@ public partial class ShellCommandsProvider : CommandProvider Icon = Icons.RunV2; Settings = _settingsManager.Settings; - _fallbackItem = new FallbackExecuteItem(_settingsManager); - _shellListPage = new ShellListPage(_settingsManager, _historyService); + _fallbackItem = new FallbackExecuteItem(_settingsManager, _shellListPage.AddToHistory); + _shellPageItem = new CommandItem(_shellListPage) { Icon = Icons.RunV2,