mirror of
https://github.com/microsoft/PowerToys.git
synced 2025-12-16 11:48:06 +01:00
CmdPal: Add history to the new run page (#40427)
_⚠️ targets #39955_
This adds history support to the new run page.
* It'll initialize the history with the history from the run dialog, if
there is any.
* Any new commands that are run, or files/dirs that are opened will also
get added to the history
* history will persist across reboots
This commit is contained in:
6
.github/actions/spell-check/allow/code.txt
vendored
6
.github/actions/spell-check/allow/code.txt
vendored
@@ -282,3 +282,9 @@ xef
|
|||||||
xes
|
xes
|
||||||
PACKAGEVERSIONNUMBER
|
PACKAGEVERSIONNUMBER
|
||||||
APPXMANIFESTVERSION
|
APPXMANIFESTVERSION
|
||||||
|
|
||||||
|
# MRU lists
|
||||||
|
CACHEWRITE
|
||||||
|
MRUCMPPROC
|
||||||
|
MRUINFO
|
||||||
|
REGSTR
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
// 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.Collections.Generic;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.Common.Services;
|
||||||
|
|
||||||
|
public interface IRunHistoryService
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the run history.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>A list of run history items.</returns>
|
||||||
|
IReadOnlyList<string> GetRunHistory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Clears the run history.
|
||||||
|
/// </summary>
|
||||||
|
void ClearRunHistory();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a run history item.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="item">The run history item to add.</param>
|
||||||
|
void AddRunHistoryItem(string item);
|
||||||
|
}
|
||||||
@@ -21,8 +21,12 @@ public partial class AppStateModel : ObservableObject
|
|||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// STATE HERE
|
// STATE HERE
|
||||||
|
// Make sure that you make the setters public (JsonSerializer.Deserialize will fail silently otherwise)!
|
||||||
|
// Make sure that any new types you add are added to JsonSerializationContext!
|
||||||
public RecentCommandsManager RecentCommands { get; set; } = new();
|
public RecentCommandsManager RecentCommands { get; set; } = new();
|
||||||
|
|
||||||
|
public List<string> RunHistory { get; set; } = [];
|
||||||
|
|
||||||
// END SETTINGS
|
// END SETTINGS
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
@@ -86,7 +90,7 @@ public partial class AppStateModel : ObservableObject
|
|||||||
{
|
{
|
||||||
foreach (var item in newSettings)
|
foreach (var item in newSettings)
|
||||||
{
|
{
|
||||||
savedSettings[item.Key] = item.Value != null ? item.Value.DeepClone() : null;
|
savedSettings[item.Key] = item.Value?.DeepClone();
|
||||||
}
|
}
|
||||||
|
|
||||||
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
|
var serialized = savedSettings.ToJsonString(JsonSerializationContext.Default.AppStateModel.Options);
|
||||||
@@ -121,20 +125,4 @@ public partial class AppStateModel : ObservableObject
|
|||||||
// now, the settings is just next to the exe
|
// now, the settings is just next to the exe
|
||||||
return Path.Combine(directory, "state.json");
|
return Path.Combine(directory, "state.json");
|
||||||
}
|
}
|
||||||
|
|
||||||
// [UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
|
|
||||||
// private static readonly JsonSerializerOptions _serializerOptions = new()
|
|
||||||
// {
|
|
||||||
// WriteIndented = true,
|
|
||||||
// Converters = { new JsonStringEnumConverter() },
|
|
||||||
// };
|
|
||||||
|
|
||||||
// private static readonly JsonSerializerOptions _deserializerOptions = new()
|
|
||||||
// {
|
|
||||||
// PropertyNameCaseInsensitive = true,
|
|
||||||
// IncludeFields = true,
|
|
||||||
// AllowTrailingCommas = true,
|
|
||||||
// PreferredObjectCreationHandling = JsonObjectCreationHandling.Populate,
|
|
||||||
// ReadCommentHandling = JsonCommentHandling.Skip,
|
|
||||||
// };
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -101,6 +101,7 @@ public partial class App : Application
|
|||||||
var files = new IndexerCommandsProvider();
|
var files = new IndexerCommandsProvider();
|
||||||
files.SuppressFallbackWhen(ShellCommandsProvider.SuppressFileFallbackIf);
|
files.SuppressFallbackWhen(ShellCommandsProvider.SuppressFileFallbackIf);
|
||||||
services.AddSingleton<ICommandProvider>(allApps);
|
services.AddSingleton<ICommandProvider>(allApps);
|
||||||
|
|
||||||
services.AddSingleton<ICommandProvider, ShellCommandsProvider>();
|
services.AddSingleton<ICommandProvider, ShellCommandsProvider>();
|
||||||
services.AddSingleton<ICommandProvider, CalculatorCommandProvider>();
|
services.AddSingleton<ICommandProvider, CalculatorCommandProvider>();
|
||||||
services.AddSingleton<ICommandProvider>(files);
|
services.AddSingleton<ICommandProvider>(files);
|
||||||
@@ -146,6 +147,7 @@ public partial class App : Application
|
|||||||
services.AddSingleton(state);
|
services.AddSingleton(state);
|
||||||
services.AddSingleton<IExtensionService, ExtensionService>();
|
services.AddSingleton<IExtensionService, ExtensionService>();
|
||||||
services.AddSingleton<TrayIconService>();
|
services.AddSingleton<TrayIconService>();
|
||||||
|
services.AddSingleton<IRunHistoryService, RunHistoryService>();
|
||||||
|
|
||||||
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
services.AddSingleton<IRootPageService, PowerToysRootPageService>();
|
||||||
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
services.AddSingleton<IAppHostService, PowerToysAppHostService>();
|
||||||
|
|||||||
@@ -336,8 +336,6 @@ public sealed partial class SearchBar : UserControl,
|
|||||||
// ... Move the cursor to the end of the input
|
// ... Move the cursor to the end of the input
|
||||||
FilterBox.Select(FilterBox.Text.Length, 0);
|
FilterBox.Select(FilterBox.Text.Length, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO! deal with suggestion
|
|
||||||
}
|
}
|
||||||
else if (property == nameof(ListViewModel.InitialSearchText))
|
else if (property == nameof(ListViewModel.InitialSearchText))
|
||||||
{
|
{
|
||||||
|
|||||||
50
src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs
Normal file
50
src/modules/cmdpal/Microsoft.CmdPal.UI/RunHistoryService.cs
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
// 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 Microsoft.CmdPal.Common.Services;
|
||||||
|
using Microsoft.CmdPal.UI.ViewModels;
|
||||||
|
|
||||||
|
namespace Microsoft.CmdPal.UI;
|
||||||
|
|
||||||
|
internal sealed class RunHistoryService : IRunHistoryService
|
||||||
|
{
|
||||||
|
private readonly AppStateModel _appStateModel;
|
||||||
|
|
||||||
|
public RunHistoryService(AppStateModel appStateModel)
|
||||||
|
{
|
||||||
|
_appStateModel = appStateModel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public IReadOnlyList<string> GetRunHistory()
|
||||||
|
{
|
||||||
|
if (_appStateModel.RunHistory.Count == 0)
|
||||||
|
{
|
||||||
|
var history = Microsoft.Terminal.UI.RunHistory.CreateRunHistory();
|
||||||
|
_appStateModel.RunHistory.AddRange(history);
|
||||||
|
}
|
||||||
|
|
||||||
|
return _appStateModel.RunHistory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void ClearRunHistory()
|
||||||
|
{
|
||||||
|
_appStateModel.RunHistory.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddRunHistoryItem(string 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -383,4 +383,5 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
|||||||
icon.Height(targetSize);
|
icon.Height(targetSize);
|
||||||
return icon;
|
return icon;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ namespace winrt::Microsoft::Terminal::UI::implementation
|
|||||||
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
|
static Microsoft::UI::Xaml::Controls::IconSource IconSourceMUX(const winrt::hstring& iconPath, bool convertToGrayscale, const int targetSize=24);
|
||||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
|
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath);
|
||||||
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);
|
static Microsoft::UI::Xaml::Controls::IconElement IconMUX(const winrt::hstring& iconPath, const int targetSize);
|
||||||
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -153,6 +153,9 @@
|
|||||||
<ClInclude Include="IconPathConverter.h">
|
<ClInclude Include="IconPathConverter.h">
|
||||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="RunHistory.h">
|
||||||
|
<DependentUpon>RunHistory.idl</DependentUpon>
|
||||||
|
</ClInclude>
|
||||||
<ClInclude Include="ResourceString.h">
|
<ClInclude Include="ResourceString.h">
|
||||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@@ -168,6 +171,9 @@
|
|||||||
<ClCompile Include="IconPathConverter.cpp">
|
<ClCompile Include="IconPathConverter.cpp">
|
||||||
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
<DependentUpon>IconPathConverter.idl</DependentUpon>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="RunHistory.cpp">
|
||||||
|
<DependentUpon>RunHistory.idl</DependentUpon>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="ResourceString.cpp">
|
<ClCompile Include="ResourceString.cpp">
|
||||||
<DependentUpon>ResourceString.idl</DependentUpon>
|
<DependentUpon>ResourceString.idl</DependentUpon>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -176,6 +182,7 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Midl Include="Converters.idl" />
|
<Midl Include="Converters.idl" />
|
||||||
<Midl Include="IconPathConverter.idl" />
|
<Midl Include="IconPathConverter.idl" />
|
||||||
|
<Midl Include="RunHistory.idl" />
|
||||||
<Midl Include="IDirectKeyListener.idl" />
|
<Midl Include="IDirectKeyListener.idl" />
|
||||||
<Midl Include="ResourceString.idl" />
|
<Midl Include="ResourceString.idl" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
87
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.cpp
Normal file
87
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.cpp
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
#include "pch.h"
|
||||||
|
#include "RunHistory.h"
|
||||||
|
#include "RunHistory.g.cpp"
|
||||||
|
|
||||||
|
|
||||||
|
using namespace winrt::Windows;
|
||||||
|
|
||||||
|
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||||
|
{
|
||||||
|
// Run history
|
||||||
|
// Largely copied from the Run work circa 2022.
|
||||||
|
|
||||||
|
winrt::Windows::Foundation::Collections::IVector<hstring> RunHistory::CreateRunHistory()
|
||||||
|
{
|
||||||
|
// Load MRU history
|
||||||
|
std::vector<hstring> history;
|
||||||
|
|
||||||
|
wil::unique_hmodule _comctl;
|
||||||
|
HANDLE(WINAPI* _createMRUList)(MRUINFO* lpmi);
|
||||||
|
int(WINAPI* _enumMRUList)(HANDLE hMRU,int nItem,void* lpData,UINT uLen);
|
||||||
|
void(WINAPI *_freeMRUList)(HANDLE hMRU);
|
||||||
|
int(WINAPI *_addMRUString)(HANDLE hMRU, LPCWSTR szString);
|
||||||
|
|
||||||
|
// Lazy load comctl32.dll
|
||||||
|
// Theoretically, we could cache this into a magic static, but we shouldn't need to actually do this more than once in CmdPal
|
||||||
|
_comctl.reset(LoadLibraryExW(L"comctl32.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
|
||||||
|
|
||||||
|
_createMRUList = reinterpret_cast<decltype(_createMRUList)>(GetProcAddress(_comctl.get(), "CreateMRUListW"));
|
||||||
|
FAIL_FAST_LAST_ERROR_IF(!_createMRUList);
|
||||||
|
|
||||||
|
_enumMRUList = reinterpret_cast<decltype(_enumMRUList)>(GetProcAddress(_comctl.get(), "EnumMRUListW"));
|
||||||
|
FAIL_FAST_LAST_ERROR_IF(!_enumMRUList);
|
||||||
|
|
||||||
|
_freeMRUList = reinterpret_cast<decltype(_freeMRUList)>(GetProcAddress(_comctl.get(), "FreeMRUList"));
|
||||||
|
FAIL_FAST_LAST_ERROR_IF(!_freeMRUList);
|
||||||
|
|
||||||
|
_addMRUString = reinterpret_cast<decltype(_addMRUString)>(GetProcAddress(_comctl.get(), "AddMRUStringW"));
|
||||||
|
FAIL_FAST_LAST_ERROR_IF(!_addMRUString);
|
||||||
|
|
||||||
|
static const WCHAR c_szRunMRU[] = REGSTR_PATH_EXPLORER L"\\RunMRU";
|
||||||
|
MRUINFO mi = {
|
||||||
|
sizeof(mi),
|
||||||
|
26,
|
||||||
|
MRU_CACHEWRITE,
|
||||||
|
HKEY_CURRENT_USER,
|
||||||
|
c_szRunMRU,
|
||||||
|
NULL // NOTE: use default string compare
|
||||||
|
// since this is a GLOBAL MRU
|
||||||
|
};
|
||||||
|
|
||||||
|
if (const auto hMruList = _createMRUList(&mi))
|
||||||
|
{
|
||||||
|
auto freeMRUList = wil::scope_exit([=]() {
|
||||||
|
_freeMRUList(hMruList);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (int nMax = _enumMRUList(hMruList, -1, NULL, 0), i = 0; i < nMax; ++i)
|
||||||
|
{
|
||||||
|
WCHAR szCommand[MAX_PATH + 2];
|
||||||
|
|
||||||
|
const auto length = _enumMRUList(hMruList, i, szCommand, ARRAYSIZE(szCommand));
|
||||||
|
if (length > 1)
|
||||||
|
{
|
||||||
|
// clip off the null-terminator
|
||||||
|
std::wstring_view text{ szCommand, wil::safe_cast<size_t>(length - 1) };
|
||||||
|
//#pragma disable warning(C26493)
|
||||||
|
#pragma warning( push )
|
||||||
|
#pragma warning( disable : 26493 )
|
||||||
|
if (text.back() == L'\\')
|
||||||
|
{
|
||||||
|
// old MRU format has a slash at the end with the show cmd
|
||||||
|
text = { szCommand, wil::safe_cast<size_t>(length - 2) };
|
||||||
|
#pragma warning( pop )
|
||||||
|
if (text.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
history.emplace_back(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update dropdown & initial value
|
||||||
|
return winrt::single_threaded_observable_vector<winrt::hstring>(std::move(history));
|
||||||
|
}
|
||||||
|
}
|
||||||
21
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.h
Normal file
21
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.h
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "RunHistory.g.h"
|
||||||
|
#include "types.h"
|
||||||
|
|
||||||
|
namespace winrt::Microsoft::Terminal::UI::implementation
|
||||||
|
{
|
||||||
|
struct RunHistory
|
||||||
|
{
|
||||||
|
RunHistory() = default;
|
||||||
|
static winrt::Windows::Foundation::Collections::IVector<hstring> CreateRunHistory();
|
||||||
|
|
||||||
|
private:
|
||||||
|
winrt::Windows::Foundation::Collections::IVector<hstring> _mruHistory;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace winrt::Microsoft::Terminal::UI::factory_implementation
|
||||||
|
{
|
||||||
|
BASIC_FACTORY(RunHistory);
|
||||||
|
}
|
||||||
11
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.idl
Normal file
11
src/modules/cmdpal/Microsoft.Terminal.UI/RunHistory.idl
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
namespace Microsoft.Terminal.UI
|
||||||
|
{
|
||||||
|
static runtimeclass RunHistory
|
||||||
|
{
|
||||||
|
static Windows.Foundation.Collections.IVector<String> CreateRunHistory();
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
@@ -64,6 +64,8 @@
|
|||||||
|
|
||||||
// WIL
|
// WIL
|
||||||
#include <wil/com.h>
|
#include <wil/com.h>
|
||||||
|
#include <wil/resource.h>
|
||||||
|
#include <wil/safecast.h>
|
||||||
#include <wil/stl.h>
|
#include <wil/stl.h>
|
||||||
#include <wil/filesystem.h>
|
#include <wil/filesystem.h>
|
||||||
// Due to the use of RESOURCE_SUPPRESS_STL in result.h, we need to include resource.h first, which happens
|
// Due to the use of RESOURCE_SUPPRESS_STL in result.h, we need to include resource.h first, which happens
|
||||||
@@ -90,6 +92,7 @@
|
|||||||
|
|
||||||
#include <winrt/Windows.ApplicationModel.Resources.h>
|
#include <winrt/Windows.ApplicationModel.Resources.h>
|
||||||
#include <winrt/Windows.Foundation.h>
|
#include <winrt/Windows.Foundation.h>
|
||||||
|
#include <winrt/Windows.Foundation.Collections.h>
|
||||||
|
|
||||||
#include <winrt/Windows.Graphics.Imaging.h>
|
#include <winrt/Windows.Graphics.Imaging.h>
|
||||||
#include <Windows.Graphics.Imaging.Interop.h>
|
#include <Windows.Graphics.Imaging.Interop.h>
|
||||||
|
|||||||
23
src/modules/cmdpal/Microsoft.Terminal.UI/types.h
Normal file
23
src/modules/cmdpal/Microsoft.Terminal.UI/types.h
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
// Copyright (c) Microsoft Corporation.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#define MRU_CACHEWRITE 0x0002
|
||||||
|
#define REGSTR_PATH_EXPLORER TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer")
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/shell/mrucmpproc
|
||||||
|
typedef int(CALLBACK* MRUCMPPROC)(
|
||||||
|
LPCTSTR pString1,
|
||||||
|
LPCTSTR pString2);
|
||||||
|
|
||||||
|
// https://learn.microsoft.com/en-us/windows/win32/shell/mruinfo
|
||||||
|
struct MRUINFO
|
||||||
|
{
|
||||||
|
DWORD cbSize;
|
||||||
|
UINT uMax;
|
||||||
|
UINT fFlags;
|
||||||
|
HKEY hKey;
|
||||||
|
LPCTSTR lpszSubKey;
|
||||||
|
MRUCMPPROC lpfnCompare;
|
||||||
|
};
|
||||||
@@ -15,10 +15,11 @@ namespace Microsoft.CmdPal.Ext.Shell;
|
|||||||
|
|
||||||
internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDisposable
|
internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDisposable
|
||||||
{
|
{
|
||||||
|
private readonly Action<string>? _addToHistory;
|
||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
private Task? _currentUpdateTask;
|
private Task? _currentUpdateTask;
|
||||||
|
|
||||||
public FallbackExecuteItem(SettingsManager settings)
|
public FallbackExecuteItem(SettingsManager settings, Action<string>? addToHistory)
|
||||||
: base(
|
: base(
|
||||||
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
new NoOpCommand() { Id = "com.microsoft.run.fallback" },
|
||||||
Resources.shell_command_display_title)
|
Resources.shell_command_display_title)
|
||||||
@@ -26,6 +27,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
Title = string.Empty;
|
Title = string.Empty;
|
||||||
Subtitle = Properties.Resources.generic_run_command;
|
Subtitle = Properties.Resources.generic_run_command;
|
||||||
Icon = Icons.RunV2Icon; // Defined in Icons.cs and contains the execute command icon.
|
Icon = Icons.RunV2Icon; // Defined in Icons.cs and contains the execute command icon.
|
||||||
|
_addToHistory = addToHistory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void UpdateQuery(string query)
|
public override void UpdateQuery(string query)
|
||||||
@@ -142,7 +144,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
if (exeExists)
|
if (exeExists)
|
||||||
{
|
{
|
||||||
// TODO we need to probably get rid of the settings for this provider entirely
|
// 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;
|
Title = exeItem.Title;
|
||||||
Subtitle = exeItem.Subtitle;
|
Subtitle = exeItem.Subtitle;
|
||||||
Icon = exeItem.Icon;
|
Icon = exeItem.Icon;
|
||||||
@@ -151,7 +153,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
}
|
}
|
||||||
else if (pathIsDir)
|
else if (pathIsDir)
|
||||||
{
|
{
|
||||||
var pathItem = new PathListItem(exe, query);
|
var pathItem = new PathListItem(exe, query, _addToHistory);
|
||||||
Title = pathItem.Title;
|
Title = pathItem.Title;
|
||||||
Subtitle = pathItem.Subtitle;
|
Subtitle = pathItem.Subtitle;
|
||||||
Icon = pathItem.Icon;
|
Icon = pathItem.Icon;
|
||||||
@@ -160,7 +162,7 @@ internal sealed partial class FallbackExecuteItem : FallbackCommandItem, IDispos
|
|||||||
}
|
}
|
||||||
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
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;
|
Title = searchText;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|||||||
@@ -7,7 +7,9 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Commands;
|
using Microsoft.CmdPal.Ext.Shell.Commands;
|
||||||
|
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||||
using Microsoft.CommandPalette.Extensions.Toolkit;
|
using Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
namespace Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
@@ -94,4 +96,80 @@ public class ShellListPageHelpers
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static ListItem? ListItemForCommandString(string query, Action<string>? addToHistory)
|
||||||
|
{
|
||||||
|
var li = new ListItem();
|
||||||
|
|
||||||
|
var searchText = query.Trim();
|
||||||
|
var expanded = Environment.ExpandEnvironmentVariables(searchText);
|
||||||
|
searchText = expanded;
|
||||||
|
if (string.IsNullOrEmpty(searchText) || string.IsNullOrWhiteSpace(searchText))
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShellListPage.ParseExecutableAndArgs(searchText, out var exe, out var args);
|
||||||
|
|
||||||
|
var exeExists = false;
|
||||||
|
var pathIsDir = false;
|
||||||
|
var fullExePath = string.Empty;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(200));
|
||||||
|
|
||||||
|
// Use Task.Run with timeout - this will actually timeout even if the sync operations don't respond to cancellation
|
||||||
|
var pathResolutionTask = Task.Run(
|
||||||
|
() =>
|
||||||
|
{
|
||||||
|
// Don't check cancellation token here - let the Task timeout handle it
|
||||||
|
exeExists = ShellListPageHelpers.FileExistInPath(exe, out fullExePath);
|
||||||
|
pathIsDir = Directory.Exists(expanded);
|
||||||
|
},
|
||||||
|
CancellationToken.None); // Use None here since we're handling timeout differently
|
||||||
|
|
||||||
|
// Wait for either completion or timeout
|
||||||
|
pathResolutionTask.Wait(cts.Token);
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exeExists)
|
||||||
|
{
|
||||||
|
// TODO we need to probably get rid of the settings for this provider entirely
|
||||||
|
var exeItem = ShellListPage.CreateExeItem(exe, args, fullExePath, addToHistory);
|
||||||
|
li.Command = exeItem.Command;
|
||||||
|
li.Title = exeItem.Title;
|
||||||
|
li.Subtitle = exeItem.Subtitle;
|
||||||
|
li.Icon = exeItem.Icon;
|
||||||
|
li.MoreCommands = exeItem.MoreCommands;
|
||||||
|
}
|
||||||
|
else if (pathIsDir)
|
||||||
|
{
|
||||||
|
var pathItem = new PathListItem(exe, query, addToHistory);
|
||||||
|
li.Command = pathItem.Command;
|
||||||
|
li.Title = pathItem.Title;
|
||||||
|
li.Subtitle = pathItem.Subtitle;
|
||||||
|
li.Icon = pathItem.Icon;
|
||||||
|
li.MoreCommands = pathItem.MoreCommands;
|
||||||
|
}
|
||||||
|
else if (System.Uri.TryCreate(searchText, UriKind.Absolute, out var uri))
|
||||||
|
{
|
||||||
|
li.Command = new OpenUrlWithHistoryCommand(searchText) { Result = CommandResult.Dismiss() };
|
||||||
|
li.Title = searchText;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (li != null)
|
||||||
|
{
|
||||||
|
li.TextToSuggest = searchText;
|
||||||
|
}
|
||||||
|
|
||||||
|
return li;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
<ProjectReference Include="..\..\extensionsdk\Microsoft.CommandPalette.Extensions.Toolkit\Microsoft.CommandPalette.Extensions.Toolkit.csproj" />
|
||||||
|
<ProjectReference Include="..\..\Microsoft.CmdPal.Common\Microsoft.CmdPal.Common.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="System.CommandLine" />
|
<PackageReference Include="System.CommandLine" />
|
||||||
|
|||||||
@@ -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<string>? _addToHistory;
|
||||||
|
private readonly string _url;
|
||||||
|
|
||||||
|
public OpenUrlWithHistoryCommand(string url, Action<string>? addToHistory = null)
|
||||||
|
: base(url)
|
||||||
|
{
|
||||||
|
_addToHistory = addToHistory;
|
||||||
|
_url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override CommandResult Invoke()
|
||||||
|
{
|
||||||
|
_addToHistory?.Invoke(_url);
|
||||||
|
var result = base.Invoke();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,7 @@ namespace Microsoft.CmdPal.Ext.Shell.Pages;
|
|||||||
internal sealed partial class RunExeItem : ListItem
|
internal sealed partial class RunExeItem : ListItem
|
||||||
{
|
{
|
||||||
private readonly Lazy<IconInfo> _icon;
|
private readonly Lazy<IconInfo> _icon;
|
||||||
|
private readonly Action<string>? _addToHistory;
|
||||||
|
|
||||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||||
|
|
||||||
@@ -22,7 +23,9 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
|
|
||||||
private string _args = string.Empty;
|
private string _args = string.Empty;
|
||||||
|
|
||||||
public RunExeItem(string exe, string args, string fullExePath)
|
private string FullString => string.IsNullOrEmpty(_args) ? Exe : $"{Exe} {_args}";
|
||||||
|
|
||||||
|
public RunExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
||||||
{
|
{
|
||||||
FullExePath = fullExePath;
|
FullExePath = fullExePath;
|
||||||
Exe = exe;
|
Exe = exe;
|
||||||
@@ -41,6 +44,8 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
return t.Result;
|
return t.Result;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
_addToHistory = addToHistory;
|
||||||
|
|
||||||
UpdateArgs(args);
|
UpdateArgs(args);
|
||||||
|
|
||||||
MoreCommands = [
|
MoreCommands = [
|
||||||
@@ -89,16 +94,22 @@ internal sealed partial class RunExeItem : ListItem
|
|||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
{
|
{
|
||||||
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args);
|
ShellHelpers.OpenInShell(FullExePath, _args);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunAsAdmin()
|
public void RunAsAdmin()
|
||||||
{
|
{
|
||||||
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.Administrator);
|
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.Administrator);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void RunAsOther()
|
public void RunAsOther()
|
||||||
{
|
{
|
||||||
|
_addToHistory?.Invoke(FullString);
|
||||||
|
|
||||||
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.OtherUser);
|
ShellHelpers.OpenInShell(FullExePath, _args, runAs: ShellHelpers.ShellRunAsType.OtherUser);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using System.IO;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.CmdPal.Common.Services;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||||
using Microsoft.CommandPalette.Extensions;
|
using Microsoft.CommandPalette.Extensions;
|
||||||
@@ -20,7 +21,11 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
private readonly ShellListPageHelpers _helper;
|
private readonly ShellListPageHelpers _helper;
|
||||||
|
|
||||||
private readonly List<ListItem> _topLevelItems = [];
|
private readonly List<ListItem> _topLevelItems = [];
|
||||||
private readonly List<ListItem> _historyItems = [];
|
private readonly Dictionary<string, ListItem> _historyItems = [];
|
||||||
|
private readonly List<ListItem> _currentHistoryItems = [];
|
||||||
|
|
||||||
|
private readonly IRunHistoryService _historyService;
|
||||||
|
|
||||||
private RunExeItem? _exeItem;
|
private RunExeItem? _exeItem;
|
||||||
private List<ListItem> _pathItems = [];
|
private List<ListItem> _pathItems = [];
|
||||||
private ListItem? _uriItem;
|
private ListItem? _uriItem;
|
||||||
@@ -28,13 +33,16 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
private CancellationTokenSource? _cancellationTokenSource;
|
private CancellationTokenSource? _cancellationTokenSource;
|
||||||
private Task? _currentSearchTask;
|
private Task? _currentSearchTask;
|
||||||
|
|
||||||
public ShellListPage(SettingsManager settingsManager, bool addBuiltins = false)
|
private bool _loadedInitialHistory;
|
||||||
|
|
||||||
|
public ShellListPage(SettingsManager settingsManager, IRunHistoryService runHistoryService, bool addBuiltins = false)
|
||||||
{
|
{
|
||||||
Icon = Icons.RunV2Icon;
|
Icon = Icons.RunV2Icon;
|
||||||
Id = "com.microsoft.cmdpal.shell";
|
Id = "com.microsoft.cmdpal.shell";
|
||||||
Name = Resources.cmd_plugin_name;
|
Name = Resources.cmd_plugin_name;
|
||||||
PlaceholderText = Resources.list_placeholder_text;
|
PlaceholderText = Resources.list_placeholder_text;
|
||||||
_helper = new(settingsManager);
|
_helper = new(settingsManager);
|
||||||
|
_historyService = runHistoryService;
|
||||||
|
|
||||||
EmptyContent = new CommandItem()
|
EmptyContent = new CommandItem()
|
||||||
{
|
{
|
||||||
@@ -68,8 +76,6 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
_cancellationTokenSource = new CancellationTokenSource();
|
_cancellationTokenSource = new CancellationTokenSource();
|
||||||
var cancellationToken = _cancellationTokenSource.Token;
|
var cancellationToken = _cancellationTokenSource.Token;
|
||||||
|
|
||||||
IsLoading = true;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Save the latest search task
|
// Save the latest search task
|
||||||
@@ -139,6 +145,10 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
_pathItems.Clear();
|
_pathItems.Clear();
|
||||||
_exeItem = null;
|
_exeItem = null;
|
||||||
_uriItem = null;
|
_uriItem = null;
|
||||||
|
|
||||||
|
_currentHistoryItems.Clear();
|
||||||
|
_currentHistoryItems.AddRange(_historyItems.Values);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,6 +216,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
&& (!exeExists || pathIsDir)
|
&& (!exeExists || pathIsDir)
|
||||||
&& couldResolvePath)
|
&& couldResolvePath)
|
||||||
{
|
{
|
||||||
|
IsLoading = true;
|
||||||
await CreatePathItemsAsync(expanded, searchText, cancellationToken);
|
await CreatePathItemsAsync(expanded, searchText, cancellationToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,18 +242,53 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
_uriItem = null;
|
_uriItem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var histItemsNotInSearch =
|
||||||
|
_historyItems
|
||||||
|
.Where(kv => !kv.Key.Equals(newSearch, StringComparison.OrdinalIgnoreCase));
|
||||||
|
if (_exeItem != null)
|
||||||
|
{
|
||||||
|
// If we have an exe item, we want to remove it from the history items
|
||||||
|
histItemsNotInSearch = histItemsNotInSearch
|
||||||
|
.Where(kv => !kv.Value.Title.Equals(_exeItem.Title, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_uriItem != null)
|
||||||
|
{
|
||||||
|
// If we have an uri item, we want to remove it from the history items
|
||||||
|
histItemsNotInSearch = histItemsNotInSearch
|
||||||
|
.Where(kv => !kv.Value.Title.Equals(_uriItem.Title, StringComparison.OrdinalIgnoreCase));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter the history items based on the search text
|
||||||
|
var filterHistory = (string query, KeyValuePair<string, ListItem> pair) =>
|
||||||
|
{
|
||||||
|
// Fuzzy search on the key (command string)
|
||||||
|
var score = StringMatcher.FuzzySearch(query, pair.Key).Score;
|
||||||
|
return score;
|
||||||
|
};
|
||||||
|
|
||||||
|
var filteredHistory =
|
||||||
|
ListHelpers.FilterList<KeyValuePair<string, ListItem>>(
|
||||||
|
histItemsNotInSearch,
|
||||||
|
searchText,
|
||||||
|
filterHistory)
|
||||||
|
.Select(p => p.Value);
|
||||||
|
|
||||||
|
_currentHistoryItems.Clear();
|
||||||
|
_currentHistoryItems.AddRange(filteredHistory);
|
||||||
|
|
||||||
// Final cancellation check
|
// Final cancellation check
|
||||||
cancellationToken.ThrowIfCancellationRequested();
|
cancellationToken.ThrowIfCancellationRequested();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static ListItem PathToListItem(string path, string originalPath, string args = "")
|
private static ListItem PathToListItem(string path, string originalPath, string args = "", Action<string>? 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
|
// Is this path an executable? If so, then make a RunExeItem
|
||||||
if (IsExecutable(path))
|
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 = [
|
||||||
.. exeItem.MoreCommands,
|
.. exeItem.MoreCommands,
|
||||||
@@ -255,24 +301,30 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
|
|
||||||
public override IListItem[] GetItems()
|
public override IListItem[] GetItems()
|
||||||
{
|
{
|
||||||
|
if (!_loadedInitialHistory)
|
||||||
|
{
|
||||||
|
LoadInitialHistory();
|
||||||
|
}
|
||||||
|
|
||||||
var filteredTopLevel = ListHelpers.FilterList(_topLevelItems, SearchText);
|
var filteredTopLevel = ListHelpers.FilterList(_topLevelItems, SearchText);
|
||||||
List<ListItem> uriItems = _uriItem != null ? [_uriItem] : [];
|
List<ListItem> uriItems = _uriItem != null ? [_uriItem] : [];
|
||||||
List<ListItem> exeItems = _exeItem != null ? [_exeItem] : [];
|
List<ListItem> exeItems = _exeItem != null ? [_exeItem] : [];
|
||||||
|
|
||||||
return
|
return
|
||||||
exeItems
|
exeItems
|
||||||
.Concat(filteredTopLevel)
|
.Concat(filteredTopLevel)
|
||||||
.Concat(_historyItems)
|
.Concat(_currentHistoryItems)
|
||||||
.Concat(_pathItems)
|
.Concat(_pathItems)
|
||||||
.Concat(uriItems)
|
.Concat(uriItems)
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static RunExeItem CreateExeItem(string exe, string args, string fullExePath)
|
internal static RunExeItem CreateExeItem(string exe, string args, string fullExePath, Action<string>? addToHistory)
|
||||||
{
|
{
|
||||||
// PathToListItem will return a RunExeItem if it can find a executable.
|
// PathToListItem will return a RunExeItem if it can find a executable.
|
||||||
// It will ALSO add the file search commands to the RunExeItem.
|
// It will ALSO add the file search commands to the RunExeItem.
|
||||||
return PathToListItem(fullExePath, exe, args) as RunExeItem ??
|
return PathToListItem(fullExePath, exe, args, addToHistory) as RunExeItem ??
|
||||||
new RunExeItem(exe, args, fullExePath);
|
new RunExeItem(exe, args, fullExePath, addToHistory);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CreateAndAddExeItems(string exe, string args, string fullExePath)
|
private void CreateAndAddExeItems(string exe, string args, string fullExePath)
|
||||||
@@ -284,7 +336,7 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_exeItem = CreateExeItem(exe, args, fullExePath);
|
_exeItem = CreateExeItem(exe, args, fullExePath, AddToHistory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -442,6 +494,40 @@ internal sealed partial class ShellListPage : DynamicListPage, IDisposable
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LoadInitialHistory()
|
||||||
|
{
|
||||||
|
var hist = _historyService.GetRunHistory();
|
||||||
|
var histItems = hist
|
||||||
|
.Select(h => (h, ShellListPageHelpers.ListItemForCommandString(h, AddToHistory)))
|
||||||
|
.Where(tuple => tuple.Item2 != null)
|
||||||
|
.Select(tuple => (tuple.h, tuple.Item2!))
|
||||||
|
.ToList();
|
||||||
|
_historyItems.Clear();
|
||||||
|
|
||||||
|
// Add all the history items to the _historyItems dictionary
|
||||||
|
foreach (var (h, item) in histItems)
|
||||||
|
{
|
||||||
|
_historyItems[h] = item;
|
||||||
|
}
|
||||||
|
|
||||||
|
_currentHistoryItems.Clear();
|
||||||
|
_currentHistoryItems.AddRange(histItems.Select(tuple => tuple.Item2));
|
||||||
|
|
||||||
|
_loadedInitialHistory = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void AddToHistory(string commandString)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(commandString))
|
||||||
|
{
|
||||||
|
return; // Do not add empty or whitespace items
|
||||||
|
}
|
||||||
|
|
||||||
|
_historyService.AddRunHistoryItem(commandString);
|
||||||
|
LoadInitialHistory();
|
||||||
|
DoUpdateSearchText(SearchText);
|
||||||
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_cancellationTokenSource?.Cancel();
|
_cancellationTokenSource?.Cancel();
|
||||||
|
|||||||
@@ -16,8 +16,8 @@ internal sealed partial class PathListItem : ListItem
|
|||||||
|
|
||||||
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
public override IIconInfo? Icon { get => _icon.Value; set => base.Icon = value; }
|
||||||
|
|
||||||
public PathListItem(string path, string originalDir)
|
public PathListItem(string path, string originalDir, Action<string>? addToHistory)
|
||||||
: base(new OpenUrlCommand(path))
|
: base(new OpenUrlWithHistoryCommand(path, addToHistory))
|
||||||
{
|
{
|
||||||
var fileName = Path.GetFileName(path);
|
var fileName = Path.GetFileName(path);
|
||||||
_isDirectory = Directory.Exists(path);
|
_isDirectory = Directory.Exists(path);
|
||||||
@@ -50,6 +50,7 @@ internal sealed partial class PathListItem : ListItem
|
|||||||
new CommandContextItem(new CopyTextCommand(path) { Name = Properties.Resources.copy_path_command_name }) { }
|
new CommandContextItem(new CopyTextCommand(path) { Name = Properties.Resources.copy_path_command_name }) { }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// TODO: Follow-up during 0.4. Add the indexer commands here.
|
||||||
// MoreCommands = [
|
// MoreCommands = [
|
||||||
// new CommandContextItem(new OpenWithCommand(indexerItem)),
|
// new CommandContextItem(new OpenWithCommand(indexerItem)),
|
||||||
// new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
// new CommandContextItem(new ShowFileInFolderCommand(indexerItem.FullPath) { Name = Resources.Indexer_Command_ShowInFolder }),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// The Microsoft Corporation licenses this file to you under the MIT license.
|
// The Microsoft Corporation licenses this file to you under the MIT license.
|
||||||
// See the LICENSE file in the project root for more information.
|
// See the LICENSE file in the project root for more information.
|
||||||
|
|
||||||
|
using Microsoft.CmdPal.Common.Services;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
using Microsoft.CmdPal.Ext.Shell.Helpers;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Pages;
|
using Microsoft.CmdPal.Ext.Shell.Pages;
|
||||||
using Microsoft.CmdPal.Ext.Shell.Properties;
|
using Microsoft.CmdPal.Ext.Shell.Properties;
|
||||||
@@ -15,18 +16,24 @@ public partial class ShellCommandsProvider : CommandProvider
|
|||||||
private readonly CommandItem _shellPageItem;
|
private readonly CommandItem _shellPageItem;
|
||||||
|
|
||||||
private readonly SettingsManager _settingsManager = new();
|
private readonly SettingsManager _settingsManager = new();
|
||||||
|
private readonly ShellListPage _shellListPage;
|
||||||
private readonly FallbackCommandItem _fallbackItem;
|
private readonly FallbackCommandItem _fallbackItem;
|
||||||
|
private readonly IRunHistoryService _historyService;
|
||||||
|
|
||||||
public ShellCommandsProvider()
|
public ShellCommandsProvider(IRunHistoryService runHistoryService)
|
||||||
{
|
{
|
||||||
|
_historyService = runHistoryService;
|
||||||
|
|
||||||
Id = "Run";
|
Id = "Run";
|
||||||
DisplayName = Resources.cmd_plugin_name;
|
DisplayName = Resources.cmd_plugin_name;
|
||||||
Icon = Icons.RunV2Icon;
|
Icon = Icons.RunV2Icon;
|
||||||
Settings = _settingsManager.Settings;
|
Settings = _settingsManager.Settings;
|
||||||
|
|
||||||
_fallbackItem = new FallbackExecuteItem(_settingsManager);
|
_shellListPage = new ShellListPage(_settingsManager, _historyService);
|
||||||
|
|
||||||
_shellPageItem = new CommandItem(new ShellListPage(_settingsManager))
|
_fallbackItem = new FallbackExecuteItem(_settingsManager, _shellListPage.AddToHistory);
|
||||||
|
|
||||||
|
_shellPageItem = new CommandItem(_shellListPage)
|
||||||
{
|
{
|
||||||
Icon = Icons.RunV2Icon,
|
Icon = Icons.RunV2Icon,
|
||||||
Title = Resources.shell_command_name,
|
Title = Resources.shell_command_name,
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ public partial class ListHelpers
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
|
public static IEnumerable<T> FilterList<T>(IEnumerable<T> items, string query, Func<string, T, int> scoreFunction)
|
||||||
where T : class
|
|
||||||
{
|
{
|
||||||
var scores = items
|
var scores = items
|
||||||
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
|
.Select(li => new Scored<T>() { Item = li, Score = scoreFunction(query, li) })
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
namespace Microsoft.CommandPalette.Extensions.Toolkit;
|
||||||
|
|
||||||
public sealed partial class OpenUrlCommand : InvokableCommand
|
public partial class OpenUrlCommand : InvokableCommand
|
||||||
{
|
{
|
||||||
private readonly string _target;
|
private readonly string _target;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user