From 7112f4e0819973fb512993d964742b70f86de1d5 Mon Sep 17 00:00:00 2001 From: Don-Vito Date: Thu, 26 Aug 2021 22:04:35 +0300 Subject: [PATCH] Teach CommandPalette to persist recent command lines (#11030) Closes #11026 --- src/cascadia/TerminalApp/CommandPalette.cpp | 60 +++++++++++++++---- src/cascadia/TerminalApp/CommandPalette.h | 7 ++- .../ApplicationState.cpp | 42 +++++++++++++ .../TerminalSettingsModel/ApplicationState.h | 5 +- .../ApplicationState.idl | 2 + .../TerminalSettingsModel/JsonUtils.h | 4 +- 6 files changed, 102 insertions(+), 18 deletions(-) diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index bd6badf65..31ec712b9 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -36,7 +36,6 @@ namespace winrt::TerminalApp::implementation _allCommands = winrt::single_threaded_vector(); _tabActions = winrt::single_threaded_vector(); _mruTabActions = winrt::single_threaded_vector(); - _commandLineHistory = winrt::single_threaded_vector(); _switchToMode(CommandPaletteMode::ActionMode); @@ -587,7 +586,7 @@ namespace winrt::TerminalApp::implementation case CommandPaletteMode::TabSwitchMode: return _tabSwitcherMode == TabSwitcherMode::MostRecentlyUsed ? _mruTabActions : _tabActions; case CommandPaletteMode::CommandlineMode: - return _commandLineHistory; + return _loadRecentCommands(); default: return _allCommands; } @@ -720,14 +719,10 @@ namespace winrt::TerminalApp::implementation // - void CommandPalette::_dispatchCommandline(winrt::TerminalApp::FilteredCommand const& command) { - const auto filteredCommand = command ? command : _buildCommandLineCommand(_getTrimmedInput()); + const auto filteredCommand = command ? command : _buildCommandLineCommand(winrt::hstring(_getTrimmedInput())); if (filteredCommand.has_value()) { - if (_commandLineHistory.Size() == CommandLineHistoryLength) - { - _commandLineHistory.RemoveAtEnd(); - } - _commandLineHistory.InsertAt(0, filteredCommand.value()); + _updateRecentCommands(filteredCommand.value().Item().Name()); TraceLoggingWrite( g_hTerminalAppProvider, // handle to TerminalApp tracelogging provider @@ -744,15 +739,14 @@ namespace winrt::TerminalApp::implementation } } - std::optional CommandPalette::_buildCommandLineCommand(std::wstring const& commandLine) + std::optional CommandPalette::_buildCommandLineCommand(const hstring& commandLine) { if (commandLine.empty()) { return std::nullopt; } - winrt::hstring cl{ commandLine }; - auto commandLinePaletteItem{ winrt::make(cl) }; + auto commandLinePaletteItem{ winrt::make(commandLine) }; return winrt::make(commandLinePaletteItem); } @@ -1217,4 +1211,48 @@ namespace winrt::TerminalApp::implementation itemContainer.DataContext(args.Item()); } } + + // Method Description: + // - Reads the list of recent commands from the persistent application state + // Return Value: + // - The list of FilteredCommand representing the ones stored in the state + IVector CommandPalette::_loadRecentCommands() + { + const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); + std::vector parsedCommands; + parsedCommands.reserve(std::min(recentCommands.Size(), CommandLineHistoryLength)); + + for (const auto& c : recentCommands) + { + if (parsedCommands.size() >= CommandLineHistoryLength) + { + // Don't load more than CommandLineHistoryLength commands + break; + } + + if (const auto parsedCommand = _buildCommandLineCommand(c)) + { + parsedCommands.push_back(*parsedCommand); + } + } + return single_threaded_vector(std::move(parsedCommands)); + } + + // Method Description: + // - Update recent commands by putting the provided command as most recent. + // Upon race condition might override an update made by another window. + // Return Value: + // - + void CommandPalette::_updateRecentCommands(const hstring& command) + { + const auto recentCommands = ApplicationState::SharedInstance().RecentCommands(); + const auto countToCopy = std::min(recentCommands.Size(), CommandLineHistoryLength - 1); + std::vector newRecentCommands{ countToCopy + 1 }; + til::at(newRecentCommands, 0) = command; + if (countToCopy) + { + recentCommands.GetMany(0, { newRecentCommands.data() + 1, countToCopy }); + } + ApplicationState::SharedInstance().RecentCommands(single_threaded_vector(std::move(newRecentCommands))); + } } diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index af065d67d..29838eee8 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -123,15 +123,16 @@ namespace winrt::TerminalApp::implementation void _dispatchCommand(winrt::TerminalApp::FilteredCommand const& command); void _dispatchCommandline(winrt::TerminalApp::FilteredCommand const& command); void _switchToTab(winrt::TerminalApp::FilteredCommand const& command); - std::optional _buildCommandLineCommand(std::wstring const& commandLine); + static std::optional _buildCommandLineCommand(const winrt::hstring& commandLine); void _dismissPalette(); void _scrollToIndex(uint32_t index); uint32_t _getNumVisibleItems(); - static constexpr int CommandLineHistoryLength = 10; - Windows::Foundation::Collections::IVector _commandLineHistory{ nullptr }; + static constexpr uint32_t CommandLineHistoryLength = 20; + static Windows::Foundation::Collections::IVector _loadRecentCommands(); + static void _updateRecentCommands(const winrt::hstring& command); ::TerminalApp::AppCommandlineArgs _appArgs; void _choosingItemContainer(Windows::UI::Xaml::Controls::ListViewBase const& sender, Windows::UI::Xaml::Controls::ChoosingItemContainerEventArgs const& args); diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp index 0984a5330..f0f536ecf 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.cpp +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.cpp @@ -55,6 +55,48 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return fmt::format("{}[]", ConversionTrait{}.TypeDescription()); } }; + + template + struct ConversionTrait> + { + winrt::Windows::Foundation::Collections::IVector FromJson(const Json::Value& json) const + { + ConversionTrait trait; + std::vector val; + val.reserve(json.size()); + + for (const auto& element : json) + { + val.push_back(trait.FromJson(element)); + } + + return winrt::single_threaded_vector(move(val)); + } + + bool CanConvert(const Json::Value& json) const + { + ConversionTrait trait; + return json.isArray() && std::all_of(json.begin(), json.end(), [trait](const auto& json) -> bool { return trait.CanConvert(json); }); + } + + Json::Value ToJson(const winrt::Windows::Foundation::Collections::IVector& val) + { + ConversionTrait trait; + Json::Value json{ Json::arrayValue }; + + for (const auto& key : val) + { + json.append(trait.ToJson(key)); + } + + return json; + } + + std::string TypeDescription() const + { + return fmt::format("vector ({})", ConversionTrait{}.TypeDescription()); + } + }; } using namespace ::Microsoft::Terminal::Settings::Model; diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.h b/src/cascadia/TerminalSettingsModel/ApplicationState.h index 90320f0e2..3a9a1e8d7 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.h +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.h @@ -21,8 +21,9 @@ Abstract: // This macro generates all getters and setters for ApplicationState. // It provides X with the following arguments: // (type, function name, JSON key, ...variadic construction arguments) -#define MTSM_APPLICATION_STATE_FIELDS(X) \ - X(std::unordered_set, GeneratedProfiles, "generatedProfiles") +#define MTSM_APPLICATION_STATE_FIELDS(X) \ + X(std::unordered_set, GeneratedProfiles, "generatedProfiles") \ + X(Windows::Foundation::Collections::IVector, RecentCommands, "recentCommands") namespace winrt::Microsoft::Terminal::Settings::Model::implementation { diff --git a/src/cascadia/TerminalSettingsModel/ApplicationState.idl b/src/cascadia/TerminalSettingsModel/ApplicationState.idl index 8f5eed84e..972b3e55e 100644 --- a/src/cascadia/TerminalSettingsModel/ApplicationState.idl +++ b/src/cascadia/TerminalSettingsModel/ApplicationState.idl @@ -9,5 +9,7 @@ namespace Microsoft.Terminal.Settings.Model void Reload(); String FilePath { get; }; + + Windows.Foundation.Collections.IVector RecentCommands { get; set; }; } } diff --git a/src/cascadia/TerminalSettingsModel/JsonUtils.h b/src/cascadia/TerminalSettingsModel/JsonUtils.h index 220ec1df3..1bab27c36 100644 --- a/src/cascadia/TerminalSettingsModel/JsonUtils.h +++ b/src/cascadia/TerminalSettingsModel/JsonUtils.h @@ -161,7 +161,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return til::u8u16(Detail::GetStringView(json)); } - bool CanConvert(const Json::Value& json) + bool CanConvert(const Json::Value& json) const { return json.isString(); } @@ -252,7 +252,7 @@ namespace Microsoft::Terminal::Settings::Model::JsonUtils return til::u16u8(val); } - bool CanConvert(const Json::Value& json) + bool CanConvert(const Json::Value& json) const { // hstring has a specific behavior for null, so it can convert it return ConversionTrait::CanConvert(json) || json.isNull();