From e8dd48625c03e5074a9cf8170745b618d75bd869 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 17 Mar 2021 16:39:08 -0700 Subject: [PATCH 01/31] Give Command ownership over KeyChord --- src/cascadia/TerminalApp/TerminalPage.cpp | 12 +---- .../TerminalSettingsModel/Command.cpp | 11 ++++- src/cascadia/TerminalSettingsModel/Command.h | 4 +- .../TerminalSettingsModel/Command.idl | 3 +- .../KeyChordSerialization.cpp | 47 +++++++++++++++++-- .../KeyChordSerialization.h | 10 ++++ 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 16617c75818..fb2a726780a 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -75,17 +75,7 @@ namespace winrt::TerminalApp::implementation for (const auto& nameAndCmd : commands) { const auto& command = nameAndCmd.Value(); - // If there's a keybinding that's bound to exactly this command, - // then get the string for that keychord and display it as a - // part of the command in the UI. Each Command's KeyChordText is - // unset by default, so we don't need to worry about clearing it - // if there isn't a key associated with it. - auto keyChord{ settings.KeyMap().GetKeyBindingForActionWithArgs(command.Action()) }; - - if (keyChord) - { - command.KeyChordText(KeyChordSerialization::ToString(keyChord)); - } + if (command.HasNestedCommands()) { _recursiveUpdateCommandKeybindingLabels(settings, command.NestedCommands()); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 5d42d04743d..b7392ed008f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -6,7 +6,7 @@ #include "Command.g.cpp" #include "ActionAndArgs.h" -#include "JsonUtils.h" +#include "KeyChordSerialization.h" #include #include "TerminalSettingsSerializationHelpers.h" @@ -26,6 +26,7 @@ static constexpr std::string_view ActionKey{ "command" }; static constexpr std::string_view ArgsKey{ "args" }; static constexpr std::string_view IterateOnKey{ "iterateOn" }; static constexpr std::string_view CommandsKey{ "commands" }; +static constexpr std::string_view KeysKey{ "keys" }; static constexpr std::string_view ProfileNameToken{ "${profile.name}" }; static constexpr std::string_view ProfileIconToken{ "${profile.icon}" }; @@ -43,7 +44,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto command{ winrt::make_self() }; command->_Name = _Name; command->_Action = _Action; - command->_KeyChordText = _KeyChordText; + command->_Keys = _Keys; command->_IconPath = _IconPath; command->_IterateOn = _IterateOn; @@ -178,6 +179,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } JsonUtils::GetValueForKey(json, IconKey, result->_IconPath); + JsonUtils::GetValueForKey(json, KeysKey, result->_Keys); // If we're a nested command, we can ignore the current action. if (!nested) @@ -470,4 +472,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return newCommands; } + + hstring Command::KeyChordText() + { + return KeyChordSerialization::ToString(_Keys); + } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index e8f1ea6b849..48119158103 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -51,12 +51,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool HasNestedCommands() const; Windows::Foundation::Collections::IMapView NestedCommands() const; + hstring KeyChordText(); + winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker; WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(Model::ActionAndArgs, Action, _PropertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(Control::KeyChord, Keys, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers); WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index 0a2b8e3715e..bf65b9e659b 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -14,7 +14,8 @@ namespace Microsoft.Terminal.Settings.Model String Name; ActionAndArgs Action; - String KeyChordText; + Microsoft.Terminal.Control.KeyChord Keys; + String KeyChordText { get; }; String IconPath; diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index b240b8d2802..056354ffc4d 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -7,6 +7,7 @@ using namespace winrt::Microsoft::Terminal::Control; using namespace winrt::Microsoft::Terminal::Settings::Model::implementation; +using namespace Microsoft::Terminal::Settings::Model::JsonUtils; static constexpr std::wstring_view CTRL_KEY{ L"ctrl" }; static constexpr std::wstring_view SHIFT_KEY{ L"shift" }; @@ -104,10 +105,8 @@ static const std::unordered_map vkeyNamePairs { // - hstr: the string to parse into a keychord. // Return Value: // - a newly constructed KeyChord -KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) +static KeyChord _FromString(const std::wstring& wstr) { - std::wstring wstr{ hstr }; - // Split the string on '+' std::wstring temp; std::vector parts; @@ -220,7 +219,7 @@ KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) // names listed in the vkeyNamePairs vector above, or is one of 0-9a-z. // Return Value: // - a string which is an equivalent serialization of this object. -winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) +static std::wstring _ToString(const KeyChord& chord) { bool serializedSuccessfully = false; const auto modifiers = chord.Modifiers(); @@ -292,5 +291,43 @@ winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) buffer = L""; } - return winrt::hstring{ buffer }; + return buffer; +} + +KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) +{ + return _FromString(std::wstring{ hstr }); +} + +winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) +{ + return hstring{ _ToString(chord) }; +} + +KeyChord ConversionTrait::FromJson(const Json::Value& json) +{ + const std::string keyChordText{ json.asString() }; + try + { + return _FromString(til::u8u16(keyChordText)); + } + catch (winrt::hresult_invalid_argument) + { + return nullptr; + } +} + +bool ConversionTrait::CanConvert(const Json::Value& json) +{ + return json.isString(); +} + +Json::Value ConversionTrait::ToJson(const KeyChord& val) +{ + return til::u16u8(_ToString(val)); +} + +std::string ConversionTrait::TypeDescription() const +{ + return "key chord"; } diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.h b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.h index 5b795f89040..3f80c920496 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.h +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.h @@ -4,6 +4,7 @@ #pragma once #include "KeyChordSerialization.g.h" +#include "JsonUtils.h" #include "../inc/cppwinrt_utils.h" namespace winrt::Microsoft::Terminal::Settings::Model::implementation @@ -17,6 +18,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation }; } +template<> +struct Microsoft::Terminal::Settings::Model::JsonUtils::ConversionTrait +{ + winrt::Microsoft::Terminal::Control::KeyChord FromJson(const Json::Value& json); + bool CanConvert(const Json::Value& json); + Json::Value ToJson(const winrt::Microsoft::Terminal::Control::KeyChord& val); + std::string TypeDescription() const; +}; + namespace winrt::Microsoft::Terminal::Settings::Model::factory_implementation { // C++/WinRT generates a constructor even though one is not specified in the IDL From 42c43ff2e9f6e347198376922bb189f0efbae410 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 18 Mar 2021 16:42:30 -0700 Subject: [PATCH 02/31] remove KeyChordText from Command --- .../TerminalApp/ActionPaletteItem.cpp | 4 +- .../TerminalSettingsEditor/Actions.cpp | 2 +- .../TerminalSettingsEditor/Actions.xaml | 43 +++++++++-------- .../TerminalSettingsEditor/Converters.idl | 10 ++++ .../KeyChordConverter.cpp | 48 +++++++++++++++++++ .../KeyChordConverter.h | 11 +++++ ...Microsoft.Terminal.Settings.Editor.vcxproj | 6 +++ .../TerminalSettingsModel/Command.cpp | 5 -- src/cascadia/TerminalSettingsModel/Command.h | 4 +- .../TerminalSettingsModel/Command.idl | 1 - .../KeyChordSerialization.cpp | 5 ++ 11 files changed, 106 insertions(+), 33 deletions(-) create mode 100644 src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/KeyChordConverter.h diff --git a/src/cascadia/TerminalApp/ActionPaletteItem.cpp b/src/cascadia/TerminalApp/ActionPaletteItem.cpp index 69014dca1f8..807a15c5417 100644 --- a/src/cascadia/TerminalApp/ActionPaletteItem.cpp +++ b/src/cascadia/TerminalApp/ActionPaletteItem.cpp @@ -22,7 +22,7 @@ namespace winrt::TerminalApp::implementation _Command(command) { Name(command.Name()); - KeyChordText(command.KeyChordText()); + KeyChordText(KeyChordSerialization::ToString(command.Keys())); Icon(command.IconPath()); _commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) { @@ -38,7 +38,7 @@ namespace winrt::TerminalApp::implementation } else if (changedProperty == L"KeyChordText") { - item->KeyChordText(senderCommand.KeyChordText()); + item->KeyChordText(KeyChordSerialization::ToString(senderCommand.Keys())); } else if (changedProperty == L"IconPath") { diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index 4d082997745..1f640e3e314 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -31,7 +31,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation // Filter out nested commands, and commands that aren't bound to a // key. This page is currently just for displaying the actions that // _are_ bound to keys. - if (command.HasNestedCommands() || command.KeyChordText().empty()) + if (command.HasNestedCommands() || !command.Keys()) { continue; } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 9911c415503..03baf79fa4a 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -18,7 +18,8 @@ - + + + AutomationProperties.Name="{x:Bind Name, Mode=OneWay}" + AutomationProperties.AcceleratorKey="{x:Bind Keys, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}"> @@ -59,26 +60,26 @@ - - - + + + + VerticalAlignment="Center"> - + diff --git a/src/cascadia/TerminalSettingsEditor/Converters.idl b/src/cascadia/TerminalSettingsEditor/Converters.idl index b6bde3fa01d..99b91440680 100644 --- a/src/cascadia/TerminalSettingsEditor/Converters.idl +++ b/src/cascadia/TerminalSettingsEditor/Converters.idl @@ -59,4 +59,14 @@ namespace Microsoft.Terminal.Settings.Editor DesktopWallpaperToEmptyStringConverter(); }; + runtimeclass KeyChordToVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter + { + KeyChordToVisibilityConverter(); + }; + + runtimeclass KeyChordToStringConverter : [default] Windows.UI.Xaml.Data.IValueConverter + { + KeyChordToStringConverter(); + }; + } diff --git a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp new file mode 100644 index 00000000000..dc42a21c71c --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "KeyChordConverter.h" +#include "KeyChordToStringConverter.g.cpp" +#include "KeyChordToVisibilityConverter.g.cpp" + +using namespace winrt::Windows; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Text; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Foundation::IInspectable KeyChordToStringConverter::Convert(Foundation::IInspectable const& value, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /* parameter */, + hstring const& /* language */) + { + const auto& keys{ winrt::unbox_value(value) }; + return box_value(Model::KeyChordSerialization::ToString(keys)); + } + + Foundation::IInspectable KeyChordToStringConverter::ConvertBack(Foundation::IInspectable const& value, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /*parameter*/, + hstring const& /* language */) + { + const auto& keys{ winrt::unbox_value(value) }; + return box_value(Model::KeyChordSerialization::FromString(keys)); + } + + Foundation::IInspectable KeyChordToVisibilityConverter::Convert(Foundation::IInspectable const& value, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /* parameter */, + hstring const& /* language */) + { + return box_value(value ? Visibility::Visible : Visibility::Collapsed); + } + + Foundation::IInspectable KeyChordToVisibilityConverter::ConvertBack(Foundation::IInspectable const& /*value*/, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /*parameter*/, + hstring const& /* language */) + { + throw hresult_not_implemented(); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h new file mode 100644 index 00000000000..0f2a08a98f0 --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "KeyChordToStringConverter.g.h" +#include "KeyChordToVisibilityConverter.g.h" +#include "../inc/cppwinrt_utils.h" + +DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, KeyChordToStringConverter); +DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, KeyChordToVisibilityConverter); diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index dfa79ac6a7c..72922802b9a 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -56,6 +56,9 @@ Converters.idl + + Converters.idl + Converters.idl @@ -156,6 +159,9 @@ Converters.idl + + Converters.idl + Converters.idl diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index b7392ed008f..04b2b041fb9 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -472,9 +472,4 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return newCommands; } - - hstring Command::KeyChordText() - { - return KeyChordSerialization::ToString(_Keys); - } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 48119158103..659f090caff 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -51,14 +51,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool HasNestedCommands() const; Windows::Foundation::Collections::IMapView NestedCommands() const; - hstring KeyChordText(); - winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker; WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); WINRT_OBSERVABLE_PROPERTY(Model::ActionAndArgs, Action, _PropertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(Control::KeyChord, Keys, _PropertyChangedHandlers); + WINRT_OBSERVABLE_PROPERTY(Control::KeyChord, Keys, _PropertyChangedHandlers, nullptr); WINRT_OBSERVABLE_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers); WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index bf65b9e659b..5c26b03d937 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -15,7 +15,6 @@ namespace Microsoft.Terminal.Settings.Model String Name; ActionAndArgs Action; Microsoft.Terminal.Control.KeyChord Keys; - String KeyChordText { get; }; String IconPath; diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index 056354ffc4d..fcb0c18ca09 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -221,6 +221,11 @@ static KeyChord _FromString(const std::wstring& wstr) // - a string which is an equivalent serialization of this object. static std::wstring _ToString(const KeyChord& chord) { + if (!chord) + { + return {}; + } + bool serializedSuccessfully = false; const auto modifiers = chord.Modifiers(); const auto vkey = chord.Vkey(); From c0c5a49ffd60dde109f1f293c84b2f4d6ea3edaa Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 19 Mar 2021 10:55:25 -0700 Subject: [PATCH 03/31] fix nested key chords --- src/cascadia/TerminalApp/TerminalPage.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index fb2a726780a..5965c654e8e 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -76,6 +76,16 @@ namespace winrt::TerminalApp::implementation { const auto& command = nameAndCmd.Value(); + // If there's a keybinding that's bound to exactly this command, + // then get the keychord and display it as a + // part of the command in the UI. Each Command's KeyChord is + // unset by default, so we don't need to worry about clearing it + // if there isn't a key associated with it. + // However, if a nested command tries to bind a KeyChord, the + // Command records it, but it is never actually bound to the keys. + const auto keyChord{ settings.KeyMap().GetKeyBindingForActionWithArgs(command.Action()) }; + command.Keys(keyChord); + if (command.HasNestedCommands()) { _recursiveUpdateCommandKeybindingLabels(settings, command.NestedCommands()); From 68984c811bcad0c39b5c5149e2935faf143dc6d6 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 29 Mar 2021 16:50:44 -0700 Subject: [PATCH 04/31] xaml format --- src/cascadia/TerminalSettingsEditor/Actions.xaml | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 03baf79fa4a..c2a25d1afdc 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -18,8 +18,9 @@ - - + + + + AutomationProperties.AcceleratorKey="{x:Bind Keys, Mode=OneWay, Converter={StaticResource KeyChordToStringConverter}}" + AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"> @@ -77,8 +78,8 @@ HorizontalAlignment="Right" VerticalAlignment="Center"> - From 9de4f589992293a3a1b7acdd1f20cac3984fe407 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 31 Mar 2021 14:24:20 -0700 Subject: [PATCH 05/31] add [] notation support to converter --- .../TerminalSettingsModel/KeyChordSerialization.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index fcb0c18ca09..dadc48520a5 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -311,9 +311,11 @@ winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) KeyChord ConversionTrait::FromJson(const Json::Value& json) { - const std::string keyChordText{ json.asString() }; try { + const std::string keyChordText{ json.isString() ? + json.asString() : + json[0].asString() }; return _FromString(til::u8u16(keyChordText)); } catch (winrt::hresult_invalid_argument) @@ -324,7 +326,7 @@ KeyChord ConversionTrait::FromJson(const Json::Value& json) bool ConversionTrait::CanConvert(const Json::Value& json) { - return json.isString(); + return json.isString() || (json.isArray() && json.size() == 1); } Json::Value ConversionTrait::ToJson(const KeyChord& val) From 460f91477c9694cd4bbffe8a4b7b755bdfb6f449 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 25 Mar 2021 14:37:16 -0700 Subject: [PATCH 06/31] Introduce ActionMap to Terminal Settings Model --- .../DeserializationTests.cpp | 68 ++- .../KeyBindingsTests.cpp | 242 +++++------ .../TerminalSettingsTests.cpp | 28 +- .../LocalTests_SettingsModel/TestUtils.h | 10 +- .../LocalTests_TerminalApp/SettingsTests.cpp | 47 +-- src/cascadia/TerminalApp/AppKeyBindings.cpp | 9 +- src/cascadia/TerminalApp/AppKeyBindings.h | 4 +- src/cascadia/TerminalApp/AppKeyBindings.idl | 2 +- src/cascadia/TerminalApp/CommandPalette.cpp | 13 +- src/cascadia/TerminalApp/CommandPalette.h | 4 +- src/cascadia/TerminalApp/CommandPalette.idl | 2 +- src/cascadia/TerminalApp/TabBase.cpp | 8 +- src/cascadia/TerminalApp/TabBase.h | 4 +- src/cascadia/TerminalApp/TabManagement.cpp | 2 +- src/cascadia/TerminalApp/TerminalPage.cpp | 55 ++- src/cascadia/TerminalApp/TerminalPage.h | 2 +- .../TerminalSettingsEditor/Actions.cpp | 2 +- .../TerminalSettingsModel/ActionMap.cpp | 388 ++++++++++++++++++ .../TerminalSettingsModel/ActionMap.h | 89 ++++ .../TerminalSettingsModel/ActionMap.idl | 30 ++ .../ActionMapSerialization.cpp | 82 ++++ .../CascadiaSettings.cpp | 6 +- .../TerminalSettingsModel/CascadiaSettings.h | 2 +- .../CascadiaSettings.idl | 2 +- .../CascadiaSettingsSerialization.cpp | 51 --- .../TerminalSettingsModel/Command.cpp | 38 +- .../TerminalSettingsModel/Command.idl | 22 +- .../GlobalAppSettings.cpp | 42 +- .../TerminalSettingsModel/GlobalAppSettings.h | 9 +- .../GlobalAppSettings.idl | 7 +- .../TerminalSettingsModel/KeyMapping.cpp | 144 ------- .../TerminalSettingsModel/KeyMapping.h | 80 ---- .../TerminalSettingsModel/KeyMapping.idl | 39 -- .../KeyMappingSerialization.cpp | 162 -------- ...crosoft.Terminal.Settings.ModelLib.vcxproj | 20 +- ...Terminal.Settings.ModelLib.vcxproj.filters | 5 +- .../Microsoft.Terminal.Settings.Model.vcxproj | 1 + 37 files changed, 912 insertions(+), 809 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/ActionMap.cpp create mode 100644 src/cascadia/TerminalSettingsModel/ActionMap.h create mode 100644 src/cascadia/TerminalSettingsModel/ActionMap.idl create mode 100644 src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp delete mode 100644 src/cascadia/TerminalSettingsModel/KeyMapping.cpp delete mode 100644 src/cascadia/TerminalSettingsModel/KeyMapping.h delete mode 100644 src/cascadia/TerminalSettingsModel/KeyMapping.idl delete mode 100644 src/cascadia/TerminalSettingsModel/KeyMappingSerialization.cpp diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index d0a075a7de6..14d2e7e7dd3 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -1918,7 +1918,7 @@ namespace SettingsModelLocalTests const auto settingsObject = VerifyParseSucceeded(badSettings); auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size()); + VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().KeybindingCount()); VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0)); @@ -1962,7 +1962,7 @@ namespace SettingsModelLocalTests auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->_keymap->_keyShortcuts.size()); + VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().KeybindingCount()); for (const auto& warning : settings->_globals->_keybindingsWarnings) { @@ -2103,18 +2103,17 @@ namespace SettingsModelLocalTests const auto profile2Guid = settings->_allProfiles.GetAt(2).Guid(); VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); - auto keymap = winrt::get_self(settings->_globals->KeyMap()); - VERIFY_ARE_EQUAL(5u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::get_self(settings->_globals->ActionMap()); + VERIFY_ARE_EQUAL(5u, actionMap->KeybindingCount()); // A/D, B, C, E will be in the list of commands, for 4 total. // * A and D share the same name, so they'll only generate a single action. // * F's name is set manually to `null` - auto commands = settings->_globals->Commands(); - VERIFY_ARE_EQUAL(4u, commands.Size()); + VERIFY_ARE_EQUAL(4u, settings->_globals->ActionMap().CommandCount()); { KeyChord kc{ true, false, false, static_cast('A') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2131,7 +2130,7 @@ namespace SettingsModelLocalTests { KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2145,7 +2144,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('D') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2159,7 +2158,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('E') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2173,7 +2172,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('F') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -2187,9 +2186,9 @@ namespace SettingsModelLocalTests } Log::Comment(L"Now verify the commands"); - _logCommandNames(commands); + _logCommandNames(actionMap->NameMap()); { - auto command = commands.Lookup(L"Split pane, split: vertical"); + auto command = actionMap->GetActionByName(L"Split pane, split: vertical"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2205,7 +2204,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = commands.Lookup(L"ctrl+b"); + auto command = actionMap->GetActionByName(L"ctrl+b"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2221,7 +2220,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = commands.Lookup(L"ctrl+c"); + auto command = actionMap->GetActionByName(L"ctrl+c"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2237,7 +2236,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = commands.Lookup(L"Split pane, split: horizontal"); + auto command = actionMap->GetActionByName(L"Split pane, split: horizontal"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2308,16 +2307,15 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - auto commands = settings->_globals->Commands(); settings->_ValidateSettings(); - _logCommandNames(commands); + _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); // Because the "parent" command didn't have a name, it couldn't be // placed into the list of commands. It and it's children are just // ignored. - VERIFY_ARE_EQUAL(0u, commands.Size()); + VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); } void DeserializationTests::TestNestedCommandWithBadSubCommands() @@ -2358,13 +2356,12 @@ namespace SettingsModelLocalTests auto settings = winrt::make_self(); settings->_ParseJsonString(settingsJson, false); settings->LayerJson(settings->_userSettings); - auto commands = settings->_globals->Commands(); settings->_ValidateSettings(); VERIFY_ARE_EQUAL(2u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1)); - VERIFY_ARE_EQUAL(0u, commands.Size()); + VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); } void DeserializationTests::TestUnbindNestedCommand() @@ -2432,22 +2429,20 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - auto commands = settings->_globals->Commands(); settings->_ValidateSettings(); - _logCommandNames(commands); + _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, commands.Size()); + VERIFY_ARE_EQUAL(1u, settings->_globals->ActionMap().CommandCount()); Log::Comment(L"Layer second bit of json, to unbind the original command."); settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); - commands = settings->_globals->Commands(); - _logCommandNames(commands); + _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(0u, commands.Size()); + VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); } void DeserializationTests::TestRebindNestedCommand() @@ -2516,16 +2511,16 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); - auto commands = settings->_globals->Commands(); + const auto& actionMap{ settings->ActionMap() }; settings->_ValidateSettings(); - _logCommandNames(commands); + _logCommandNames(actionMap.NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, commands.Size()); + VERIFY_ARE_EQUAL(1u, settings->_globals->ActionMap().CommandCount()); { winrt::hstring commandName{ L"parent" }; - auto commandProj = commands.Lookup(commandName); + auto commandProj = actionMap.GetActionByName(commandName); VERIFY_IS_NOT_NULL(commandProj); winrt::com_ptr commandImpl; @@ -2539,14 +2534,13 @@ namespace SettingsModelLocalTests settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); - commands = settings->_globals->Commands(); - _logCommandNames(commands); + _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, commands.Size()); + VERIFY_ARE_EQUAL(1u, settings->ActionMap().CommandCount()); { winrt::hstring commandName{ L"parent" }; - auto commandProj = commands.Lookup(commandName); + auto commandProj = actionMap.GetActionByName(commandName); VERIFY_IS_NOT_NULL(commandProj); auto actionAndArgs = commandProj.Action(); @@ -2642,8 +2636,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(settings->_globals->_colorSchemes.HasKey(schemeName), copyImpl->_globals->_colorSchemes.HasKey(schemeName)); // test actions - VERIFY_ARE_EQUAL(settings->_globals->_keymap->_keyShortcuts.size(), copyImpl->_globals->_keymap->_keyShortcuts.size()); - VERIFY_ARE_EQUAL(settings->_globals->_commands.Size(), copyImpl->_globals->_commands.Size()); + VERIFY_ARE_EQUAL(settings->ActionMap().KeybindingCount(), copyImpl->ActionMap().KeybindingCount()); + VERIFY_ARE_EQUAL(settings->ActionMap().CommandCount(), copyImpl->ActionMap().CommandCount()); // Test that changing the copy should not change the original VERIFY_ARE_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters()); diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 506c6fde610..2ec0519ae5e 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -5,7 +5,7 @@ #include "../TerminalSettingsModel/ColorScheme.h" #include "../TerminalSettingsModel/CascadiaSettings.h" -#include "../TerminalSettingsModel/KeyMapping.h" +#include "../TerminalSettingsModel/ActionMap.h" #include "JsonTestClass.h" #include "TestUtils.h" @@ -71,18 +71,18 @@ namespace SettingsModelLocalTests const auto bindings1Json = VerifyParseSucceeded(bindings1String); const auto bindings2Json = VerifyParseSucceeded(bindings2String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings1Json); + VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings2Json); + VERIFY_ARE_EQUAL(4u, actionMap->KeybindingCount()); } void KeyBindingsTests::LayerKeybindings() @@ -95,18 +95,18 @@ namespace SettingsModelLocalTests const auto bindings1Json = VerifyParseSucceeded(bindings1String); const auto bindings2Json = VerifyParseSucceeded(bindings2String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings1Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings2Json); + VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); } void KeyBindingsTests::UnbindKeybindings() @@ -125,52 +125,52 @@ namespace SettingsModelLocalTests const auto bindings4Json = VerifyParseSucceeded(bindings4String); const auto bindings5Json = VerifyParseSucceeded(bindings5String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); - keymap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings1Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `\"unbound\"` to unbind the key")); - keymap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings2Json); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `null` to unbind the key")); // First add back a good binding - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); // Then try layering in the bad setting - keymap->LayerJson(bindings3Json); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings3Json); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using an unrecognized command to unbind the key")); // First add back a good binding - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); // Then try layering in the bad setting - keymap->LayerJson(bindings4Json); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings4Json); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using a straight up invalid value to unbind the key")); // First add back a good binding - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); // Then try layering in the bad setting - keymap->LayerJson(bindings5Json); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings5Json); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); Log::Comment(NoThrowString().Format( L"Try unbinding a key that wasn't bound at all")); - keymap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); + actionMap->LayerJson(bindings2Json); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); } void KeyBindingsTests::TestArbitraryArgs() @@ -194,17 +194,17 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(10u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(10u, actionMap->KeybindingCount()); { Log::Comment(NoThrowString().Format( L"Verify that `copy` without args parses as Copy(SingleLine=false)")); KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value @@ -215,7 +215,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `copy` with args parses them correctly")); KeyChord kc{ true, false, true, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value @@ -226,7 +226,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `copy` with args parses them correctly")); KeyChord kc{ false, true, true, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value @@ -237,7 +237,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `newTab` without args parses as NewTab(Index=null)")); KeyChord kc{ true, false, false, static_cast('T') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -249,7 +249,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `newTab` parses args correctly")); KeyChord kc{ true, false, true, static_cast('T') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -263,7 +263,7 @@ namespace SettingsModelLocalTests L"Verify that `newTab` with an index greater than the legacy " L"args afforded parses correctly")); KeyChord kc{ true, false, true, static_cast('Y') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -277,7 +277,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `copy` ignores args it doesn't understand")); KeyChord kc{ true, false, true, static_cast('B') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -289,7 +289,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `copy` null as it's `args` parses as the default option")); KeyChord kc{ true, false, true, static_cast('B') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -301,7 +301,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `adjustFontSize` with a positive delta parses args correctly")); KeyChord kc{ true, false, false, static_cast('F') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -313,7 +313,7 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Verify that `adjustFontSize` with a negative delta parses args correctly")); KeyChord kc{ true, false, false, static_cast('G') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::AdjustFontSize, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -333,15 +333,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(4u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(4u, actionMap->KeybindingCount()); { KeyChord kc{ true, false, false, static_cast('D') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -350,7 +350,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('E') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -359,7 +359,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('G') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -368,7 +368,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('H') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -387,15 +387,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(3u, actionMap->KeybindingCount()); { KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -404,7 +404,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('D') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -415,7 +415,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('F') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SetTabColor, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -432,15 +432,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); { KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value @@ -461,15 +461,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(6u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(6u, actionMap->KeybindingCount()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -478,7 +478,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ false, false, false, static_cast(VK_DOWN) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -487,7 +487,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -496,7 +496,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast(VK_DOWN) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -505,7 +505,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, true, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollUp, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -515,7 +515,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, true, static_cast(VK_DOWN) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ScrollDown, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -526,10 +526,10 @@ namespace SettingsModelLocalTests { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "scrollDown", "rowsToScroll": -1 } }])" }; const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); - auto invalidKeyMap = winrt::make_self(); - VERIFY_IS_NOT_NULL(invalidKeyMap); - VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size()); - VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception); + auto invalidActionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(invalidActionMap); + VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } @@ -542,15 +542,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(2u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -559,7 +559,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ false, false, false, static_cast(VK_DOWN) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::MoveTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -568,17 +568,17 @@ namespace SettingsModelLocalTests } { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": "moveTab" }])" }; - auto keyMapNoArgs = winrt::make_self(); - keyMapNoArgs->LayerJson(bindingsInvalidString); - VERIFY_ARE_EQUAL(0u, keyMapNoArgs->_keyShortcuts.size()); + auto actionMapNoArgs = winrt::make_self(); + actionMapNoArgs->LayerJson(bindingsInvalidString); + VERIFY_ARE_EQUAL(0u, actionMapNoArgs->KeybindingCount()); } { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" }; const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); - auto invalidKeyMap = winrt::make_self(); - VERIFY_IS_NOT_NULL(invalidKeyMap); - VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size()); - VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception); + auto invalidActionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(invalidActionMap); + VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } @@ -592,15 +592,15 @@ namespace SettingsModelLocalTests const auto bindings0Json = VerifyParseSucceeded(bindings0String); - auto keymap = winrt::make_self(); - VERIFY_IS_NOT_NULL(keymap); - VERIFY_ARE_EQUAL(0u, keymap->_keyShortcuts.size()); - keymap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(3u, keymap->_keyShortcuts.size()); + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(3u, actionMap->KeybindingCount()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -609,7 +609,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -618,7 +618,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, true, static_cast(VK_UP) }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(*keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(*actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::ToggleCommandPalette, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -628,10 +628,10 @@ namespace SettingsModelLocalTests { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "commandPalette", "launchMode": "bad" } }])" }; const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); - auto invalidKeyMap = winrt::make_self(); - VERIFY_IS_NOT_NULL(invalidKeyMap); - VERIFY_ARE_EQUAL(0u, invalidKeyMap->_keyShortcuts.size()); - VERIFY_THROWS(invalidKeyMap->LayerJson(bindingsInvalidJson);, std::exception); + auto invalidActionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(invalidActionMap); + VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } } diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index e46d4eb78d3..cae0f1ad451 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -103,17 +103,17 @@ namespace SettingsModelLocalTests CascadiaSettings settings{ til::u8u16(settingsJson) }; - auto keymap = settings.GlobalSettings().KeyMap(); + auto actionMap = settings.GlobalSettings().ActionMap(); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid(); VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); - VERIFY_ARE_EQUAL(12u, keymap.Size()); + VERIFY_ARE_EQUAL(12u, actionMap.KeybindingCount()); { KeyChord kc{ true, false, false, static_cast('A') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -134,7 +134,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('B') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -156,7 +156,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('C') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -178,7 +178,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('D') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -200,7 +200,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('E') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -222,7 +222,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('F') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -245,7 +245,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('G') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -265,7 +265,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('H') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -287,7 +287,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('I') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -310,7 +310,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('J') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -332,7 +332,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('K') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); @@ -355,7 +355,7 @@ namespace SettingsModelLocalTests } { KeyChord kc{ true, false, false, static_cast('L') }; - auto actionAndArgs = ::TestUtils::GetActionAndArgs(keymap, kc); + auto actionAndArgs = ::TestUtils::GetActionAndArgs(actionMap, kc); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); diff --git a/src/cascadia/LocalTests_SettingsModel/TestUtils.h b/src/cascadia/LocalTests_SettingsModel/TestUtils.h index 12c8dd4f9d3..32ea76b7bcd 100644 --- a/src/cascadia/LocalTests_SettingsModel/TestUtils.h +++ b/src/cascadia/LocalTests_SettingsModel/TestUtils.h @@ -19,11 +19,11 @@ class TestUtils // - This is a helper to retrieve the ActionAndArgs from the keybindings // for a given chord. // Arguments: - // - keymap: The AppKeyBindings to lookup the ActionAndArgs from. + // - actionMap: The ActionMap to lookup the ActionAndArgs from. // - kc: The key chord to look up the bound ActionAndArgs for. // Return Value: // - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it. - static const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs GetActionAndArgs(const winrt::Microsoft::Terminal::Settings::Model::KeyMapping& keymap, + static const winrt::Microsoft::Terminal::Settings::Model::ActionAndArgs GetActionAndArgs(const winrt::Microsoft::Terminal::Settings::Model::ActionMap& actionMap, const winrt::Microsoft::Terminal::Control::KeyChord& kc) { std::wstring buffer{ L"" }; @@ -42,8 +42,8 @@ class TestUtils buffer += static_cast(MapVirtualKeyW(kc.Vkey(), MAPVK_VK_TO_CHAR)); WEX::Logging::Log::Comment(WEX::Common::NoThrowString().Format(L"Looking for key:%s", buffer.c_str())); - const auto action = keymap.TryLookup(kc); - VERIFY_IS_NOT_NULL(action, L"Expected to find an action bound to the given KeyChord"); - return action; + const auto cmd = actionMap.GetActionByKeyChord(kc); + VERIFY_IS_NOT_NULL(cmd, L"Expected to find an action bound to the given KeyChord"); + return cmd.Action(); }; }; diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index 2d6273ad894..e7b8a6fade9 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -120,11 +120,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - VERIFY_ARE_EQUAL(1u, commands.Size()); + auto actionMap{ settings.ActionMap() }; + VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); { - auto command = commands.Lookup(L"iterable command ${profile.name}"); + auto command = actionMap.GetActionByName(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -141,7 +141,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -247,11 +247,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - VERIFY_ARE_EQUAL(1u, commands.Size()); + auto actionMap{ settings.ActionMap() }; + VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); { - auto command = commands.Lookup(L"Split pane, profile: ${profile.name}"); + auto command = actionMap.GetActionByName(L"Split pane, profile: ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -268,7 +268,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -376,11 +376,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - VERIFY_ARE_EQUAL(1u, commands.Size()); + auto actionMap{ settings.ActionMap() }; + VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); { - auto command = commands.Lookup(L"iterable command ${profile.name}"); + auto command = actionMap.GetActionByName(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -397,7 +397,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -513,8 +513,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -608,8 +607,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -731,8 +729,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -868,8 +865,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -971,8 +967,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(settings.ActionMap().NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -1113,11 +1108,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto commands = settings.GlobalSettings().Commands(); - VERIFY_ARE_EQUAL(1u, commands.Size()); + auto actionMap{ settings.ActionMap() }; + VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); { - auto command = commands.Lookup(L"iterable command ${scheme.name}"); + auto command = actionMap.GetActionByName(L"iterable command ${scheme.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -1134,7 +1129,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(commands, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); // This is the same warning as above diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index 4559e7624cf..a627252ae7a 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -14,10 +14,9 @@ namespace winrt::TerminalApp::implementation { bool AppKeyBindings::TryKeyChord(const KeyChord& kc) { - const auto actionAndArgs = _keymap.TryLookup(kc); - if (actionAndArgs) + if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) }) { - return _dispatch.DoAction(actionAndArgs); + return _dispatch.DoAction(cmd.Action()); } return false; } @@ -27,8 +26,8 @@ namespace winrt::TerminalApp::implementation _dispatch = dispatch; } - void AppKeyBindings::SetKeyMapping(const winrt::Microsoft::Terminal::Settings::Model::KeyMapping& keymap) + void AppKeyBindings::SetActionMap(const winrt::Microsoft::Terminal::Settings::Model::ActionMap& actionMap) { - _keymap = keymap; + _actionMap = actionMap; } } diff --git a/src/cascadia/TerminalApp/AppKeyBindings.h b/src/cascadia/TerminalApp/AppKeyBindings.h index 301367eb58b..90006c08195 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.h +++ b/src/cascadia/TerminalApp/AppKeyBindings.h @@ -22,10 +22,10 @@ namespace winrt::TerminalApp::implementation bool TryKeyChord(winrt::Microsoft::Terminal::Control::KeyChord const& kc); void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); - void SetKeyMapping(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap); + void SetActionMap(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap); private: - winrt::Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr }; + winrt::Microsoft::Terminal::Settings::Model::ActionMap _actionMap{ nullptr }; winrt::TerminalApp::ShortcutActionDispatch _dispatch{ nullptr }; diff --git a/src/cascadia/TerminalApp/AppKeyBindings.idl b/src/cascadia/TerminalApp/AppKeyBindings.idl index 1bd93de0d9d..8bf61a8b460 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.idl +++ b/src/cascadia/TerminalApp/AppKeyBindings.idl @@ -9,6 +9,6 @@ namespace TerminalApp AppKeyBindings(); void SetDispatch(ShortcutActionDispatch dispatch); - void SetKeyMapping(Microsoft.Terminal.Settings.Model.KeyMapping keymap); + void SetActionMap(Microsoft.Terminal.Settings.Model.ActionMap actionMap); } } diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 1c71a69d282..5214ae2b65d 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -261,19 +261,18 @@ namespace winrt::TerminalApp::implementation // Only give anchored tab switcher the ability to cycle through tabs with the tab button. // For unanchored mode, accessibility becomes an issue when we try to hijack tab since it's // a really widely used keyboard navigation key. - if (_currentMode == CommandPaletteMode::TabSwitchMode && _keymap) + if (_currentMode == CommandPaletteMode::TabSwitchMode && _actionMap) { winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; - const auto action = _keymap.TryLookup(kc); - if (action) + if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) }) { - if (action.Action() == ShortcutAction::PrevTab) + if (cmd.Action().Action() == ShortcutAction::PrevTab) { SelectNextItem(false); e.Handled(true); return; } - else if (action.Action() == ShortcutAction::NextTab) + else if (cmd.Action().Action() == ShortcutAction::NextTab) { SelectNextItem(true); e.Handled(true); @@ -864,9 +863,9 @@ namespace winrt::TerminalApp::implementation return _filteredActions; } - void CommandPalette::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) + void CommandPalette::SetActionMap(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap) { - _keymap = keymap; + _actionMap = actionMap; } void CommandPalette::SetCommands(Collections::IVector const& actions) diff --git a/src/cascadia/TerminalApp/CommandPalette.h b/src/cascadia/TerminalApp/CommandPalette.h index fab6ec91492..baa4421251b 100644 --- a/src/cascadia/TerminalApp/CommandPalette.h +++ b/src/cascadia/TerminalApp/CommandPalette.h @@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation void SetCommands(Windows::Foundation::Collections::IVector const& actions); void SetTabs(Windows::Foundation::Collections::IObservableVector const& tabs, Windows::Foundation::Collections::IObservableVector const& mruTabs); - void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap); + void SetActionMap(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap); bool OnDirectKeyEvent(const uint32_t vkey, const uint8_t scanCode, const bool down); @@ -107,7 +107,7 @@ namespace winrt::TerminalApp::implementation std::wstring _getTrimmedInput(); void _evaluatePrefix(); - Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr }; + Microsoft::Terminal::Settings::Model::ActionMap _actionMap{ nullptr }; // Tab Switcher Windows::Foundation::Collections::IVector _tabActions{ nullptr }; diff --git a/src/cascadia/TerminalApp/CommandPalette.idl b/src/cascadia/TerminalApp/CommandPalette.idl index c38da8acbf8..7710a9f53de 100644 --- a/src/cascadia/TerminalApp/CommandPalette.idl +++ b/src/cascadia/TerminalApp/CommandPalette.idl @@ -25,7 +25,7 @@ namespace TerminalApp void SetTabs(Windows.Foundation.Collections.IObservableVector tabs, Windows.Foundation.Collections.IObservableVector mruTabs); - void SetKeyMap(Microsoft.Terminal.Settings.Model.KeyMapping keymap); + void SetActionMap(Microsoft.Terminal.Settings.Model.ActionMap actionMap); void SelectNextItem(Boolean moveDown); diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp index ff51c2dfb0b..7643ea00456 100644 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ b/src/cascadia/TerminalApp/TabBase.cpp @@ -148,9 +148,9 @@ namespace winrt::TerminalApp::implementation _dispatch = dispatch; } - void TabBase::SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) + void TabBase::SetActionMap(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap) { - _keymap = keymap; + _actionMap = actionMap; _UpdateSwitchToTabKeyChord(); } @@ -163,9 +163,7 @@ namespace winrt::TerminalApp::implementation // - winrt::fire_and_forget TabBase::_UpdateSwitchToTabKeyChord() { - SwitchToTabArgs args{ _TabViewIndex }; - ActionAndArgs switchToTab{ ShortcutAction::SwitchToTab, args }; - const auto keyChord = _keymap ? _keymap.GetKeyBindingForActionWithArgs(switchToTab) : nullptr; + const auto keyChord = _actionMap ? _actionMap.GetKeyBindingForAction(ShortcutAction::SwitchToTab, SwitchToTabArgs{ _TabViewIndex }) : nullptr; const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; if (_keyChord == keyChordText) diff --git a/src/cascadia/TerminalApp/TabBase.h b/src/cascadia/TerminalApp/TabBase.h index 6ae3093d346..629ee4b90b5 100644 --- a/src/cascadia/TerminalApp/TabBase.h +++ b/src/cascadia/TerminalApp/TabBase.h @@ -23,7 +23,7 @@ namespace winrt::TerminalApp::implementation void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch); void UpdateTabViewIndex(const uint32_t idx, const uint32_t numTabs); - void SetKeyMap(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap); + void SetActionMap(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap); WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler); WINRT_CALLBACK(CloseRequested, winrt::Windows::Foundation::EventHandler); @@ -46,7 +46,7 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeOtherTabsMenuItem{}; winrt::Windows::UI::Xaml::Controls::MenuFlyoutItem _closeTabsAfterMenuItem{}; winrt::TerminalApp::ShortcutActionDispatch _dispatch; - Microsoft::Terminal::Settings::Model::KeyMapping _keymap{ nullptr }; + Microsoft::Terminal::Settings::Model::ActionMap _actionMap{ nullptr }; winrt::hstring _keyChord{}; virtual void _CreateContextMenu(); diff --git a/src/cascadia/TerminalApp/TabManagement.cpp b/src/cascadia/TerminalApp/TabManagement.cpp index e52dcc70724..6e71ddf2955 100644 --- a/src/cascadia/TerminalApp/TabManagement.cpp +++ b/src/cascadia/TerminalApp/TabManagement.cpp @@ -130,7 +130,7 @@ namespace winrt::TerminalApp::implementation _mruTabs.Append(*newTabImpl); newTabImpl->SetDispatch(*_actionDispatch); - newTabImpl->SetKeyMap(_settings.KeyMap()); + newTabImpl->SetActionMap(_settings.ActionMap()); // Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command. _UpdateTabIndices(); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 5965c654e8e..d5aff6acf50 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -83,13 +83,16 @@ namespace winrt::TerminalApp::implementation // if there isn't a key associated with it. // However, if a nested command tries to bind a KeyChord, the // Command records it, but it is never actually bound to the keys. - const auto keyChord{ settings.KeyMap().GetKeyBindingForActionWithArgs(command.Action()) }; - command.Keys(keyChord); - if (command.HasNestedCommands()) { + command.Keys(nullptr); _recursiveUpdateCommandKeybindingLabels(settings, command.NestedCommands()); } + else + { + const auto keyChord{ settings.ActionMap().GetKeyBindingForAction(command.Action().Action(), command.Action().Args()) }; + command.Keys(keyChord); + } } } @@ -107,7 +110,7 @@ namespace winrt::TerminalApp::implementation // happen before the Settings UI is reloaded and tries to re-read // those values. _UpdateCommandsForPalette(); - CommandPalette().SetKeyMap(_settings.KeyMap()); + CommandPalette().SetActionMap(_settings.ActionMap()); if (needRefreshUI) { @@ -123,7 +126,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::Create() { // Hookup the key bindings - _HookupKeyBindings(_settings.KeyMap()); + _HookupKeyBindings(_settings.ActionMap()); _tabContent = this->TabContent(); _tabRow = this->TabRow(); @@ -523,7 +526,7 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_CreateNewTabFlyout() { auto newTabFlyout = WUX::Controls::MenuFlyout{}; - auto keyBindings = _settings.KeyMap(); + auto actionMap = _settings.ActionMap(); const auto defaultProfileGuid = _settings.GlobalSettings().DefaultProfile(); // the number of profiles should not change in the loop for this to work @@ -538,8 +541,7 @@ namespace winrt::TerminalApp::implementation // NewTab(ProfileIndex=N) action NewTerminalArgs newTerminalArgs{ profileIndex }; NewTabArgs newTabArgs{ newTerminalArgs }; - ActionAndArgs actionAndArgs{ ShortcutAction::NewTab, newTabArgs }; - auto profileKeyChord{ keyBindings.GetKeyBindingForActionWithArgs(actionAndArgs) }; + auto profileKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) }; // make sure we find one to display if (profileKeyChord) @@ -663,9 +665,7 @@ namespace winrt::TerminalApp::implementation settingsItem.Click({ this, &TerminalPage::_SettingsButtonOnClick }); newTabFlyout.Items().Append(settingsItem); - Microsoft::Terminal::Settings::Model::OpenSettingsArgs args{ SettingsTarget::SettingsUI }; - Microsoft::Terminal::Settings::Model::ActionAndArgs settingsAction{ ShortcutAction::OpenSettings, args }; - const auto settingsKeyChord{ keyBindings.GetKeyBindingForActionWithArgs(settingsAction) }; + const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) }; if (settingsKeyChord) { _SetAcceleratorForMenuItem(settingsItem, settingsKeyChord); @@ -892,14 +892,13 @@ namespace winrt::TerminalApp::implementation auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; - const auto actionAndArgs = _settings.KeyMap().TryLookup(kc); - if (actionAndArgs) + if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }) { - if (CommandPalette().Visibility() == Visibility::Visible && actionAndArgs.Action() != ShortcutAction::ToggleCommandPalette) + if (CommandPalette().Visibility() == Visibility::Visible && cmd.Action().Action() != ShortcutAction::ToggleCommandPalette) { CommandPalette().Visibility(Visibility::Collapsed); } - _actionDispatch->DoAction(actionAndArgs); + _actionDispatch->DoAction(cmd.Action()); e.Handled(true); } } @@ -920,23 +919,23 @@ namespace winrt::TerminalApp::implementation auto const shiftDown = WI_IsFlagSet(CoreWindow::GetForCurrentThread().GetKeyState(winrt::Windows::System::VirtualKey::Shift), CoreVirtualKeyStates::Down); winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; - const auto actionAndArgs = _settings.KeyMap().TryLookup(kc); - if (actionAndArgs && (actionAndArgs.Action() == ShortcutAction::CloseTab || actionAndArgs.Action() == ShortcutAction::NextTab || actionAndArgs.Action() == ShortcutAction::PrevTab || actionAndArgs.Action() == ShortcutAction::ClosePane)) + const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }; + if (cmd && (cmd.Action().Action() == ShortcutAction::CloseTab || cmd.Action().Action() == ShortcutAction::NextTab || cmd.Action().Action() == ShortcutAction::PrevTab || cmd.Action().Action() == ShortcutAction::ClosePane)) { - _actionDispatch->DoAction(actionAndArgs); + _actionDispatch->DoAction(cmd.Action()); e.Handled(true); } } // Method Description: - // - Configure the AppKeyBindings to use our ShortcutActionDispatch and the updated KeyMapping - // as the object to handle dispatching ShortcutAction events. + // - Configure the AppKeyBindings to use our ShortcutActionDispatch and the updated ActionMap + // as the object to handle dispatching ShortcutAction events. // Arguments: - // - bindings: A AppKeyBindings object to wire up with our event handlers - void TerminalPage::_HookupKeyBindings(const KeyMapping& keymap) noexcept + // - bindings: An ActionMap object to wire up with our event handlers + void TerminalPage::_HookupKeyBindings(const ActionMap& actionMap) noexcept { _bindings->SetDispatch(*_actionDispatch); - _bindings->SetKeyMapping(keymap); + _bindings->SetActionMap(actionMap); } // Method Description: @@ -1388,7 +1387,7 @@ namespace winrt::TerminalApp::implementation menuShortcut.Key(static_cast(keyChord.Vkey())); // inspect the modifiers from the KeyChord and set the flags int he XAML value - auto modifiers = AppKeyBindings::ConvertVKModifiers(keyChord.Modifiers()); + auto modifiers = ActionMap::ConvertVKModifiers(keyChord.Modifiers()); // add the modifiers to the shortcut menuShortcut.Modifiers(modifiers); @@ -1812,7 +1811,7 @@ namespace winrt::TerminalApp::implementation { // Re-wire the keybindings to their handlers, as we'll have created a // new AppKeyBindings object. - _HookupKeyBindings(_settings.KeyMap()); + _HookupKeyBindings(_settings.ActionMap()); // Refresh UI elements auto profiles = _settings.ActiveProfiles(); @@ -1860,7 +1859,7 @@ namespace winrt::TerminalApp::implementation } auto tabImpl{ winrt::get_self(tab) }; - tabImpl->SetKeyMap(_settings.KeyMap()); + tabImpl->SetActionMap(_settings.ActionMap()); } auto weakThis{ get_weak() }; @@ -1941,7 +1940,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_UpdateCommandsForPalette() { - IMap copyOfCommands = _ExpandCommands(_settings.GlobalSettings().Commands(), + IMap copyOfCommands = _ExpandCommands(_settings.GlobalSettings().ActionMap().NameMap(), _settings.ActiveProfiles().GetView(), _settings.GlobalSettings().ColorSchemes()); @@ -2335,7 +2334,7 @@ namespace winrt::TerminalApp::implementation _mruTabs.Append(*newTabImpl); newTabImpl->SetDispatch(*_actionDispatch); - newTabImpl->SetKeyMap(_settings.KeyMap()); + newTabImpl->SetActionMap(_settings.ActionMap()); // Give the tab its index in the _tabs vector so it can manage its own SwitchToTab command. _UpdateTabIndices(); diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 4e84a08462b..f7d24adf582 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -198,7 +198,7 @@ namespace winrt::TerminalApp::implementation void _KeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); void _SUIPreviewKeyDownHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e); - void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::KeyMapping& keymap) noexcept; + void _HookupKeyBindings(const Microsoft::Terminal::Settings::Model::ActionMap& actionMap) noexcept; void _RegisterActionCallbacks(); void _UpdateTitle(const TerminalTab& tab); diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index 1f640e3e314..53137858a42 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -26,7 +26,7 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { _State = e.Parameter().as(); - for (const auto& [k, command] : _State.Settings().GlobalSettings().Commands()) + for (const auto& [k, command] : _State.Settings().GlobalSettings().ActionMap().NameMap()) { // Filter out nested commands, and commands that aren't bound to a // key. This page is currently just for displaying the actions that diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp new file mode 100644 index 00000000000..833c8db4ea6 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -0,0 +1,388 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "ActionMap.h" + +#include "ActionMap.g.cpp" + +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Control; + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + // Method Description: + // - Retrieves the Command in the ActionList, if it's valid + // - We internally store invalid commands as full commands. + // This helper function returns nullptr when we get an invalid + // command. This allows us to simply check for null when we + // want a valid command. + // Arguments: + // - actionID: the internal ID associated with a Command + // Return Value: + // - If the command is valid, the command itself. Otherwise, null. + Model::Command ActionMap::_GetActionByID(size_t actionID) const + { + // ActionMap is not being maintained appropriately + // the given ID points out of bounds + FAIL_FAST_IF(actionID >= _ActionList.size()); + + const auto& cmd{ til::at(_ActionList, actionID) }; + + // ActionList should never point to nullptr + FAIL_FAST_IF_NULL(cmd); + + return !cmd.HasNestedCommands() && cmd.Action().Action() == ShortcutAction::Invalid ? + nullptr : + cmd; + } + + bool ActionMap::_IsActionIdValid(size_t actionID) const + { + // ActionMap is not being maintained appropriately + // the given ID points out of bounds + FAIL_FAST_IF(actionID >= _ActionList.size()); + + const auto& cmd{ til::at(_ActionList, actionID) }; + + // ActionList should never point to nullptr + FAIL_FAST_IF_NULL(cmd); + + return cmd.HasNestedCommands() || cmd.Action().Action() != ShortcutAction::Invalid; + } + + // Method Description: + // - Retrieves a map of command names to the commands themselves + // - These commands should not be modified directly because they may result in + // an invalid state for the `ActionMap` + Windows::Foundation::Collections::IMapView ActionMap::NameMap() const + { + // add everything from our parents + auto map{ single_threaded_map() }; + for (const auto& parent : _parents) + { + const auto& inheritedNameMap{ parent->NameMap() }; + for (const auto& [key, val] : inheritedNameMap) + { + map.Insert(key, val); + } + } + + // add everything from our layer + for (const auto& [name, actionID] : _NameMap) + { + if (const auto& cmd{ _GetActionByID(actionID) }) + { + map.Insert(name, cmd); + } + } + return map.GetView(); + } + + com_ptr ActionMap::Copy() const + { + auto actionMap{ make_self() }; + std::for_each(_NameMap.begin(), _NameMap.end(), [actionMap](const auto& pair) { + actionMap->_NameMap.insert(pair); + }); + + std::for_each(_KeyMap.begin(), _KeyMap.end(), [actionMap](const auto& pair) { + actionMap->_KeyMap.insert(pair); + }); + + std::for_each(_ActionList.begin(), _ActionList.end(), [actionMap](const auto& cmd) { + const auto& cmdCopy{ get_self(cmd)->Copy() }; + actionMap->_ActionList.push_back(*cmdCopy); + }); + return actionMap; + } + + // Method Description: + // - [Recursive] The number of key bindings the ActionMap has + // access to. + size_t ActionMap::KeybindingCount() const noexcept + { + // NOTE: We cannot do _KeyMap.size() here because + // unbinding keys works by adding an "unbound"/"invalid" + // action to the KeyMap and ActionList. + // Thus, we must check the validity of each action in our KeyMap. + + // count the key bindings in our layer + size_t count{ 0 }; + for (const auto& [_, actionID] : _KeyMap) + { + if (_IsActionIdValid(actionID)) + { + ++count; + } + } + + // count the key bindings in our parents + for (const auto& parent : _parents) + { + count += parent->KeybindingCount(); + } + return count; + } + + // Method Description: + // - [Recursive] The number of commands the ActionMap has + // access to. + size_t ActionMap::CommandCount() const noexcept + { + // count the named commands in our layer + size_t count{ 0 }; + for (const auto& [_, actionID] : _NameMap) + { + if (_IsActionIdValid(actionID)) + { + ++count; + } + } + + // count the named commands in our parents + for (const auto& parent : _parents) + { + count += parent->CommandCount(); + } + return count; + } + + // Method Description: + // - Adds a command to the ActionMap + // Arguments: + // - cmd: the command we're adding + void ActionMap::AddAction(const Model::Command& cmd) + { + // _Never_ add null to the ActionMap + if (!cmd) + { + return; + } + + // General Case: + // Add the new command to the NameMap and KeyMap (if applicable). + // These maps direct you to an entry in the ActionList. + // Unbinding Actions: + // Same as above, except the ActionList entry is specifically + // an unbound action. This is important for serialization. + + const auto actionID{ _ActionList.size() }; + _ActionList.push_back(cmd); + + // Update NameMap + const auto name{ cmd.Name() }; + if (!name.empty()) + { + const auto oldActionPair{ _NameMap.find(name) }; + if (oldActionPair != _NameMap.end()) + { + // the name was already in use. + // remove the old one. + auto& cmd{ til::at(_ActionList, oldActionPair->second) }; + cmd.Name(L""); + + // and add the new one + _NameMap.insert_or_assign(name, actionID); + } + else + { + // the name isn't mapped to anything, + // insert the actionID. + _NameMap.insert({ name, actionID }); + } + } + + // Update KeyMap + if (const auto keys{ cmd.Keys() }) + { + const auto oldActionPair{ _KeyMap.find(keys) }; + if (oldActionPair != _KeyMap.end()) + { + // the key chord was already in use. + // remove the old one. + auto& cmd{ til::at(_ActionList, oldActionPair->second) }; + cmd.Keys(nullptr); + + // and add the new one + _KeyMap.insert_or_assign(keys, actionID); + } + else + { + // the key chord isn't mapped to anything, + // insert the actionID. + _KeyMap.insert({ keys, actionID }); + } + } + } + + // Method Description: + // - Retrieves the assigned command with the given name + // Arguments: + // - name: the name of the command to search for + // Return Value: + // - the command with the given name + // - nullptr if the name is explicitly unbound + Model::Command ActionMap::GetActionByName(hstring const& name) const + { + // Check the current layer + const auto& cmd{ _GetActionByNameInternal(name) }; + if (cmd.has_value()) + { + return *cmd; + } + + // Check our parents + for (const auto& parent : _parents) + { + const auto& inheritedCmd{ parent->_GetActionByNameInternal(name) }; + if (inheritedCmd.has_value()) + { + return *inheritedCmd; + } + } + + // This action does not exist + return nullptr; + } + + // Method Description: + // - Retrieves the assigned command _in this layer_ with the given name + // Arguments: + // - name: the name of the command to search for + // Return Value: + // - the command with the given name + // - nullptr if the name is explicitly unbound + // - nullopt if it was not bound in this layer + std::optional ActionMap::_GetActionByNameInternal(hstring const& name) const + { + // Check the current layer + const auto actionIDPair{ _NameMap.find(name) }; + if (actionIDPair != _NameMap.end()) + { + // the action was explicitly bound, + // return what we found (invalid commands exposed as nullptr) + return _GetActionByID(actionIDPair->second); + } + + // the command was not bound in this layer + return std::nullopt; + } + + // Method Description: + // - Retrieves the assigned command that can be invoked with the given key chord + // Arguments: + // - keys: the key chord of the command to search for + // Return Value: + // - the command with the given key chord + // - nullptr if the key chord is explicitly unbound + Model::Command ActionMap::GetActionByKeyChord(Control::KeyChord const& keys) const + { + // Check the current layer + const auto& cmd{ _GetActionByKeyChordInternal(keys) }; + if (cmd.has_value()) + { + return *cmd; + } + + // Check our parents + for (const auto& parent : _parents) + { + const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; + if (inheritedCmd.has_value()) + { + return *inheritedCmd; + } + } + + // This action does not exist + return nullptr; + } + + // Method Description: + // - Retrieves the assigned command _in this layer_ with the given key chord + // Arguments: + // - keys: the key chord of the command to search for + // Return Value: + // - the command with the given key chord + // - nullptr if the key chord is explicitly unbound + // - nullopt if it was not bound in this layer + std::optional ActionMap::_GetActionByKeyChordInternal(Control::KeyChord const& keys) const + { + // Check the current layer + const auto actionIDPair{ _KeyMap.find(keys) }; + if (actionIDPair != _KeyMap.end()) + { + // the command was explicitly bound, + // return what we found (invalid commands exposed as nullptr) + return _GetActionByID(actionIDPair->second); + } + + // the command was not bound in this layer + return std::nullopt; + } + + // Method Description: + // - Retrieves the key chord for the provided action + // Arguments: + // - action: the shortcut action (an action type) we're looking for + // Return Value: + // - the key chord that executes the given action + // - nullptr if the action is not bound to a key chord + Control::KeyChord ActionMap::GetKeyBindingForAction(ShortcutAction const& action) const + { + return GetKeyBindingForAction(action, nullptr); + } + + // Method Description: + // - Retrieves the key chord for the provided action + // Arguments: + // - action: the shortcut action (an action type) we're looking for + // - myArgs: the action args for the action we're looking for + // Return Value: + // - the key chord that executes the given action + // - nullptr if the action is not bound to a key chord + Control::KeyChord ActionMap::GetKeyBindingForAction(ShortcutAction const& myAction, IActionArgs const& myArgs) const + { + if (myAction == ShortcutAction::Invalid) + { + return nullptr; + } + + // Check our internal state. + // Iterate through the list backwards so that we find the most recent change. + for (auto cmd{ _ActionList.rbegin() }; cmd != _ActionList.rend(); ++cmd) + { + // breakdown cmd + const auto& actionAndArgs{ cmd->Action() }; + + if (!actionAndArgs || actionAndArgs.Action() == ShortcutAction::Invalid) + { + continue; + } + + const auto& action{ actionAndArgs.Action() }; + const auto& args{ actionAndArgs.Args() }; + + // check if we have a match + const auto actionMatched{ action == myAction }; + const auto argsMatched{ args ? args.Equals(myArgs) : args == myArgs }; + if (actionMatched && argsMatched) + { + return cmd->Keys(); + } + } + + // Check our parents + for (const auto& parent : _parents) + { + if (const auto& keys{ parent->GetKeyBindingForAction(myAction, myArgs) }) + { + return keys; + } + } + + // This key binding does not exist + return nullptr; + } +} diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h new file mode 100644 index 00000000000..f18cb6c07bb --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -0,0 +1,89 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- ActionMap.h + +Abstract: +- A mapping of key chords to actions. Includes (de)serialization logic. + +Author(s): +- Carlos Zamora - September 2020 + +--*/ + +#pragma once + +#include "ActionMap.g.h" +#include "IInheritable.h" +#include "ActionArgs.h" +#include "Command.h" +#include "../inc/cppwinrt_utils.h" + +// fwdecl unittest classes +namespace SettingsModelLocalTests +{ + class KeyBindingsTests; +} + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + struct KeyChordHash + { + std::size_t operator()(const Control::KeyChord& key) const + { + std::hash keyHash; + std::hash modifiersHash; + std::size_t hashedKey = keyHash(key.Vkey()); + std::size_t hashedMods = modifiersHash(key.Modifiers()); + return hashedKey ^ hashedMods; + } + }; + + struct KeyChordEquality + { + bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const + { + return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey(); + } + }; + + struct ActionMap : ActionMapT, IInheritable + { + ActionMap() = default; + + // capacity + size_t KeybindingCount() const noexcept; + size_t CommandCount() const noexcept; + + // views + Windows::Foundation::Collections::IMapView NameMap() const; + com_ptr Copy() const; + + // queries + Model::Command GetActionByName(hstring const& name) const; + Model::Command GetActionByKeyChord(Control::KeyChord const& keys) const; + Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action) const; + Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action, IActionArgs const& actionArgs) const; + + // population + void AddAction(const Model::Command& cmd); + std::vector LayerJson(const Json::Value& json); + + static Windows::System::VirtualKeyModifiers ConvertVKModifiers(Control::KeyModifiers modifiers); + + private: + Model::Command _GetActionByID(size_t actionID) const; + bool _IsActionIdValid(size_t actionID) const; + + std::optional _GetActionByNameInternal(hstring const& name) const; + std::optional _GetActionByKeyChordInternal(Control::KeyChord const& keys) const; + + std::unordered_map _NameMap; + std::unordered_map _KeyMap; + std::vector _ActionList; + + friend class SettingsModelLocalTests::KeyBindingsTests; + }; +} diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl new file mode 100644 index 00000000000..343483c6330 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -0,0 +1,30 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +import "Command.idl"; + +namespace Microsoft.Terminal.Settings.Model +{ + [default_interface] runtimeclass ActionMap { + UInt64 KeybindingCount { get; }; + UInt64 CommandCount { get; }; + + Command GetActionByName(String name); + Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); + + Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); + Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); + + Windows.Foundation.Collections.IMapView NameMap { get; }; + + //ActionAndArgs TryLookup(Microsoft.Terminal.Control.KeyChord chord); + + //UInt64 Size(); + // + //void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.Control.KeyChord chord); + //void ClearKeyBinding(Microsoft.Terminal.Control.KeyChord chord); + // + //Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); + //Microsoft.Terminal.Control.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs); + } +} diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp new file mode 100644 index 00000000000..7926034dd85 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. +// - A couple helper functions for serializing/deserializing a KeyMapping +// to/from json. +// +// Author(s): +// - Mike Griese - May 2019 + +#include "pch.h" +#include "ActionMap.h" +#include "ActionAndArgs.h" +#include "KeyChordSerialization.h" +#include "JsonUtils.h" + +#include "Command.h" + +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Microsoft::Terminal::Settings::Model; + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + // Method Description: + // - Deserialize an ActionMap from the array `json`. The json array should contain + // an array of serialized `Command` objects. + // - These actions are added to the `ActionMap`, where we automatically handle + // overwriting and unbinding actions. + // Arguments: + // - json: an array of Json::Value's to deserialize into our ActionMap. + // Return value: + // - a list of warnings encountered while deserializing the json + std::vector ActionMap::LayerJson(const Json::Value& json) + { + // It's possible that the user provided keybindings have some warnings in + // them - problems that we should alert the user to, but we can recover + // from. Most of these warnings cannot be detected later in the Validate + // settings phase, so we'll collect them now. + std::vector warnings; + + for (const auto& cmdJson : json) + { + if (!cmdJson.isObject()) + { + continue; + } + + // TODO CARLOS: We get a serialization error here, but only on settings reload. + // "The application called an interface that was marshalled for a different thread." + AddAction(*Command::FromJson(cmdJson, warnings)); + } + + return warnings; + } + + // Method Description: + // - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML + // Return Value: + // - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used. + Windows::System::VirtualKeyModifiers ActionMap::ConvertVKModifiers(KeyModifiers modifiers) + { + Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None; + + if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl)) + { + keyModifiers |= Windows::System::VirtualKeyModifiers::Control; + } + if (WI_IsFlagSet(modifiers, KeyModifiers::Shift)) + { + keyModifiers |= Windows::System::VirtualKeyModifiers::Shift; + } + if (WI_IsFlagSet(modifiers, KeyModifiers::Alt)) + { + // note: Menu is the Alt VK_MENU + keyModifiers |= Windows::System::VirtualKeyModifiers::Menu; + } + if (WI_IsFlagSet(modifiers, KeyModifiers::Windows)) + { + keyModifiers |= Windows::System::VirtualKeyModifiers::Windows; + } + + return keyModifiers; + } +} diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 1cef8481b06..5f29ce710bf 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -186,9 +186,9 @@ IObservableVector Cascadia // - // Return Value: // - the globally configured keybindings -winrt::Microsoft::Terminal::Settings::Model::KeyMapping CascadiaSettings::KeyMap() const noexcept +winrt::Microsoft::Terminal::Settings::Model::ActionMap CascadiaSettings::ActionMap() const noexcept { - return _globals->KeyMap(); + return _globals->ActionMap(); } // Method Description: @@ -780,7 +780,7 @@ void CascadiaSettings::_ValidateKeybindings() void CascadiaSettings::_ValidateColorSchemesInCommands() { bool foundInvalidScheme{ false }; - for (const auto& nameAndCmd : _globals->Commands()) + for (const auto& nameAndCmd : _globals->ActionMap().NameMap()) { if (_HasInvalidColorScheme(nameAndCmd.Value())) { diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h index 2dbeb4eda24..bf8e3c87e2c 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.h +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.h @@ -70,7 +70,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::GlobalAppSettings GlobalSettings() const; Windows::Foundation::Collections::IObservableVector AllProfiles() const noexcept; Windows::Foundation::Collections::IObservableVector ActiveProfiles() const noexcept; - Model::KeyMapping KeyMap() const noexcept; + Model::ActionMap ActionMap() const noexcept; static com_ptr FromJson(const Json::Value& json); void LayerJson(const Json::Value& json); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl index eb71b85e463..cff8f42a7de 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.idl @@ -30,7 +30,7 @@ namespace Microsoft.Terminal.Settings.Model Windows.Foundation.Collections.IObservableVector AllProfiles { get; }; Windows.Foundation.Collections.IObservableVector ActiveProfiles { get; }; - KeyMapping KeyMap { get; }; + ActionMap ActionMap { get; }; Windows.Foundation.Collections.IVectorView Warnings { get; }; Windows.Foundation.IReference GetLoadingError { get; }; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index e63775a19f2..46d53af4774 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -248,57 +248,6 @@ winrt::Microsoft::Terminal::Settings::Model::CascadiaSettings CascadiaSettings:: // If this throws, the app will catch it and use the default settings resultPtr->_ValidateSettings(); - // GH 3855 - Gathering Data on custom profiles to inform better defaults - // Do it after everything else so it won't happen unless validation passed. - // Also, avoid processing unless someone's listening for measures. The keybindings work, at least, - // is a lot of computation we can skip if no one cares. - if (TraceLoggingProviderEnabled(g_hSettingsModelProvider, 0, MICROSOFT_KEYWORD_MEASURES)) - { - const auto guid = resultPtr->GlobalSettings().DefaultProfile(); - - // Compare to the defaults.json one that we set on install. - // If it's different, log what the user chose. - if (hardcodedDefaultGuid != guid) - { - TraceLoggingWrite( - g_hSettingsModelProvider, // handle to TerminalApp tracelogging provider - "CustomDefaultProfile", - TraceLoggingDescription("Event emitted when user has chosen a different default profile than hardcoded one on load/reload"), - TraceLoggingGuid(guid, "DefaultProfile", "ID of user-chosen default profile"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - - // If the user had keybinding settings preferences, we want to learn from them to make better defaults - auto userKeybindings = resultPtr->_userSettings[JsonKey(LegacyKeybindingsKey)]; - if (!userKeybindings.empty()) - { - // If there are custom key bindings, let's understand what they are because maybe the defaults aren't good enough - - // Run it through the object so we can parse it apart and then only serialize the fields we're interested in - // and avoid extraneous data. - auto km = winrt::make_self(); - km->LayerJson(userKeybindings); - auto value = km->ToJson(); - - // Reserialize the keybindings - Json::StreamWriterBuilder wbuilder; - // Use 4 spaces to indent instead of \t - wbuilder.settings_["indentation"] = " "; - wbuilder.settings_["enableYAMLCompatibility"] = true; // suppress spaces around colons - - const auto keybindingsString = Json::writeString(wbuilder, value); - - TraceLoggingWrite( - g_hSettingsModelProvider, // handle to TerminalApp tracelogging provider - "CustomKeybindings", - TraceLoggingDescription("Event emitted when custom keybindings are identified on load/reload"), - TraceLoggingUtf8String(keybindingsString.c_str(), "Keybindings", "Keybindings as JSON"), - TraceLoggingKeyword(MICROSOFT_KEYWORD_MEASURES), - TelemetryPrivacyDataTag(PDT_ProductAndServiceUsage)); - } - } - return *resultPtr; } catch (const SettingsException& ex) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 04b2b041fb9..1d23d389a3f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -43,6 +43,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { auto command{ winrt::make_self() }; command->_Name = _Name; + + // TODO GH#6900: We probably want ActionAndArgs::Copy here + // This is fine for now because SUI can't actually + // modify the copy yet. command->_Action = _Action; command->_Keys = _Keys; command->_IconPath = _IconPath; @@ -175,11 +179,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // { "name": "foo", "commands": null } will land in this case, which // should also be used for unbinding. - return nullptr; + + // create an "invalid" ActionAndArgs + result->Action(make()); + return result; } JsonUtils::GetValueForKey(json, IconKey, result->_IconPath); - JsonUtils::GetValueForKey(json, KeysKey, result->_Keys); // If we're a nested command, we can ignore the current action. if (!nested) @@ -196,8 +202,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // Something like // { name: "foo", action: "unbound" } - // will _remove_ the "foo" command, by returning null here. - return nullptr; + // will _remove_ the "foo" command, by returning an "invalid" action here. + result->Action(make()); + return result; } // If an iterable command doesn't have a name set, we'll still just @@ -211,7 +218,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // { name: "foo", action: null } will land in this case, which // should also be used for unbinding. - return nullptr; + + // create an "invalid" ActionAndArgs + result->Action(make()); + return result; + } + + // GH#4239 - If the user provided more than one key + // chord to a "keys" array, warn the user here. + // TODO: GH#1334 - remove this check. + const auto keysJson{ json[JsonKey(KeysKey)] }; + if (keysJson.isArray() && keysJson.size() > 1) + { + warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); + } + else + { + JsonUtils::GetValueForKey(json, KeysKey, result->_Keys); } } else @@ -224,11 +247,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // values we can iterate on are. result->_originalJson = json; - if (result->_Name.empty()) - { - return nullptr; - } - return result; } diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index 5c26b03d937..1f5e3395e8a 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -1,13 +1,33 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT license. -import "KeyMapping.idl"; +#include "AllShortcutActions.h" + +import "ActionArgs.idl"; import "Profile.idl"; import "ColorScheme.idl"; import "TerminalWarnings.idl"; namespace Microsoft.Terminal.Settings.Model { + enum ShortcutAction + { + Invalid = 0, // treat Invalid as unbound actions + + // When adding a new action, add them to AllShortcutActions.h! + #define ON_ALL_ACTIONS(action) action, + ALL_SHORTCUT_ACTIONS + #undef ON_ALL_ACTIONS + }; + + [default_interface] runtimeclass ActionAndArgs { + ActionAndArgs(); + ActionAndArgs(ShortcutAction action, IActionArgs args); + + IActionArgs Args; + ShortcutAction Action; + }; + [default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged { Command(); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 871b8f74f15..28977582c95 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -64,12 +64,11 @@ bool GlobalAppSettings::_getDefaultDebugFeaturesValue() } GlobalAppSettings::GlobalAppSettings() : - _keymap{ winrt::make_self() }, + _actionMap{ winrt::make_self() }, _keybindingsWarnings{}, _validDefaultProfile{ false }, _defaultProfile{} { - _commands = winrt::single_threaded_map(); _colorSchemes = winrt::single_threaded_map(); } @@ -85,10 +84,9 @@ void GlobalAppSettings::_FinalizeInheritance() FAIL_FAST_IF(_parents.size() > 1); for (auto parent : _parents) { - _keymap = std::move(parent->_keymap); + _actionMap->InsertParent(parent->_actionMap); _keybindingsWarnings = std::move(parent->_keybindingsWarnings); _colorSchemes = std::move(parent->_colorSchemes); - _commands = std::move(parent->_commands); } } @@ -128,10 +126,7 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_UnparsedDefaultProfile = _UnparsedDefaultProfile; globals->_validDefaultProfile = _validDefaultProfile; globals->_defaultProfile = _defaultProfile; - if (_keymap) - { - globals->_keymap = _keymap->Copy(); - } + globals->_actionMap = _actionMap->Copy(); std::copy(_keybindingsWarnings.begin(), _keybindingsWarnings.end(), std::back_inserter(globals->_keybindingsWarnings)); if (_colorSchemes) @@ -143,15 +138,6 @@ winrt::com_ptr GlobalAppSettings::Copy() const } } - if (_commands) - { - for (auto kv : _commands) - { - const auto commandImpl{ winrt::get_self(kv.Value()) }; - globals->_commands.Insert(kv.Key(), *commandImpl->Copy()); - } - } - // Globals only ever has 1 parent FAIL_FAST_IF(_parents.size() > 1); for (auto parent : _parents) @@ -232,9 +218,9 @@ std::optional GlobalAppSettings::_getUnparsedDefaultProfileImpl( } #pragma endregion -winrt::Microsoft::Terminal::Settings::Model::KeyMapping GlobalAppSettings::KeyMap() const noexcept +winrt::Microsoft::Terminal::Settings::Model::ActionMap GlobalAppSettings::ActionMap() const noexcept { - return *_keymap; + return *_actionMap; } // Method Description: @@ -326,7 +312,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) auto parseBindings = [this, &json](auto jsonKey) { if (auto bindings{ json[JsonKey(jsonKey)] }) { - auto warnings = _keymap->LayerJson(bindings); + auto warnings = _actionMap->LayerJson(bindings); + // It's possible that the user provided keybindings have some warnings // in them - problems that we should alert the user to, but we can // recover from. Most of these warnings cannot be detected later in the @@ -334,16 +321,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) // warnings generated from parsing these keybindings, add them to our // list of warnings. _keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end()); - - // Now parse the array again, but this time as a list of commands. - warnings = implementation::Command::LayerJson(_commands, bindings); - - // We cannot add all warnings, as some of them were already populated while parsing key mapping. - // Hence let's cherry-pick the ones relevant for command parsing. - if (std::count(warnings.begin(), warnings.end(), SettingsLoadWarnings::FailedToParseSubCommands)) - { - _keybindingsWarnings.push_back(SettingsLoadWarnings::FailedToParseSubCommands); - } } }; parseBindings(LegacyKeybindingsKey); @@ -380,11 +357,6 @@ std::vector G return _keybindingsWarnings; } -winrt::Windows::Foundation::Collections::IMapView GlobalAppSettings::Commands() noexcept -{ - return _commands.GetView(); -} - // Method Description: // - Create a new serialized JsonObject from an instance of this class // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 5f5f2456c9b..9bb72b7e91d 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -18,7 +18,7 @@ Author(s): #include "GlobalAppSettings.g.h" #include "IInheritable.h" -#include "KeyMapping.h" +#include "ActionMap.h" #include "Command.h" #include "ColorScheme.h" @@ -42,7 +42,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void AddColorScheme(const Model::ColorScheme& scheme); void RemoveColorScheme(hstring schemeName); - Model::KeyMapping KeyMap() const noexcept; + Model::ActionMap ActionMap() const noexcept; static com_ptr FromJson(const Json::Value& json); void LayerJson(const Json::Value& json); @@ -51,8 +51,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector KeybindingsWarnings() const; - Windows::Foundation::Collections::IMapView Commands() noexcept; - // These are implemented manually to handle the string/GUID exchange // by higher layers in the app. void DefaultProfile(const guid& defaultProfile) noexcept; @@ -97,11 +95,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional _UnparsedDefaultProfile{ std::nullopt }; bool _validDefaultProfile; - com_ptr _keymap; + com_ptr _actionMap; std::vector _keybindingsWarnings; Windows::Foundation::Collections::IMap _colorSchemes; - Windows::Foundation::Collections::IMap _commands; std::optional _getUnparsedDefaultProfileImpl() const; static bool _getDefaultDebugFeaturesValue(); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl index 48542a0b0af..b9694744b83 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.idl @@ -4,8 +4,7 @@ #include "IInheritable.idl.h" import "ColorScheme.idl"; -import "KeyMapping.idl"; -import "Command.idl"; +import "ActionMap.idl"; namespace Microsoft.Terminal.Settings.Model { @@ -73,8 +72,6 @@ namespace Microsoft.Terminal.Settings.Model void AddColorScheme(ColorScheme scheme); void RemoveColorScheme(String schemeName); - KeyMapping KeyMap(); - - Windows.Foundation.Collections.IMapView Commands(); + ActionMap ActionMap { get; }; } } diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.cpp b/src/cascadia/TerminalSettingsModel/KeyMapping.cpp deleted file mode 100644 index 880a6b34d4a..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeyMapping.cpp +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "KeyMapping.h" -#include "KeyChordSerialization.h" -#include "ActionAndArgs.h" - -#include "KeyMapping.g.cpp" - -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Microsoft::Terminal::Control; - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - winrt::com_ptr KeyMapping::Copy() const - { - auto keymap{ winrt::make_self() }; - std::for_each(_keyShortcuts.begin(), _keyShortcuts.end(), [keymap](auto& kv) { - const auto actionAndArgsImpl{ winrt::get_self(kv.second) }; - keymap->_keyShortcuts[kv.first] = *actionAndArgsImpl->Copy(); - }); - return keymap; - } - - Microsoft::Terminal::Settings::Model::ActionAndArgs KeyMapping::TryLookup(KeyChord const& chord) const - { - const auto result = _keyShortcuts.find(chord); - if (result != _keyShortcuts.end()) - { - return result->second; - } - return nullptr; - } - - uint64_t KeyMapping::Size() const - { - return _keyShortcuts.size(); - } - - void KeyMapping::SetKeyBinding(const Microsoft::Terminal::Settings::Model::ActionAndArgs& actionAndArgs, - const KeyChord& chord) - { - // if the chord is already mapped - clear the mapping - if (_keyShortcuts.find(chord) != _keyShortcuts.end()) - { - ClearKeyBinding(chord); - } - - _keyShortcuts[chord] = actionAndArgs; - _keyShortcutsByInsertionOrder.push_back(std::make_pair(chord, actionAndArgs)); - } - - // Method Description: - // - Remove the action that's bound to a particular KeyChord. - // Arguments: - // - chord: the keystroke to remove the action for. - // Return Value: - // - - void KeyMapping::ClearKeyBinding(const KeyChord& chord) - { - _keyShortcuts.erase(chord); - - KeyChordEquality keyChordEquality; - _keyShortcutsByInsertionOrder.erase(std::remove_if(_keyShortcutsByInsertionOrder.begin(), _keyShortcutsByInsertionOrder.end(), [keyChordEquality, chord](const auto& keyBinding) { - return keyChordEquality(keyBinding.first, chord); - }), - _keyShortcutsByInsertionOrder.end()); - } - - KeyChord KeyMapping::GetKeyBindingForAction(Microsoft::Terminal::Settings::Model::ShortcutAction const& action) - { - for (auto it = _keyShortcutsByInsertionOrder.rbegin(); it != _keyShortcutsByInsertionOrder.rend(); ++it) - { - const auto& kv = *it; - if (kv.second.Action() == action) - { - return kv.first; - } - } - return { nullptr }; - } - - // Method Description: - // - Lookup the keychord bound to a particular combination of ShortcutAction - // and IActionArgs. This enables searching no only for the binding of a - // particular ShortcutAction, but also a particular set of values for - // arguments to that action. - // If several bindings might match the lookup, prefers the one that was added last. - // Arguments: - // - actionAndArgs: The ActionAndArgs to lookup the keybinding for. - // Return Value: - // - The bound keychord, if this ActionAndArgs is bound to a key, otherwise nullptr. - KeyChord KeyMapping::GetKeyBindingForActionWithArgs(Microsoft::Terminal::Settings::Model::ActionAndArgs const& actionAndArgs) - { - if (actionAndArgs == nullptr) - { - return { nullptr }; - } - - for (auto it = _keyShortcutsByInsertionOrder.rbegin(); it != _keyShortcutsByInsertionOrder.rend(); ++it) - { - const auto& kv = *it; - const auto action = kv.second.Action(); - const auto args = kv.second.Args(); - const auto actionMatched = action == actionAndArgs.Action(); - const auto argsMatched = args ? args.Equals(actionAndArgs.Args()) : args == actionAndArgs.Args(); - if (actionMatched && argsMatched) - { - return kv.first; - } - } - return { nullptr }; - } - - // Method Description: - // - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML - // Return Value: - // - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used. - Windows::System::VirtualKeyModifiers KeyMapping::ConvertVKModifiers(KeyModifiers modifiers) - { - Windows::System::VirtualKeyModifiers keyModifiers = Windows::System::VirtualKeyModifiers::None; - - if (WI_IsFlagSet(modifiers, KeyModifiers::Ctrl)) - { - keyModifiers |= Windows::System::VirtualKeyModifiers::Control; - } - if (WI_IsFlagSet(modifiers, KeyModifiers::Shift)) - { - keyModifiers |= Windows::System::VirtualKeyModifiers::Shift; - } - if (WI_IsFlagSet(modifiers, KeyModifiers::Alt)) - { - // note: Menu is the Alt VK_MENU - keyModifiers |= Windows::System::VirtualKeyModifiers::Menu; - } - if (WI_IsFlagSet(modifiers, KeyModifiers::Windows)) - { - keyModifiers |= Windows::System::VirtualKeyModifiers::Windows; - } - - return keyModifiers; - } -} diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.h b/src/cascadia/TerminalSettingsModel/KeyMapping.h deleted file mode 100644 index ce5c811e389..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeyMapping.h +++ /dev/null @@ -1,80 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- KeyMapping.h - -Abstract: -- A mapping of key chords to actions. Includes (de)serialization logic. - -Author(s): -- Carlos Zamora - September 2020 - ---*/ - -#pragma once - -#include "KeyMapping.g.h" -#include "ActionArgs.h" -#include "../inc/cppwinrt_utils.h" - -// fwdecl unittest classes -namespace SettingsModelLocalTests -{ - class DeserializationTests; - class KeyBindingsTests; - class TestUtils; -} - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - struct KeyChordHash - { - std::size_t operator()(const Control::KeyChord& key) const - { - std::hash keyHash; - std::hash modifiersHash; - std::size_t hashedKey = keyHash(key.Vkey()); - std::size_t hashedMods = modifiersHash(key.Modifiers()); - return hashedKey ^ hashedMods; - } - }; - - struct KeyChordEquality - { - bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const - { - return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey(); - } - }; - - struct KeyMapping : KeyMappingT - { - KeyMapping() = default; - com_ptr Copy() const; - - Model::ActionAndArgs TryLookup(Control::KeyChord const& chord) const; - uint64_t Size() const; - - void SetKeyBinding(Model::ActionAndArgs const& actionAndArgs, - Control::KeyChord const& chord); - void ClearKeyBinding(Control::KeyChord const& chord); - Control::KeyChord GetKeyBindingForAction(Model::ShortcutAction const& action); - Control::KeyChord GetKeyBindingForActionWithArgs(Model::ActionAndArgs const& actionAndArgs); - - static Windows::System::VirtualKeyModifiers ConvertVKModifiers(Control::KeyModifiers modifiers); - - // Defined in KeyMappingSerialization.cpp - std::vector LayerJson(const Json::Value& json); - Json::Value ToJson(); - - private: - std::unordered_map _keyShortcuts; - std::vector> _keyShortcutsByInsertionOrder; - - friend class SettingsModelLocalTests::DeserializationTests; - friend class SettingsModelLocalTests::KeyBindingsTests; - friend class SettingsModelLocalTests::TestUtils; - }; -} diff --git a/src/cascadia/TerminalSettingsModel/KeyMapping.idl b/src/cascadia/TerminalSettingsModel/KeyMapping.idl deleted file mode 100644 index 540c74dea50..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeyMapping.idl +++ /dev/null @@ -1,39 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "AllShortcutActions.h" - -import "ActionArgs.idl"; - -namespace Microsoft.Terminal.Settings.Model -{ - enum ShortcutAction - { - Invalid = 0, - - // When adding a new action, add them to AllShortcutActions.h! - #define ON_ALL_ACTIONS(action) action, - ALL_SHORTCUT_ACTIONS - #undef ON_ALL_ACTIONS - }; - - [default_interface] runtimeclass ActionAndArgs { - ActionAndArgs(); - ActionAndArgs(ShortcutAction action, IActionArgs args); - - IActionArgs Args; - ShortcutAction Action; - }; - - [default_interface] runtimeclass KeyMapping - { - ActionAndArgs TryLookup(Microsoft.Terminal.Control.KeyChord chord); - UInt64 Size(); - - void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.Control.KeyChord chord); - void ClearKeyBinding(Microsoft.Terminal.Control.KeyChord chord); - - Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); - Microsoft.Terminal.Control.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs); - } -} diff --git a/src/cascadia/TerminalSettingsModel/KeyMappingSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyMappingSerialization.cpp deleted file mode 100644 index 24d6a4bee16..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeyMappingSerialization.cpp +++ /dev/null @@ -1,162 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. -// - A couple helper functions for serializing/deserializing a KeyMapping -// to/from json. -// -// Author(s): -// - Mike Griese - May 2019 - -#include "pch.h" -#include "KeyMapping.h" -#include "ActionAndArgs.h" -#include "KeyChordSerialization.h" -#include "JsonUtils.h" - -using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Microsoft::Terminal::Settings::Model; - -static constexpr std::string_view KeysKey{ "keys" }; -static constexpr std::string_view CommandKey{ "command" }; -static constexpr std::string_view ActionKey{ "action" }; - -// Function Description: -// - Small helper to create a json value serialization of a single -// KeyBinding->Action mapping. -// { -// keys:[String], -// command:String -// } -// Arguments: -// - chord: The KeyChord to serialize -// - actionName: the name of the ShortcutAction to use with this KeyChord -// Return Value: -// - a Json::Value which is an equivalent serialization of this object. -static Json::Value _ShortcutAsJsonObject(const KeyChord& chord, - const std::string_view actionName) -{ - const auto keyString = KeyChordSerialization::ToString(chord); - if (keyString == L"") - { - return nullptr; - } - - Json::Value jsonObject; - Json::Value keysArray; - keysArray.append(winrt::to_string(keyString)); - - jsonObject[JsonKey(KeysKey)] = keysArray; - jsonObject[JsonKey(CommandKey)] = actionName.data(); - - return jsonObject; -} - -// Method Description: -// - Serialize this KeyMapping to a json array of objects. Each object in -// the array represents a single keybinding, mapping a KeyChord to a -// ShortcutAction. -// Return Value: -// - a Json::Value which is an equivalent serialization of this object. -Json::Value winrt::Microsoft::Terminal::Settings::Model::implementation::KeyMapping::ToJson() -{ - Json::Value bindingsArray; - - // Iterate over all the possible actions in the names list, and see if - // it has a binding. - for (auto& actionName : ActionAndArgs::ActionKeyNamesMap) - { - const auto searchedForName = actionName.first; - const auto searchedForAction = actionName.second; - - if (const auto chord{ GetKeyBindingForAction(searchedForAction) }) - { - if (const auto serialization{ _ShortcutAsJsonObject(chord, searchedForName) }) - { - bindingsArray.append(serialization); - } - } - } - - return bindingsArray; -} - -// Method Description: -// - Deserialize a KeyMapping from the key mappings that are in the array -// `json`. The json array should contain an array of objects with both a -// `command` string and a `keys` array, where `command` is one of the names -// listed in `ActionAndArgs::ActionKeyNamesMap`, and `keys` is an array of -// keypresses. Currently, the array should contain a single string, which can -// be deserialized into a KeyChord. -// - Applies the deserialized keybindings to the provided `bindings` object. If -// a key chord in `json` is already bound to an action, that chord will be -// overwritten with the new action. If a chord is bound to `null` or -// `"unbound"`, then we'll clear the keybinding from the existing keybindings. -// Arguments: -// - json: an array of Json::Value's to deserialize into our _keyShortcuts mapping. -std::vector winrt::Microsoft::Terminal::Settings::Model::implementation::KeyMapping::LayerJson(const Json::Value& json) -{ - // It's possible that the user provided keybindings have some warnings in - // them - problems that we should alert the user to, but we can recover - // from. Most of these warnings cannot be detected later in the Validate - // settings phase, so we'll collect them now. - std::vector warnings; - - for (const auto& value : json) - { - if (!value.isObject()) - { - continue; - } - - const auto commandVal = value[JsonKey(CommandKey)]; - const auto keys = value[JsonKey(KeysKey)]; - - if (keys) - { - const auto validString = keys.isString(); - const auto validArray = keys.isArray() && keys.size() == 1; - - // GH#4239 - If the user provided more than one key - // chord to a "keys" array, warn the user here. - // TODO: GH#1334 - remove this check. - if (keys.isArray() && keys.size() > 1) - { - warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); - } - - if (!validString && !validArray) - { - continue; - } - const auto keyChordString = keys.isString() ? winrt::to_hstring(keys.asString()) : winrt::to_hstring(keys[0].asString()); - - // If the action was null, "unbound", or something we didn't - // understand, this will return nullptr. - auto actionAndArgs = ActionAndArgs::FromJson(commandVal, warnings); - - // Try parsing the chord - try - { - const auto chord = KeyChordSerialization::FromString(keyChordString); - - // If we couldn't find the action they want to set the chord to, - // or the action was `null` or `"unbound"`, just clear out the - // keybinding. Otherwise, set the keybinding to the action we - // found. - if (actionAndArgs) - { - SetKeyBinding(*actionAndArgs, chord); - } - else - { - ClearKeyBinding(chord); - } - } - catch (...) - { - continue; - } - } - } - - return warnings; -} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index ef8229af2db..a691c1bf16a 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -26,6 +26,9 @@ ActionArgs.idl + + ActionMap.idl + CascadiaSettings.idl @@ -46,9 +49,6 @@ KeyChordSerialization.idl - - KeyMapping.idl - Profile.idl @@ -84,6 +84,12 @@ ActionArgs.idl + + ActionMap.idl + + + ActionMap.idl + CascadiaSettings.idl @@ -104,12 +110,6 @@ KeyChordSerialization.idl - - KeyMapping.idl - - - KeyMapping.idl - Profile.idl @@ -134,12 +134,12 @@ + - diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 08b1d69afb8..142443d1f63 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -67,16 +67,17 @@ + - + @@ -89,4 +90,4 @@ {81a6314f-aa5b-4533-a499-13bc3a5c4af0} - + \ No newline at end of file diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 3c254610002..ef54d12cf10 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -22,6 +22,7 @@ + From bc996c01700509f76dbe5715744d37373d27fba9 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 9 Apr 2021 14:27:41 -0700 Subject: [PATCH 07/31] add action hashing; update command; fix test DllMain error --- .../DeserializationTests.cpp | 51 +-- .../KeyBindingsTests.cpp | 76 ++-- .../SettingsModel.LocalTests.vcxproj | 7 + .../TerminalSettingsTests.cpp | 3 +- .../LocalTests_TerminalApp/SettingsTests.cpp | 32 +- src/cascadia/TerminalApp/TerminalPage.cpp | 15 +- .../TerminalSettingsModel/ActionAndArgs.h | 1 + .../TerminalSettingsModel/ActionArgs.h | 124 +++++++ .../TerminalSettingsModel/ActionArgs.idl | 1 + .../TerminalSettingsModel/ActionMap.cpp | 347 +++++++----------- .../TerminalSettingsModel/ActionMap.h | 40 +- .../TerminalSettingsModel/ActionMap.idl | 6 +- .../TerminalSettingsModel/Command.cpp | 210 ++++++++--- src/cascadia/TerminalSettingsModel/Command.h | 24 +- .../TerminalSettingsModel/Command.idl | 7 +- 15 files changed, 567 insertions(+), 377 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index 14d2e7e7dd3..e07f15f8f81 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -1918,7 +1918,7 @@ namespace SettingsModelLocalTests const auto settingsObject = VerifyParseSucceeded(badSettings); auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().KeybindingCount()); + VERIFY_ARE_EQUAL(0u, settings->_globals->_actionMap->_KeyMap.size()); VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0)); @@ -1962,7 +1962,7 @@ namespace SettingsModelLocalTests auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().KeybindingCount()); + VERIFY_ARE_EQUAL(0u, settings->_globals->_actionMap->_KeyMap.size()); for (const auto& warning : settings->_globals->_keybindingsWarnings) { @@ -2104,12 +2104,13 @@ namespace SettingsModelLocalTests VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); auto actionMap = winrt::get_self(settings->_globals->ActionMap()); - VERIFY_ARE_EQUAL(5u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(5u, actionMap->_KeyMap.size()); // A/D, B, C, E will be in the list of commands, for 4 total. // * A and D share the same name, so they'll only generate a single action. // * F's name is set manually to `null` - VERIFY_ARE_EQUAL(4u, settings->_globals->ActionMap().CommandCount()); + const auto& nameMap{ actionMap->NameMap() }; + VERIFY_ARE_EQUAL(4u, nameMap.Size()); { KeyChord kc{ true, false, false, static_cast('A') }; @@ -2186,9 +2187,9 @@ namespace SettingsModelLocalTests } Log::Comment(L"Now verify the commands"); - _logCommandNames(actionMap->NameMap()); + _logCommandNames(nameMap); { - auto command = actionMap->GetActionByName(L"Split pane, split: vertical"); + auto command = nameMap.TryLookup(L"Split pane, split: vertical"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2204,7 +2205,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = actionMap->GetActionByName(L"ctrl+b"); + auto command = nameMap.TryLookup(L"ctrl+b"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2220,7 +2221,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = actionMap->GetActionByName(L"ctrl+c"); + auto command = nameMap.TryLookup(L"ctrl+c"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2236,7 +2237,7 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { - auto command = actionMap->GetActionByName(L"Split pane, split: horizontal"); + auto command = nameMap.TryLookup(L"Split pane, split: horizontal"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -2308,14 +2309,15 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); settings->_ValidateSettings(); - _logCommandNames(settings->ActionMap().NameMap()); + const auto& nameMap{ settings->ActionMap().NameMap() }; + _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); // Because the "parent" command didn't have a name, it couldn't be // placed into the list of commands. It and it's children are just // ignored. - VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(0u, nameMap.Size()); } void DeserializationTests::TestNestedCommandWithBadSubCommands() @@ -2361,7 +2363,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(2u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.GetAt(0)); VERIFY_ARE_EQUAL(SettingsLoadWarnings::FailedToParseSubCommands, settings->_warnings.GetAt(1)); - VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); + const auto& nameMap{ settings->ActionMap().NameMap() }; + VERIFY_ARE_EQUAL(0u, nameMap.Size()); } void DeserializationTests::TestUnbindNestedCommand() @@ -2430,10 +2433,11 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); settings->_ValidateSettings(); - _logCommandNames(settings->ActionMap().NameMap()); + const auto& nameMap{ settings->ActionMap().NameMap() }; + _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, settings->_globals->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(1u, nameMap.Size()); Log::Comment(L"Layer second bit of json, to unbind the original command."); @@ -2442,7 +2446,7 @@ namespace SettingsModelLocalTests settings->_ValidateSettings(); _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(0u, settings->_globals->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(0u, nameMap.Size()); } void DeserializationTests::TestRebindNestedCommand() @@ -2513,14 +2517,15 @@ namespace SettingsModelLocalTests const auto& actionMap{ settings->ActionMap() }; settings->_ValidateSettings(); - _logCommandNames(actionMap.NameMap()); + const auto& nameMap{ actionMap.NameMap() }; + _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, settings->_globals->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { winrt::hstring commandName{ L"parent" }; - auto commandProj = actionMap.GetActionByName(commandName); + auto commandProj = nameMap.TryLookup(commandName); VERIFY_IS_NOT_NULL(commandProj); winrt::com_ptr commandImpl; @@ -2536,11 +2541,11 @@ namespace SettingsModelLocalTests settings->_ValidateSettings(); _logCommandNames(settings->ActionMap().NameMap()); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); - VERIFY_ARE_EQUAL(1u, settings->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { winrt::hstring commandName{ L"parent" }; - auto commandProj = actionMap.GetActionByName(commandName); + auto commandProj = nameMap.TryLookup(commandName); VERIFY_IS_NOT_NULL(commandProj); auto actionAndArgs = commandProj.Action(); @@ -2636,8 +2641,10 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(settings->_globals->_colorSchemes.HasKey(schemeName), copyImpl->_globals->_colorSchemes.HasKey(schemeName)); // test actions - VERIFY_ARE_EQUAL(settings->ActionMap().KeybindingCount(), copyImpl->ActionMap().KeybindingCount()); - VERIFY_ARE_EQUAL(settings->ActionMap().CommandCount(), copyImpl->ActionMap().CommandCount()); + VERIFY_ARE_EQUAL(settings->_globals->_actionMap->_KeyMap.size(), copyImpl->_globals->_actionMap->_KeyMap.size()); + const auto& nameMapOriginal{ settings->_globals->_actionMap->NameMap() }; + const auto& nameMapCopy{ copyImpl->_globals->_actionMap->NameMap() }; + VERIFY_ARE_EQUAL(nameMapOriginal.Size(), nameMapCopy.Size()); // Test that changing the copy should not change the original VERIFY_ARE_EQUAL(settings->_globals->WordDelimiters(), copyImpl->_globals->WordDelimiters()); diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 2ec0519ae5e..971911d6bab 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -73,16 +73,16 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(4u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); } void KeyBindingsTests::LayerKeybindings() @@ -90,23 +90,23 @@ namespace SettingsModelLocalTests const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; const std::string bindings1String{ R"([ { "command": "paste", "keys": ["ctrl+c"] } ])" }; const std::string bindings2String{ R"([ { "command": "copy", "keys": ["enter"] } ])" }; - + DebugBreak(); const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings1Json = VerifyParseSucceeded(bindings1String); const auto bindings2Json = VerifyParseSucceeded(bindings2String); auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); } void KeyBindingsTests::UnbindKeybindings() @@ -127,50 +127,50 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings1Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `\"unbound\"` to unbind the key")); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `null` to unbind the key")); // First add back a good binding actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings3Json); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using an unrecognized command to unbind the key")); // First add back a good binding actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings4Json); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); Log::Comment(NoThrowString().Format( L"Try unbinding a key using a straight up invalid value to unbind the key")); // First add back a good binding actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings5Json); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); Log::Comment(NoThrowString().Format( L"Try unbinding a key that wasn't bound at all")); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); } void KeyBindingsTests::TestArbitraryArgs() @@ -196,9 +196,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(10u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size()); { Log::Comment(NoThrowString().Format( @@ -335,9 +335,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(4u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); { KeyChord kc{ true, false, false, static_cast('D') }; @@ -389,9 +389,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(3u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); { KeyChord kc{ true, false, false, static_cast('C') }; @@ -434,9 +434,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(1u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); { KeyChord kc{ true, false, false, static_cast('C') }; @@ -463,9 +463,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(6u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; @@ -528,7 +528,7 @@ namespace SettingsModelLocalTests const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(invalidActionMap); - VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } @@ -544,9 +544,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(2u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; @@ -570,14 +570,14 @@ namespace SettingsModelLocalTests const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": "moveTab" }])" }; auto actionMapNoArgs = winrt::make_self(); actionMapNoArgs->LayerJson(bindingsInvalidString); - VERIFY_ARE_EQUAL(0u, actionMapNoArgs->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMapNoArgs->_KeyMap.size()); } { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": { "action": "moveTab", "direction": "bad" } }])" }; const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(invalidActionMap); - VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } @@ -594,9 +594,9 @@ namespace SettingsModelLocalTests auto actionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(actionMap); - VERIFY_ARE_EQUAL(0u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); actionMap->LayerJson(bindings0Json); - VERIFY_ARE_EQUAL(3u, actionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); { KeyChord kc{ false, false, false, static_cast(VK_UP) }; @@ -630,7 +630,7 @@ namespace SettingsModelLocalTests const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_IS_NOT_NULL(invalidActionMap); - VERIFY_ARE_EQUAL(0u, invalidActionMap->KeybindingCount()); + VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } diff --git a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj index 7576770b44d..9a55e63152e 100644 --- a/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj +++ b/src/cascadia/LocalTests_SettingsModel/SettingsModel.LocalTests.vcxproj @@ -76,6 +76,13 @@ onecoreuap.lib;%(AdditionalDependencies) + + /INCLUDE:_DllMain@12 + /INCLUDE:DllMain diff --git a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp index cae0f1ad451..459fadc18fe 100644 --- a/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/TerminalSettingsTests.cpp @@ -109,7 +109,8 @@ namespace SettingsModelLocalTests const auto profile2Guid = settings.ActiveProfiles().GetAt(2).Guid(); VERIFY_ARE_NOT_EQUAL(winrt::guid{}, profile2Guid); - VERIFY_ARE_EQUAL(12u, actionMap.KeybindingCount()); + const auto& actionMapImpl{ winrt::get_self(actionMap) }; + VERIFY_ARE_EQUAL(12u, actionMapImpl->_KeyMap.size()); { KeyChord kc{ true, false, false, static_cast('A') }; diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index e7b8a6fade9..f5711043626 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -120,11 +120,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto actionMap{ settings.ActionMap() }; - VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); + auto nameMap{ settings.ActionMap().NameMap() }; + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - auto command = actionMap.GetActionByName(L"iterable command ${profile.name}"); + auto command = nameMap.TryLookup(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -141,7 +141,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -247,11 +247,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto actionMap{ settings.ActionMap() }; - VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); + auto nameMap{ settings.ActionMap().NameMap() }; + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - auto command = actionMap.GetActionByName(L"Split pane, profile: ${profile.name}"); + auto command = nameMap.TryLookup(L"Split pane, profile: ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -268,7 +268,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -376,11 +376,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto actionMap{ settings.ActionMap() }; - VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); + auto nameMap{ settings.ActionMap().NameMap() }; + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - auto command = actionMap.GetActionByName(L"iterable command ${profile.name}"); + auto command = nameMap.TryLookup(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -397,7 +397,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${profile.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); VERIFY_ARE_EQUAL(0u, settings.Warnings().Size()); @@ -1108,11 +1108,11 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(3u, settings.ActiveProfiles().Size()); - auto actionMap{ settings.ActionMap() }; - VERIFY_ARE_EQUAL(1u, actionMap.CommandCount()); + auto nameMap{ settings.ActionMap().NameMap() }; + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { - auto command = actionMap.GetActionByName(L"iterable command ${scheme.name}"); + auto command = nameMap.TryLookup(L"iterable command ${scheme.name}"); VERIFY_IS_NOT_NULL(command); auto actionAndArgs = command.Action(); VERIFY_IS_NOT_NULL(actionAndArgs); @@ -1129,7 +1129,7 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"${scheme.name}", realArgs.TerminalArgs().Profile()); } - auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(actionMap.NameMap(), settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); + auto expandedCommands = winrt::TerminalApp::implementation::TerminalPage::_ExpandCommands(nameMap, settings.ActiveProfiles().GetView(), settings.GlobalSettings().ColorSchemes()); _logCommandNames(expandedCommands.GetView()); // This is the same warning as above diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index d5aff6acf50..5a2239c9126 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -75,23 +75,18 @@ namespace winrt::TerminalApp::implementation for (const auto& nameAndCmd : commands) { const auto& command = nameAndCmd.Value(); - - // If there's a keybinding that's bound to exactly this command, - // then get the keychord and display it as a - // part of the command in the UI. Each Command's KeyChord is - // unset by default, so we don't need to worry about clearing it - // if there isn't a key associated with it. - // However, if a nested command tries to bind a KeyChord, the - // Command records it, but it is never actually bound to the keys. if (command.HasNestedCommands()) { - command.Keys(nullptr); _recursiveUpdateCommandKeybindingLabels(settings, command.NestedCommands()); } else { + // If there's a keybinding that's bound to exactly this command, + // then get the keychord and display it as a + // part of the command in the UI. + // We specifically need to do this for nested commands. const auto keyChord{ settings.ActionMap().GetKeyBindingForAction(command.Action().Action(), command.Action().Args()) }; - command.Keys(keyChord); + command.RegisterKey(keyChord); } } } diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h index 6bd1a659da4..5817f595ab8 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h @@ -4,6 +4,7 @@ #pragma once #include "ActionAndArgs.g.h" +#include "ActionArgs.h" #include "TerminalWarnings.h" #include "../inc/cppwinrt_utils.h" diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 68e77ef99f4..3d8597020f3 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -49,6 +49,34 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation using namespace ::Microsoft::Terminal::Settings::Model; using FromJsonResult = std::tuple>; + //template<> + //struct std::hash + //{ + // size_t operator()(const IActionArgs& args) + // { + // return gsl::narrow_cast(args.Hash()); + // } + //}; + + template + static size_t HashProperty(const IActionArgs& args) + { + return gsl::narrow_cast(args.Hash()); + } + + template + static size_t HashProperty(const T& val) + { + std::hash hashFunc; + return hashFunc(val); + } + + template + static size_t HashProperty(const T& val, Args&&... more) + { + return HashProperty(val) ^ HashProperty(std::forward(more)...); + } + struct ActionEventArgs : public ActionEventArgsT { ActionEventArgs() = default; @@ -124,6 +152,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_ColorScheme = _ColorScheme; return *copy; } + size_t Hash() const + { + return HashProperty(_Commandline, _StartingDirectory, _TabTitle, _TabColor, _ProfileIndex, _Profile, _SuppressApplicationTitle, _ColorScheme); + } }; struct CopyTextArgs : public CopyTextArgsT @@ -164,6 +196,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_CopyFormatting = _CopyFormatting; return *copy; } + size_t Hash() const + { + return HashProperty(_SingleLine, _CopyFormatting); + } }; struct NewTabArgs : public NewTabArgsT @@ -198,6 +234,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_TerminalArgs = _TerminalArgs.Copy(); return *copy; } + size_t Hash() const + { + return HashProperty(_TerminalArgs); + } }; struct SwitchToTabArgs : public SwitchToTabArgsT @@ -234,6 +274,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_TabIndex = _TabIndex; return *copy; } + size_t Hash() const + { + return HashProperty(_TabIndex); + } }; struct ResizePaneArgs : public ResizePaneArgsT @@ -275,6 +319,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_ResizeDirection = _ResizeDirection; return *copy; } + size_t Hash() const + { + return HashProperty(_ResizeDirection); + } }; struct MoveFocusArgs : public MoveFocusArgsT @@ -319,6 +367,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_FocusDirection = _FocusDirection; return *copy; } + size_t Hash() const + { + return HashProperty(_FocusDirection); + } }; struct AdjustFontSizeArgs : public AdjustFontSizeArgsT @@ -353,6 +405,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Delta = _Delta; return *copy; } + size_t Hash() const + { + return HashProperty(_Delta); + } }; struct SendInputArgs : public SendInputArgsT @@ -390,6 +446,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Input = _Input; return *copy; } + size_t Hash() const + { + return HashProperty(_Input); + } }; struct SplitPaneArgs : public SplitPaneArgsT @@ -457,6 +517,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_SplitSize = _SplitSize; return *copy; } + size_t Hash() const + { + return HashProperty(_SplitStyle, _TerminalArgs, _SplitMode, _SplitSize); + } }; struct OpenSettingsArgs : public OpenSettingsArgsT @@ -493,6 +557,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Target = _Target; return *copy; } + size_t Hash() const + { + return HashProperty(_Target); + } }; struct SetColorSchemeArgs : public SetColorSchemeArgsT @@ -533,6 +601,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_SchemeName = _SchemeName; return *copy; } + size_t Hash() const + { + return HashProperty(_SchemeName); + } }; struct SetTabColorArgs : public SetTabColorArgsT @@ -567,6 +639,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_TabColor = _TabColor; return *copy; } + size_t Hash() const + { + return HashProperty(_TabColor); + } }; struct RenameTabArgs : public RenameTabArgsT @@ -601,6 +677,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Title = _Title; return *copy; } + size_t Hash() const + { + return HashProperty(_Title); + } }; struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT @@ -641,6 +721,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Commandline = _Commandline; return *copy; } + size_t Hash() const + { + return HashProperty(_Commandline); + } }; struct CloseOtherTabsArgs : public CloseOtherTabsArgsT @@ -677,6 +761,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Index = _Index; return *copy; } + size_t Hash() const + { + return HashProperty(_Index); + } }; struct CloseTabsAfterArgs : public CloseTabsAfterArgsT @@ -713,6 +801,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Index = _Index; return *copy; } + size_t Hash() const + { + return HashProperty(_Index); + } }; struct MoveTabArgs : public MoveTabArgsT @@ -756,6 +848,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Direction = _Direction; return *copy; } + size_t Hash() const + { + return HashProperty(_Direction); + } }; struct ScrollUpArgs : public ScrollUpArgsT @@ -790,6 +886,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_RowsToScroll = _RowsToScroll; return *copy; } + size_t Hash() const + { + return HashProperty(_RowsToScroll); + } }; struct ScrollDownArgs : public ScrollDownArgsT @@ -824,6 +924,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_RowsToScroll = _RowsToScroll; return *copy; } + size_t Hash() const + { + return HashProperty(_RowsToScroll); + } }; struct ToggleCommandPaletteArgs : public ToggleCommandPaletteArgsT @@ -860,6 +964,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_LaunchMode = _LaunchMode; return *copy; } + size_t Hash() const + { + return HashProperty(_LaunchMode); + } }; struct FindMatchArgs : public FindMatchArgsT @@ -903,6 +1011,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Direction = _Direction; return *copy; } + size_t Hash() const + { + return HashProperty(_Direction); + } }; struct NewWindowArgs : public NewWindowArgsT @@ -937,6 +1049,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_TerminalArgs = _TerminalArgs.Copy(); return *copy; } + size_t Hash() const + { + return HashProperty(_TerminalArgs); + } }; struct PrevTabArgs : public PrevTabArgsT @@ -970,6 +1086,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_SwitcherMode = _SwitcherMode; return *copy; } + size_t Hash() const + { + return HashProperty(_SwitcherMode); + } }; struct NextTabArgs : public NextTabArgsT @@ -1003,6 +1123,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_SwitcherMode = _SwitcherMode; return *copy; } + size_t Hash() const + { + return HashProperty(_SwitcherMode); + } }; struct RenameWindowArgs : public RenameWindowArgsT diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index cb666d9b826..d66db340f81 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -8,6 +8,7 @@ namespace Microsoft.Terminal.Settings.Model Boolean Equals(IActionArgs other); String GenerateName(); IActionArgs Copy(); + UInt64 Hash(); }; interface IActionEventArgs diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 833c8db4ea6..37127b44c44 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -20,134 +20,99 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - actionID: the internal ID associated with a Command // Return Value: - // - If the command is valid, the command itself. Otherwise, null. - Model::Command ActionMap::_GetActionByID(size_t actionID) const + // - If the command is valid, the command itself. + // - If the command is explicitly unbound, nullptr. + // - If the command cannot be found in this layer, nullopt. + std::optional ActionMap::_GetActionByID(size_t actionID) const { - // ActionMap is not being maintained appropriately - // the given ID points out of bounds - FAIL_FAST_IF(actionID >= _ActionList.size()); - - const auto& cmd{ til::at(_ActionList, actionID) }; + const auto& cmdPair{ _ActionMap.find(actionID) }; + if (cmdPair == _ActionMap.end()) + { + // we don't have an answer + return std::nullopt; + } + else + { + const auto& cmd{ cmdPair->second }; - // ActionList should never point to nullptr - FAIL_FAST_IF_NULL(cmd); + // ActionMap should never point to nullptr + FAIL_FAST_IF_NULL(cmd); - return !cmd.HasNestedCommands() && cmd.Action().Action() == ShortcutAction::Invalid ? - nullptr : - cmd; + return !cmd.HasNestedCommands() && cmd.Action().Action() == ShortcutAction::Invalid ? + nullptr : // explicitly unbound + cmd; + } } - bool ActionMap::_IsActionIdValid(size_t actionID) const + // Method Description: + // - Retrieves a map of command names to the commands themselves + // - These commands should not be modified directly because they may result in + // an invalid state for the `ActionMap` + Windows::Foundation::Collections::IMapView ActionMap::NameMap() { - // ActionMap is not being maintained appropriately - // the given ID points out of bounds - FAIL_FAST_IF(actionID >= _ActionList.size()); - - const auto& cmd{ til::at(_ActionList, actionID) }; + if (_NameMapCache) + { + return _NameMapCache.GetView(); + } - // ActionList should never point to nullptr - FAIL_FAST_IF_NULL(cmd); + std::unordered_map nameMap{}; + std::set visitedActionIDs{ ActionHash()(make()) }; + _PopulateNameMap(nameMap, visitedActionIDs); - return cmd.HasNestedCommands() || cmd.Action().Action() != ShortcutAction::Invalid; + _NameMapCache = single_threaded_map(std::move(nameMap)); + return _NameMapCache.GetView(); } // Method Description: - // - Retrieves a map of command names to the commands themselves - // - These commands should not be modified directly because they may result in - // an invalid state for the `ActionMap` - Windows::Foundation::Collections::IMapView ActionMap::NameMap() const + // - Populates the provided nameMap with all of our actions and our parents actions + // while omitting the actions that were already added before + // Arguments: + // - nameMap: the nameMap we're populating. This maps the name (hstring) of a command to the command itself. + // There should only ever by one of each command (identified by the actionID) in the nameMap. + // - visitedActionIDs: the actionIDs that we've already added to the nameMap. Commands with a matching actionID + // have already been added, and should be ignored. + void ActionMap::_PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const { - // add everything from our parents - auto map{ single_threaded_map() }; - for (const auto& parent : _parents) + // Update NameMap and visitedActionIDs with our current layer + for (const auto& [actionID, cmd] : _ActionMap) { - const auto& inheritedNameMap{ parent->NameMap() }; - for (const auto& [key, val] : inheritedNameMap) + // Only populate NameMap with actions that haven't been visited already. + if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { - map.Insert(key, val); + const auto& name{ cmd.Name() }; + if (!name.empty()) + { + // Update NameMap. + nameMap.insert_or_assign(name, cmd); + } + + // Record that we already handled adding this action to the NameMap. + visitedActionIDs.insert(actionID); } } - // add everything from our layer - for (const auto& [name, actionID] : _NameMap) + // Update NameMap and visitedActionIDs with our parents + for (const auto& parent : _parents) { - if (const auto& cmd{ _GetActionByID(actionID) }) - { - map.Insert(name, cmd); - } + parent->_PopulateNameMap(nameMap, visitedActionIDs); } - return map.GetView(); } com_ptr ActionMap::Copy() const { auto actionMap{ make_self() }; - std::for_each(_NameMap.begin(), _NameMap.end(), [actionMap](const auto& pair) { - actionMap->_NameMap.insert(pair); - }); std::for_each(_KeyMap.begin(), _KeyMap.end(), [actionMap](const auto& pair) { actionMap->_KeyMap.insert(pair); }); - std::for_each(_ActionList.begin(), _ActionList.end(), [actionMap](const auto& cmd) { - const auto& cmdCopy{ get_self(cmd)->Copy() }; - actionMap->_ActionList.push_back(*cmdCopy); + std::for_each(_ActionMap.begin(), _ActionMap.end(), [actionMap](const auto& pair) { + const auto& cmdCopy{ get_self(pair.second)->Copy() }; + actionMap->_ActionMap.insert({ pair.first, *cmdCopy }); }); return actionMap; } - // Method Description: - // - [Recursive] The number of key bindings the ActionMap has - // access to. - size_t ActionMap::KeybindingCount() const noexcept - { - // NOTE: We cannot do _KeyMap.size() here because - // unbinding keys works by adding an "unbound"/"invalid" - // action to the KeyMap and ActionList. - // Thus, we must check the validity of each action in our KeyMap. - - // count the key bindings in our layer - size_t count{ 0 }; - for (const auto& [_, actionID] : _KeyMap) - { - if (_IsActionIdValid(actionID)) - { - ++count; - } - } - - // count the key bindings in our parents - for (const auto& parent : _parents) - { - count += parent->KeybindingCount(); - } - return count; - } - - // Method Description: - // - [Recursive] The number of commands the ActionMap has - // access to. - size_t ActionMap::CommandCount() const noexcept - { - // count the named commands in our layer - size_t count{ 0 }; - for (const auto& [_, actionID] : _NameMap) - { - if (_IsActionIdValid(actionID)) - { - ++count; - } - } - - // count the named commands in our parents - for (const auto& parent : _parents) - { - count += parent->CommandCount(); - } - return count; - } - // Method Description: // - Adds a command to the ActionMap // Arguments: @@ -161,112 +126,72 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // General Case: - // Add the new command to the NameMap and KeyMap (if applicable). - // These maps direct you to an entry in the ActionList. - // Unbinding Actions: - // Same as above, except the ActionList entry is specifically - // an unbound action. This is important for serialization. - - const auto actionID{ _ActionList.size() }; - _ActionList.push_back(cmd); - - // Update NameMap - const auto name{ cmd.Name() }; - if (!name.empty()) + // Add the new command to the NameMap and KeyMap (whichever is applicable). + // These maps direct you to an entry in the ActionMap. + + // Removing Actions from the Command Palette: + // cmd.Name and cmd.Action have a one-to-one relationship. + // If cmd.Name is empty, we must retrieve the old name and remove it. + + // Removing Key Bindings: + // cmd.Keys and cmd.Action have a many-to-one relationship. + // If cmd.Keys is empty, we don't care. + // If action is "unbound"/"invalid", you're explicitly unbinding the provided cmd.keys. + + Model::Command oldCmd{ nullptr }; + const auto actionID{ ActionHash()(cmd.Action()) }; + auto actionPair{ _ActionMap.find(actionID) }; + if (actionPair != _ActionMap.end()) { - const auto oldActionPair{ _NameMap.find(name) }; - if (oldActionPair != _NameMap.end()) - { - // the name was already in use. - // remove the old one. - auto& cmd{ til::at(_ActionList, oldActionPair->second) }; - cmd.Name(L""); + // We're adding an action that already exists. + // Record it and update it with any new information. + oldCmd = actionPair->second; - // and add the new one - _NameMap.insert_or_assign(name, actionID); - } - else + // Update Name + const auto cmdImpl{get_self(cmd)}; + if (cmdImpl->HasName()) { - // the name isn't mapped to anything, - // insert the actionID. - _NameMap.insert({ name, actionID }); + // This command has a name, check if it's new. + const auto newName{ cmd.Name() }; + if (newName != oldCmd.Name()) + { + // The new name differs from the old name, + // update our name. + auto oldCmdImpl{ get_self(oldCmd) }; + oldCmdImpl->Name(newName); + } } } + else + { + // add this action in for the first time + _ActionMap.insert({ actionID, cmd }); + } // Update KeyMap if (const auto keys{ cmd.Keys() }) { - const auto oldActionPair{ _KeyMap.find(keys) }; - if (oldActionPair != _KeyMap.end()) + const auto oldKeyPair{ _KeyMap.find(keys) }; + if (oldKeyPair != _KeyMap.end()) { - // the key chord was already in use. - // remove the old one. - auto& cmd{ til::at(_ActionList, oldActionPair->second) }; - cmd.Keys(nullptr); - - // and add the new one - _KeyMap.insert_or_assign(keys, actionID); - } - else - { - // the key chord isn't mapped to anything, - // insert the actionID. - _KeyMap.insert({ keys, actionID }); + // Collision: The key chord was already in use. + // Remove the old one. + actionPair = _ActionMap.find(oldKeyPair->second); + const auto& conflictingCmd{ actionPair->second }; + const auto& conflictingCmdImpl{ get_self(conflictingCmd) }; + conflictingCmdImpl->EraseKey(keys); } - } - } - // Method Description: - // - Retrieves the assigned command with the given name - // Arguments: - // - name: the name of the command to search for - // Return Value: - // - the command with the given name - // - nullptr if the name is explicitly unbound - Model::Command ActionMap::GetActionByName(hstring const& name) const - { - // Check the current layer - const auto& cmd{ _GetActionByNameInternal(name) }; - if (cmd.has_value()) - { - return *cmd; - } + // Assign the new action. + _KeyMap.insert_or_assign(keys, actionID); - // Check our parents - for (const auto& parent : _parents) - { - const auto& inheritedCmd{ parent->_GetActionByNameInternal(name) }; - if (inheritedCmd.has_value()) + if (oldCmd) { - return *inheritedCmd; + // Update inner Command with new key chord + auto oldCmdImpl{ get_self(oldCmd) }; + oldCmdImpl->RegisterKey(keys); } } - - // This action does not exist - return nullptr; - } - - // Method Description: - // - Retrieves the assigned command _in this layer_ with the given name - // Arguments: - // - name: the name of the command to search for - // Return Value: - // - the command with the given name - // - nullptr if the name is explicitly unbound - // - nullopt if it was not bound in this layer - std::optional ActionMap::_GetActionByNameInternal(hstring const& name) const - { - // Check the current layer - const auto actionIDPair{ _NameMap.find(name) }; - if (actionIDPair != _NameMap.end()) - { - // the action was explicitly bound, - // return what we found (invalid commands exposed as nullptr) - return _GetActionByID(actionIDPair->second); - } - - // the command was not bound in this layer - return std::nullopt; } // Method Description: @@ -285,22 +210,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return *cmd; } - // Check our parents - for (const auto& parent : _parents) - { - const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; - if (inheritedCmd.has_value()) - { - return *inheritedCmd; - } - } - - // This action does not exist + // This key chord is not explicitly bound return nullptr; } // Method Description: - // - Retrieves the assigned command _in this layer_ with the given key chord + // - Retrieves the assigned command with the given key chord. + // - Can return nullopt to differentiate explicit unbinding vs lack of binding. // Arguments: // - keys: the key chord of the command to search for // Return Value: @@ -318,7 +234,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _GetActionByID(actionIDPair->second); } - // the command was not bound in this layer + // the command was not bound in this layer, + // ask my parents + for (const auto& parent : _parents) + { + const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; + if (inheritedCmd.has_value()) + { + return *inheritedCmd; + } + } + + // This action is not explicitly bound return std::nullopt; } @@ -350,27 +277,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Check our internal state. - // Iterate through the list backwards so that we find the most recent change. - for (auto cmd{ _ActionList.rbegin() }; cmd != _ActionList.rend(); ++cmd) + const ActionAndArgs& actionAndArgs{ myAction, myArgs }; + const auto hash{ ActionHash()(actionAndArgs) }; + if (const auto& cmd{ _GetActionByID(hash) }) { - // breakdown cmd - const auto& actionAndArgs{ cmd->Action() }; - - if (!actionAndArgs || actionAndArgs.Action() == ShortcutAction::Invalid) - { - continue; - } - - const auto& action{ actionAndArgs.Action() }; - const auto& args{ actionAndArgs.Args() }; - - // check if we have a match - const auto actionMatched{ action == myAction }; - const auto argsMatched{ args ? args.Equals(myArgs) : args == myArgs }; - if (actionMatched && argsMatched) - { - return cmd->Keys(); - } + return cmd.value().Keys(); } // Check our parents diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index f18cb6c07bb..7778ac2a9fd 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -17,7 +17,6 @@ Author(s): #include "ActionMap.g.h" #include "IInheritable.h" -#include "ActionArgs.h" #include "Command.h" #include "../inc/cppwinrt_utils.h" @@ -25,6 +24,8 @@ Author(s): namespace SettingsModelLocalTests { class KeyBindingsTests; + class DeserializationTests; + class TerminalSettingsTests; } namespace winrt::Microsoft::Terminal::Settings::Model::implementation @@ -49,20 +50,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; + struct ActionHash + { + std::size_t operator()(const Model::ActionAndArgs& actionAndArgs) const + { + std::hash actionHash; + std::size_t hashedAction{ actionHash(actionAndArgs.Action()) }; + + std::hash argsHash; + std::size_t hashedArgs{ argsHash(nullptr) }; + if (const auto& args{ actionAndArgs.Args() }) + { + hashedArgs = args.Hash(); + } + return hashedAction ^ hashedArgs; + } + }; + struct ActionMap : ActionMapT, IInheritable { ActionMap() = default; - // capacity - size_t KeybindingCount() const noexcept; - size_t CommandCount() const noexcept; - // views - Windows::Foundation::Collections::IMapView NameMap() const; + Windows::Foundation::Collections::IMapView NameMap(); com_ptr Copy() const; // queries - Model::Command GetActionByName(hstring const& name) const; Model::Command GetActionByKeyChord(Control::KeyChord const& keys) const; Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action) const; Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action, IActionArgs const& actionArgs) const; @@ -74,16 +87,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static Windows::System::VirtualKeyModifiers ConvertVKModifiers(Control::KeyModifiers modifiers); private: - Model::Command _GetActionByID(size_t actionID) const; - bool _IsActionIdValid(size_t actionID) const; - - std::optional _GetActionByNameInternal(hstring const& name) const; + std::optional _GetActionByID(size_t actionID) const; std::optional _GetActionByKeyChordInternal(Control::KeyChord const& keys) const; - std::unordered_map _NameMap; + void _PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const; + + Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; std::unordered_map _KeyMap; - std::vector _ActionList; + std::unordered_map _ActionMap; friend class SettingsModelLocalTests::KeyBindingsTests; + friend class SettingsModelLocalTests::DeserializationTests; + friend class SettingsModelLocalTests::TerminalSettingsTests; }; } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 343483c6330..34a96de4b58 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -6,10 +6,10 @@ import "Command.idl"; namespace Microsoft.Terminal.Settings.Model { [default_interface] runtimeclass ActionMap { - UInt64 KeybindingCount { get; }; - UInt64 CommandCount { get; }; + //UInt64 KeybindingCount { get; }; + //UInt64 CommandCount { get; }; - Command GetActionByName(String name); + //Command GetActionByName(String name); Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 1d23d389a3f..39c4e83c7d1 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -36,20 +36,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Command::Command() { - _setAction(nullptr); } com_ptr Command::Copy() const { auto command{ winrt::make_self() }; - command->_Name = _Name; + command->_name = _name; // TODO GH#6900: We probably want ActionAndArgs::Copy here // This is fine for now because SUI can't actually // modify the copy yet. - command->_Action = _Action; - command->_Keys = _Keys; - command->_IconPath = _IconPath; + command->_action = _action; + std::for_each(_keyMappings.begin(), _keyMappings.end(), [command](const Control::KeyChord& keys) { + command->_keyMappings.push_back({ keys.Modifiers(), keys.Vkey() }); + }); + command->_iconPath = _iconPath; command->_IterateOn = _IterateOn; command->_originalJson = _originalJson; @@ -74,6 +75,142 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { return _subcommands ? _subcommands.Size() > 0 : false; } + + bool Command::HasName() const noexcept + { + return _name.has_value(); + } + + hstring Command::Name() const noexcept + { + if (_name.has_value()) + { + // name was explicitly set, return that value. + return hstring{ _name.value() }; + } + + return get_self(_action)->GenerateName(); + } + + void Command::Name(const hstring& value) + { + if (!_name.has_value() || _name.value() != value) + { + _name = value; + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Name" }); + } + } + + std::vector Command::KeyMappings() const noexcept + { + return _keyMappings; + } + + // Function Description: + // - Add the key chord to the command's list of key mappings. + // - If the key chord was already registered, move it to the back + // of the line, and dispatch a notification that Command::Keys changed. + // Arguments: + // - keys: the new key chord that we are registering this command to + // Return Value: + // - + void Command::RegisterKey(const Control::KeyChord& keys) + { + // Check if we registered this key chord before + for (auto pos = _keyMappings.begin(); pos < _keyMappings.end(); ++pos) + { + if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) + { + // KeyChord was already registered. + if (*pos == _keyMappings.back()) + { + // It's already the latest key registered. + return; + } + else + { + // Move the new KeyChord to the back of the line. + _keyMappings.erase(pos); + _keyMappings.push_back(*pos); + // TODO CARLOS: tests fail because this is on the wrong thread + //_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); + return; + } + } + } + + // Add the KeyChord to the back of the line. + _keyMappings.push_back(keys); + // TODO CARLOS: tests fail because this is on the wrong thread + //_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); + } + + // Function Description: + // - Remove the key chord from the command's list of key mappings. + // Arguments: + // - keys: the key chord that we are unregistering + // Return Value: + // - + void Command::EraseKey(const Control::KeyChord& keys) + { + for (auto pos = _keyMappings.begin(); pos < _keyMappings.end(); ++pos) + { + if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) + { + if (*pos == _keyMappings.back()) + { + // Keys has changed if we just unbound the primary KeyChord + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); + } + + // Found the KeyChord, remove it. + _keyMappings.erase(pos); + return; + } + } + } + + // Function Description: + // - Keys is the Command's identifying KeyChord. The command may have multiple keys associated + // with it, but we'll only ever display the most recently added one externally. To do this, + // _keyMappings stores all of the associated key chords, but ensures that the last entry + // is the most recently added one. + // Arguments: + // - + // Return Value: + // - the primary key chord associated with this Command + Control::KeyChord Command::Keys() const noexcept + { + if (_keyMappings.empty()) + { + return nullptr; + } + return _keyMappings.back(); + } + + Model::ActionAndArgs Command::Action() const noexcept + { + return _action; + } + + hstring Command::IconPath() const noexcept + { + if (_iconPath.has_value()) + { + return hstring{ *_iconPath }; + } + return {}; + } + + void Command::IconPath(const hstring& val) + { + if (!_iconPath.has_value() || _iconPath.value() != val) + { + _iconPath = val; + _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"IconPath" }); + } + } + // Function Description: // - attempt to get the name of this command from the provided json object. // * If the "name" property is a string, return that value. @@ -84,7 +221,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - json: The Json::Value representing the command object we should get the name for. // Return Value: // - the empty string if we couldn't find a name, otherwise the command's name. - static winrt::hstring _nameFromJson(const Json::Value& json) + static std::optional _nameFromJson(const Json::Value& json) { if (const auto name{ json[JsonKey(NameKey)] }) { @@ -94,43 +231,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (HasLibraryResourceWithName(*resourceKey)) { - return GetLibraryResourceString(*resourceKey); + return std::wstring{ GetLibraryResourceString(*resourceKey) }; } } } else if (name.isString()) { - return JsonUtils::GetValue(name); + return JsonUtils::GetValue(name); } } - return L""; - } - - // Method Description: - // - Get the name for the command specified in `json`. If there is no "name" - // property in the provided json object, then instead generate a name for - // the provided ActionAndArgs. - // Arguments: - // - json: json for the command to generate a name for. - // - actionAndArgs: An ActionAndArgs object to use to generate a name for, - // if the json object doesn't contain a "name". - // Return Value: - // - The "name" from the json, or the generated name from ActionAndArgs::GenerateName - static winrt::hstring _nameFromJsonOrAction(const Json::Value& json, - winrt::com_ptr actionAndArgs) - { - auto manualName = _nameFromJson(json); - if (!manualName.empty()) - { - return manualName; - } - if (!actionAndArgs) - { - return L""; - } - - return actionAndArgs->GenerateName(); + return std::nullopt; } // Method Description: @@ -181,11 +292,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // should also be used for unbinding. // create an "invalid" ActionAndArgs - result->Action(make()); - return result; + result->_action = make(); } - JsonUtils::GetValueForKey(json, IconKey, result->_IconPath); + JsonUtils::GetValueForKey(json, IconKey, result->_iconPath); // If we're a nested command, we can ignore the current action. if (!nested) @@ -196,14 +306,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (actionAndArgs) { - result->_setAction(*actionAndArgs); + result->_action = *actionAndArgs; } else { // Something like // { name: "foo", action: "unbound" } // will _remove_ the "foo" command, by returning an "invalid" action here. - result->Action(make()); + result->_action = make(); return result; } @@ -212,15 +322,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // currently have. It'll probably generate something like "New tab, // profile: ${profile.name}". This string will only be temporarily // used internally, so there's no problem. - result->_setName(_nameFromJsonOrAction(json, actionAndArgs)); + result->_name = _nameFromJson(json); } else { // { name: "foo", action: null } will land in this case, which // should also be used for unbinding. - + // create an "invalid" ActionAndArgs - result->Action(make()); + result->_action = make(); return result; } @@ -234,12 +344,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - JsonUtils::GetValueForKey(json, KeysKey, result->_Keys); + Control::KeyChord keys{ nullptr }; + if (JsonUtils::GetValueForKey(json, KeysKey, keys)) + { + result->RegisterKey(keys); + } } } else { - result->_setName(_nameFromJson(json)); + result->_name = _nameFromJson(json); } // Stash the original json value in this object. If the command is @@ -272,7 +386,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { try { - auto result = Command::FromJson(value, warnings); + const auto result = Command::FromJson(value, warnings); if (result) { // Override commands with the same name @@ -284,9 +398,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // name from the json blob. If that name currently // exists in our list of commands, we should remove it. const auto name = _nameFromJson(value); - if (!name.empty()) + if (name.has_value() && !name->empty()) { - commands.Remove(name); + commands.Remove(*name); } } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 659f090caff..e1ca2345a44 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -21,6 +21,7 @@ Author(s): #include "Command.g.h" #include "TerminalWarnings.h" #include "Profile.h" +#include "ActionAndArgs.h" #include "../inc/cppwinrt_utils.h" #include "SettingsTypes.h" @@ -51,19 +52,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool HasNestedCommands() const; Windows::Foundation::Collections::IMapView NestedCommands() const; + bool HasName() const noexcept; + hstring Name() const noexcept; + void Name(const hstring& name); + + Control::KeyChord Keys() const noexcept; + std::vector KeyMappings() const noexcept; + void RegisterKey(const Control::KeyChord& keys); + void EraseKey(const Control::KeyChord& keys); + + Model::ActionAndArgs Action() const noexcept; + + hstring IconPath() const noexcept; + void IconPath(const hstring& val); + winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker; WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(Model::ActionAndArgs, Action, _PropertyChangedHandlers); - WINRT_OBSERVABLE_PROPERTY(Control::KeyChord, Keys, _PropertyChangedHandlers, nullptr); - WINRT_OBSERVABLE_PROPERTY(winrt::hstring, IconPath, _PropertyChangedHandlers); - WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); private: Json::Value _originalJson; Windows::Foundation::Collections::IMap _subcommands{ nullptr }; + std::vector _keyMappings; + std::optional _name; + std::optional _iconPath; + Model::ActionAndArgs _action{}; static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index 1f5e3395e8a..c9a3c02f3c4 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -32,9 +32,10 @@ namespace Microsoft.Terminal.Settings.Model { Command(); - String Name; - ActionAndArgs Action; - Microsoft.Terminal.Control.KeyChord Keys; + String Name { get; }; + ActionAndArgs Action { get; }; + Microsoft.Terminal.Control.KeyChord Keys { get; }; + void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); String IconPath; From a00b84f86a07141bea7b29fc96c82c1294f35dd9 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 9 Apr 2021 16:59:13 -0700 Subject: [PATCH 08/31] fix most tests. Missing 8 DeserializationTests and 5 SettingsTests --- .../KeyBindingsTests.cpp | 17 +++--- .../TerminalSettingsModel/ActionAndArgs.cpp | 9 ++-- .../TerminalSettingsModel/Command.cpp | 53 +++++++------------ 3 files changed, 37 insertions(+), 42 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 971911d6bab..341d4479fa4 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -90,7 +90,7 @@ namespace SettingsModelLocalTests const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; const std::string bindings1String{ R"([ { "command": "paste", "keys": ["ctrl+c"] } ])" }; const std::string bindings2String{ R"([ { "command": "copy", "keys": ["enter"] } ])" }; - DebugBreak(); + const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings1Json = VerifyParseSucceeded(bindings1String); const auto bindings2Json = VerifyParseSucceeded(bindings2String); @@ -138,7 +138,8 @@ namespace SettingsModelLocalTests Log::Comment(NoThrowString().Format( L"Try unbinding a key using `\"unbound\"` to unbind the key")); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `null` to unbind the key")); @@ -147,7 +148,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings3Json); - VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); Log::Comment(NoThrowString().Format( L"Try unbinding a key using an unrecognized command to unbind the key")); @@ -156,7 +158,8 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings4Json); - VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); Log::Comment(NoThrowString().Format( L"Try unbinding a key using a straight up invalid value to unbind the key")); @@ -165,12 +168,14 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting actionMap->LayerJson(bindings5Json); - VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); Log::Comment(NoThrowString().Format( L"Try unbinding a key that wasn't bound at all")); actionMap->LayerJson(bindings2Json); - VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); } void KeyBindingsTests::TestArbitraryArgs() diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 7cdfc96796b..0ea0ff1010f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -200,7 +200,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // appended to this vector. // Return Value: // - a deserialized ActionAndArgs corresponding to the values in json, or - // null if we failed to deserialize an action. + // an "invalid" action if we failed to deserialize an action. winrt::com_ptr ActionAndArgs::FromJson(const Json::Value& json, std::vector& warnings) { @@ -255,7 +255,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if an arg parser was registered, but failed, bail if (pfn && args == nullptr) { - return nullptr; + return make_self(); } } @@ -269,7 +269,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - return nullptr; + // Something like + // { name: "foo", action: "unbound" } + // will _remove_ the "foo" command, by returning an "invalid" action here. + return make_self(); } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 39c4e83c7d1..66b995555d2 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -157,11 +157,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) { - if (*pos == _keyMappings.back()) - { - // Keys has changed if we just unbound the primary KeyChord - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); - } + // TODO CARLOS: tests fail because this is on the wrong thread + //if (*pos == _keyMappings.back()) + //{ + // // Keys has changed if we just unbound the primary KeyChord + // _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); + //} // Found the KeyChord, remove it. _keyMappings.erase(pos); @@ -302,27 +303,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto actionJson{ json[JsonKey(ActionKey)] }) { - auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings); - - if (actionAndArgs) - { - result->_action = *actionAndArgs; - } - else - { - // Something like - // { name: "foo", action: "unbound" } - // will _remove_ the "foo" command, by returning an "invalid" action here. - result->_action = make(); - return result; - } - - // If an iterable command doesn't have a name set, we'll still just - // try and generate a fake name for the command give the string we - // currently have. It'll probably generate something like "New tab, - // profile: ${profile.name}". This string will only be temporarily - // used internally, so there's no problem. - result->_name = _nameFromJson(json); + result->_action = *ActionAndArgs::FromJson(actionJson, warnings); } else { @@ -331,9 +312,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // create an "invalid" ActionAndArgs result->_action = make(); - return result; } + // If an iterable command doesn't have a name set, we'll still just + // try and generate a fake name for the command give the string we + // currently have. It'll probably generate something like "New tab, + // profile: ${profile.name}". This string will only be temporarily + // used internally, so there's no problem. + result->_name = _nameFromJson(json); + // GH#4239 - If the user provided more than one key // chord to a "keys" array, warn the user here. // TODO: GH#1334 - remove this check. @@ -387,12 +374,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation try { const auto result = Command::FromJson(value, warnings); - if (result) - { - // Override commands with the same name - commands.Insert(result->Name(), *result); - } - else + if (!result || !result->Action() || result->Action().Action() == ShortcutAction::Invalid) { // If there wasn't a parsed command, then try to get the // name from the json blob. If that name currently @@ -403,6 +385,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation commands.Remove(*name); } } + else + { + // Override commands with the same name + commands.Insert(result->Name(), *result); + } } CATCH_LOG(); } From 8840bc58f727d368033140364be13a2092cdeb6a Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 12 Apr 2021 17:07:04 -0700 Subject: [PATCH 09/31] fix nested commands --- .../TerminalSettingsModel/ActionMap.cpp | 32 ++++++++++++++++- .../TerminalSettingsModel/ActionMap.h | 3 +- .../TerminalSettingsModel/Command.cpp | 36 +++++++++++-------- 3 files changed, 54 insertions(+), 17 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 37127b44c44..3cf75f13129 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -11,6 +11,11 @@ using namespace winrt::Microsoft::Terminal::Control; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + ActionMap::ActionMap() : + _NestedCommands{ single_threaded_map() } + { + } + // Method Description: // - Retrieves the Command in the ActionList, if it's valid // - We internally store invalid commands as full commands. @@ -96,6 +101,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { parent->_PopulateNameMap(nameMap, visitedActionIDs); } + + // Add NestedCommands to NameMap _after_ we handle our parents. + // This allows us to override whatever our parents tell us. + for (const auto& [name, cmd] : _NestedCommands) + { + nameMap.insert_or_assign(name, cmd); + } } com_ptr ActionMap::Copy() const @@ -125,6 +137,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } + // _Always_ add nested commands + if (cmd.HasNestedCommands()) + { + // But check if it actually has a name to bind to first + const auto name{ cmd.Name() }; + if (!name.empty()) + { + _NestedCommands.Insert(name, cmd); + } + return; + } + // General Case: // Add the new command to the NameMap and KeyMap (whichever is applicable). // These maps direct you to an entry in the ActionMap. @@ -148,7 +172,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation oldCmd = actionPair->second; // Update Name - const auto cmdImpl{get_self(cmd)}; + const auto cmdImpl{ get_self(cmd) }; if (cmdImpl->HasName()) { // This command has a name, check if it's new. @@ -160,12 +184,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto oldCmdImpl{ get_self(oldCmd) }; oldCmdImpl->Name(newName); } + + // Handle a collision with NestedCommands + _NestedCommands.TryRemove(newName); } } else { // add this action in for the first time _ActionMap.insert({ actionID, cmd }); + + // Handle a collision with NestedCommands + _NestedCommands.TryRemove(cmd.Name()); } // Update KeyMap diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 7778ac2a9fd..74b397a7b2e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -69,7 +69,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct ActionMap : ActionMapT, IInheritable { - ActionMap() = default; + ActionMap(); // views Windows::Foundation::Collections::IMapView NameMap(); @@ -93,6 +93,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; + Windows::Foundation::Collections::IMap _NestedCommands{ nullptr }; std::unordered_map _KeyMap; std::unordered_map _ActionMap; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 66b995555d2..117df962456 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -88,8 +88,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // name was explicitly set, return that value. return hstring{ _name.value() }; } - - return get_self(_action)->GenerateName(); + else if (_action) + { + // generate a name from our action + return get_self(_action)->GenerateName(); + } + else + { + // we have no name + return {}; + } } void Command::Name(const hstring& value) @@ -281,8 +289,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (result->_subcommands.Size() == 0) { + const std::string x{ json.toStyledString().c_str() }; + OutputDebugString(til::u8u16(x).c_str()); warnings.push_back(SettingsLoadWarnings::FailedToParseSubCommands); - return nullptr; + result->_action = make(); } nested = true; @@ -314,13 +324,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation result->_action = make(); } - // If an iterable command doesn't have a name set, we'll still just - // try and generate a fake name for the command give the string we - // currently have. It'll probably generate something like "New tab, - // profile: ${profile.name}". This string will only be temporarily - // used internally, so there's no problem. - result->_name = _nameFromJson(json); - // GH#4239 - If the user provided more than one key // chord to a "keys" array, warn the user here. // TODO: GH#1334 - remove this check. @@ -338,10 +341,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } } - else - { - result->_name = _nameFromJson(json); - } + + // If an iterable command doesn't have a name set, we'll still just + // try and generate a fake name for the command give the string we + // currently have. It'll probably generate something like "New tab, + // profile: ${profile.name}". This string will only be temporarily + // used internally, so there's no problem. + result->_name = _nameFromJson(json); // Stash the original json value in this object. If the command is // iterable, we'll need to re-parse it later, once we know what all the @@ -374,7 +380,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation try { const auto result = Command::FromJson(value, warnings); - if (!result || !result->Action() || result->Action().Action() == ShortcutAction::Invalid) + if (result->Action().Action() == ShortcutAction::Invalid && !result->HasNestedCommands()) { // If there wasn't a parsed command, then try to get the // name from the json blob. If that name currently From 9a1af768301a92d0ec268f8465cb558a1ce4abc7 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 12 Apr 2021 17:35:33 -0700 Subject: [PATCH 10/31] code format and minor polish --- .../KeyBindingsTests.cpp | 2 +- .../TerminalSettingsEditor/Actions.xaml | 28 +++++++++---------- .../TerminalSettingsModel/ActionArgs.h | 13 +++------ 3 files changed, 19 insertions(+), 24 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 341d4479fa4..5b41607d425 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -90,7 +90,7 @@ namespace SettingsModelLocalTests const std::string bindings0String{ R"([ { "command": "copy", "keys": ["ctrl+c"] } ])" }; const std::string bindings1String{ R"([ { "command": "paste", "keys": ["ctrl+c"] } ])" }; const std::string bindings2String{ R"([ { "command": "copy", "keys": ["enter"] } ])" }; - + const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings1Json = VerifyParseSucceeded(bindings1String); const auto bindings2Json = VerifyParseSucceeded(bindings2String); diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index c2a25d1afdc..d54705bb623 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -30,7 +30,7 @@ * We don't need the chevron for nested commands * We're not displaying the icon * We're binding directly to a Command, not a FilteredCommand - + If we wanted to reuse the command palette's list more directly, that's theoretically possible, but then it would need to be lifted out of TerminalApp and either moved into the @@ -61,22 +61,22 @@ - - + Text="{x:Bind Name, Mode=OneWay}" /> + + + VerticalAlignment="Center" + Style="{ThemeResource KeyChordBorderStyle}" + Visibility="{x:Bind Keys, Mode=OneWay, Converter={StaticResource KeyChordToVisibilityConverter}}"> >; - //template<> - //struct std::hash - //{ - // size_t operator()(const IActionArgs& args) - // { - // return gsl::narrow_cast(args.Hash()); - // } - //}; - template static size_t HashProperty(const IActionArgs& args) { @@ -1160,6 +1151,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_Name = _Name; return *copy; } + size_t Hash() const + { + return HashProperty(_Name); + } }; } From d1b71eddb6410e3294f6bf4f3cadc1898a4dd2ab Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 12 Apr 2021 17:42:51 -0700 Subject: [PATCH 11/31] update dictionary --- .github/actions/spelling/dictionary/dictionary.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/actions/spelling/dictionary/dictionary.txt b/.github/actions/spelling/dictionary/dictionary.txt index 2b3236d31dc..acc9f04bf17 100644 --- a/.github/actions/spelling/dictionary/dictionary.txt +++ b/.github/actions/spelling/dictionary/dictionary.txt @@ -345242,6 +345242,8 @@ resequester resequestration reserate reserene +reserialize +reserializes reserialization reserpine reserpinized From 4fde1b08915b0bb7582846d6b17e344caca67f57 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 13 Apr 2021 16:09:01 -0700 Subject: [PATCH 12/31] Apply Griese's PR feedback --- .../TerminalApp/ActionPaletteItem.cpp | 4 +- src/cascadia/TerminalApp/AppKeyBindings.cpp | 2 +- src/cascadia/TerminalApp/CommandPalette.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 12 ++--- .../TerminalSettingsEditor/Actions.xaml | 9 ++-- .../TerminalSettingsEditor/Converters.idl | 10 +--- .../KeyChordConverter.cpp | 48 ------------------- .../KeyChordConverter.h | 6 +-- ...Microsoft.Terminal.Settings.Editor.vcxproj | 4 +- .../NullCheckConverter.cpp | 29 +++++++++++ .../NullCheckConverter.h | 9 ++++ .../TerminalSettingsModel/ActionAndArgs.cpp | 19 ++------ .../TerminalSettingsModel/ActionArgs.h | 25 +++------- .../TerminalSettingsModel/ActionMap.cpp | 19 ++++---- .../TerminalSettingsModel/ActionMap.h | 26 +++++----- .../TerminalSettingsModel/ActionMap.idl | 14 ------ .../ActionMapSerialization.cpp | 2 - .../CascadiaSettings.cpp | 2 +- .../TerminalSettingsModel/Command.cpp | 45 ++++++++--------- src/cascadia/TerminalSettingsModel/Command.h | 5 +- .../TerminalSettingsModel/Command.idl | 3 +- .../KeyChordSerialization.cpp | 26 +++++++--- src/cascadia/inc/cppwinrt_utils.h | 14 ++++++ 23 files changed, 150 insertions(+), 187 deletions(-) delete mode 100644 src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/NullCheckConverter.cpp create mode 100644 src/cascadia/TerminalSettingsEditor/NullCheckConverter.h diff --git a/src/cascadia/TerminalApp/ActionPaletteItem.cpp b/src/cascadia/TerminalApp/ActionPaletteItem.cpp index 807a15c5417..69014dca1f8 100644 --- a/src/cascadia/TerminalApp/ActionPaletteItem.cpp +++ b/src/cascadia/TerminalApp/ActionPaletteItem.cpp @@ -22,7 +22,7 @@ namespace winrt::TerminalApp::implementation _Command(command) { Name(command.Name()); - KeyChordText(KeyChordSerialization::ToString(command.Keys())); + KeyChordText(command.KeyChordText()); Icon(command.IconPath()); _commandChangedRevoker = command.PropertyChanged(winrt::auto_revoke, [weakThis{ get_weak() }](auto& sender, auto& e) { @@ -38,7 +38,7 @@ namespace winrt::TerminalApp::implementation } else if (changedProperty == L"KeyChordText") { - item->KeyChordText(KeyChordSerialization::ToString(senderCommand.Keys())); + item->KeyChordText(senderCommand.KeyChordText()); } else if (changedProperty == L"IconPath") { diff --git a/src/cascadia/TerminalApp/AppKeyBindings.cpp b/src/cascadia/TerminalApp/AppKeyBindings.cpp index a627252ae7a..9d3814135eb 100644 --- a/src/cascadia/TerminalApp/AppKeyBindings.cpp +++ b/src/cascadia/TerminalApp/AppKeyBindings.cpp @@ -16,7 +16,7 @@ namespace winrt::TerminalApp::implementation { if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) }) { - return _dispatch.DoAction(cmd.Action()); + return _dispatch.DoAction(cmd.ActionAndArgs()); } return false; } diff --git a/src/cascadia/TerminalApp/CommandPalette.cpp b/src/cascadia/TerminalApp/CommandPalette.cpp index 5214ae2b65d..46c75428049 100644 --- a/src/cascadia/TerminalApp/CommandPalette.cpp +++ b/src/cascadia/TerminalApp/CommandPalette.cpp @@ -266,13 +266,13 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; if (const auto cmd{ _actionMap.GetActionByKeyChord(kc) }) { - if (cmd.Action().Action() == ShortcutAction::PrevTab) + if (cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab) { SelectNextItem(false); e.Handled(true); return; } - else if (cmd.Action().Action() == ShortcutAction::NextTab) + else if (cmd.ActionAndArgs().Action() == ShortcutAction::NextTab) { SelectNextItem(true); e.Handled(true); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 5a2239c9126..a319a01e5f7 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -85,7 +85,7 @@ namespace winrt::TerminalApp::implementation // then get the keychord and display it as a // part of the command in the UI. // We specifically need to do this for nested commands. - const auto keyChord{ settings.ActionMap().GetKeyBindingForAction(command.Action().Action(), command.Action().Args()) }; + const auto keyChord{ settings.ActionMap().GetKeyBindingForAction(command.ActionAndArgs().Action(), command.ActionAndArgs().Args()) }; command.RegisterKey(keyChord); } } @@ -274,7 +274,7 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_OnDispatchCommandRequested(const IInspectable& /*sender*/, const Microsoft::Terminal::Settings::Model::Command& command) { - const auto& actionAndArgs = command.Action(); + const auto& actionAndArgs = command.ActionAndArgs(); _actionDispatch->DoAction(actionAndArgs); } @@ -889,11 +889,11 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; if (const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }) { - if (CommandPalette().Visibility() == Visibility::Visible && cmd.Action().Action() != ShortcutAction::ToggleCommandPalette) + if (CommandPalette().Visibility() == Visibility::Visible && cmd.ActionAndArgs().Action() != ShortcutAction::ToggleCommandPalette) { CommandPalette().Visibility(Visibility::Collapsed); } - _actionDispatch->DoAction(cmd.Action()); + _actionDispatch->DoAction(cmd.ActionAndArgs()); e.Handled(true); } } @@ -915,9 +915,9 @@ namespace winrt::TerminalApp::implementation winrt::Microsoft::Terminal::Control::KeyChord kc{ ctrlDown, altDown, shiftDown, static_cast(key) }; const auto cmd{ _settings.ActionMap().GetActionByKeyChord(kc) }; - if (cmd && (cmd.Action().Action() == ShortcutAction::CloseTab || cmd.Action().Action() == ShortcutAction::NextTab || cmd.Action().Action() == ShortcutAction::PrevTab || cmd.Action().Action() == ShortcutAction::ClosePane)) + if (cmd && (cmd.ActionAndArgs().Action() == ShortcutAction::CloseTab || cmd.ActionAndArgs().Action() == ShortcutAction::NextTab || cmd.ActionAndArgs().Action() == ShortcutAction::PrevTab || cmd.ActionAndArgs().Action() == ShortcutAction::ClosePane)) { - _actionDispatch->DoAction(cmd.Action()); + _actionDispatch->DoAction(cmd.ActionAndArgs()); e.Handled(true); } } diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index d54705bb623..92916c9c40e 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -19,8 +19,7 @@ - - + - + Text="{x:Bind KeyChordText, Mode=OneWay}" /> diff --git a/src/cascadia/TerminalSettingsEditor/Converters.idl b/src/cascadia/TerminalSettingsEditor/Converters.idl index 99b91440680..7ddc645b3fc 100644 --- a/src/cascadia/TerminalSettingsEditor/Converters.idl +++ b/src/cascadia/TerminalSettingsEditor/Converters.idl @@ -59,14 +59,8 @@ namespace Microsoft.Terminal.Settings.Editor DesktopWallpaperToEmptyStringConverter(); }; - runtimeclass KeyChordToVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter + runtimeclass NullCheckToVisibilityConverter : [default] Windows.UI.Xaml.Data.IValueConverter { - KeyChordToVisibilityConverter(); + NullCheckToVisibilityConverter(); }; - - runtimeclass KeyChordToStringConverter : [default] Windows.UI.Xaml.Data.IValueConverter - { - KeyChordToStringConverter(); - }; - } diff --git a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp deleted file mode 100644 index dc42a21c71c..00000000000 --- a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.cpp +++ /dev/null @@ -1,48 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "KeyChordConverter.h" -#include "KeyChordToStringConverter.g.cpp" -#include "KeyChordToVisibilityConverter.g.cpp" - -using namespace winrt::Windows; -using namespace winrt::Windows::UI::Xaml; -using namespace winrt::Windows::UI::Text; - -namespace winrt::Microsoft::Terminal::Settings::Editor::implementation -{ - Foundation::IInspectable KeyChordToStringConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - const auto& keys{ winrt::unbox_value(value) }; - return box_value(Model::KeyChordSerialization::ToString(keys)); - } - - Foundation::IInspectable KeyChordToStringConverter::ConvertBack(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - const auto& keys{ winrt::unbox_value(value) }; - return box_value(Model::KeyChordSerialization::FromString(keys)); - } - - Foundation::IInspectable KeyChordToVisibilityConverter::Convert(Foundation::IInspectable const& value, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /* parameter */, - hstring const& /* language */) - { - return box_value(value ? Visibility::Visible : Visibility::Collapsed); - } - - Foundation::IInspectable KeyChordToVisibilityConverter::ConvertBack(Foundation::IInspectable const& /*value*/, - Windows::UI::Xaml::Interop::TypeName const& /* targetType */, - Foundation::IInspectable const& /*parameter*/, - hstring const& /* language */) - { - throw hresult_not_implemented(); - } -} diff --git a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h index 0f2a08a98f0..dd65a07cfbd 100644 --- a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h +++ b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h @@ -3,9 +3,7 @@ #pragma once -#include "KeyChordToStringConverter.g.h" -#include "KeyChordToVisibilityConverter.g.h" +#include "NullCheckToVisibilityConverter.g.h" #include "../inc/cppwinrt_utils.h" -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, KeyChordToStringConverter); -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, KeyChordToVisibilityConverter); +DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, NullCheckToVisibilityConverter); diff --git a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj index 72922802b9a..c87a23a7a89 100644 --- a/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj +++ b/src/cascadia/TerminalSettingsEditor/Microsoft.Terminal.Settings.Editor.vcxproj @@ -56,7 +56,7 @@ Converters.idl - + Converters.idl @@ -159,7 +159,7 @@ Converters.idl - + Converters.idl diff --git a/src/cascadia/TerminalSettingsEditor/NullCheckConverter.cpp b/src/cascadia/TerminalSettingsEditor/NullCheckConverter.cpp new file mode 100644 index 00000000000..93a19b4687a --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NullCheckConverter.cpp @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "NullCheckConverter.h" +#include "NullCheckToVisibilityConverter.g.cpp" + +using namespace winrt::Windows; +using namespace winrt::Windows::UI::Xaml; +using namespace winrt::Windows::UI::Text; + +namespace winrt::Microsoft::Terminal::Settings::Editor::implementation +{ + Foundation::IInspectable NullCheckToVisibilityConverter::Convert(Foundation::IInspectable const& value, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /* parameter */, + hstring const& /* language */) + { + return box_value(value ? Visibility::Visible : Visibility::Collapsed); + } + + Foundation::IInspectable NullCheckToVisibilityConverter::ConvertBack(Foundation::IInspectable const& /*value*/, + Windows::UI::Xaml::Interop::TypeName const& /* targetType */, + Foundation::IInspectable const& /*parameter*/, + hstring const& /* language */) + { + throw hresult_not_implemented(); + } +} diff --git a/src/cascadia/TerminalSettingsEditor/NullCheckConverter.h b/src/cascadia/TerminalSettingsEditor/NullCheckConverter.h new file mode 100644 index 00000000000..dd65a07cfbd --- /dev/null +++ b/src/cascadia/TerminalSettingsEditor/NullCheckConverter.h @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#pragma once + +#include "NullCheckToVisibilityConverter.g.h" +#include "../inc/cppwinrt_utils.h" + +DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, NullCheckToVisibilityConverter); diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 0ea0ff1010f..b2607dafdc4 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -259,21 +259,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - if (action != ShortcutAction::Invalid) - { - auto actionAndArgs = winrt::make_self(); - actionAndArgs->Action(action); - actionAndArgs->Args(args); - - return actionAndArgs; - } - else - { - // Something like - // { name: "foo", action: "unbound" } - // will _remove_ the "foo" command, by returning an "invalid" action here. - return make_self(); - } + // Something like + // { name: "foo", action: "unbound" } + // will _remove_ the "foo" command, by returning an "invalid" action here. + return make_self(action, args); } com_ptr ActionAndArgs::Copy() const diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index eae4dadc82d..d8ed43ac4e6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -44,30 +44,17 @@ // * ActionEventArgs holds a single IActionArgs. For events that don't need // additional args, this can be nullptr. +template<> +static size_t HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args) +{ + return gsl::narrow_cast(args.Hash()); +} + namespace winrt::Microsoft::Terminal::Settings::Model::implementation { using namespace ::Microsoft::Terminal::Settings::Model; using FromJsonResult = std::tuple>; - template - static size_t HashProperty(const IActionArgs& args) - { - return gsl::narrow_cast(args.Hash()); - } - - template - static size_t HashProperty(const T& val) - { - std::hash hashFunc; - return hashFunc(val); - } - - template - static size_t HashProperty(const T& val, Args&&... more) - { - return HashProperty(val) ^ HashProperty(std::forward(more)...); - } - struct ActionEventArgs : public ActionEventArgsT { ActionEventArgs() = default; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 3cf75f13129..d021920f44e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -28,7 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - If the command is valid, the command itself. // - If the command is explicitly unbound, nullptr. // - If the command cannot be found in this layer, nullopt. - std::optional ActionMap::_GetActionByID(size_t actionID) const + std::optional ActionMap::_GetActionByID(InternalActionID actionID) const { const auto& cmdPair{ _ActionMap.find(actionID) }; if (cmdPair == _ActionMap.end()) @@ -43,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); - return !cmd.HasNestedCommands() && cmd.Action().Action() == ShortcutAction::Invalid ? + return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? nullptr : // explicitly unbound cmd; } @@ -61,7 +61,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } std::unordered_map nameMap{}; - std::set visitedActionIDs{ ActionHash()(make()) }; + std::set visitedActionIDs{ ActionHash()(make()) }; _PopulateNameMap(nameMap, visitedActionIDs); _NameMapCache = single_threaded_map(std::move(nameMap)); @@ -76,7 +76,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // There should only ever by one of each command (identified by the actionID) in the nameMap. // - visitedActionIDs: the actionIDs that we've already added to the nameMap. Commands with a matching actionID // have already been added, and should be ignored. - void ActionMap::_PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const + void ActionMap::_PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const { // Update NameMap and visitedActionIDs with our current layer for (const auto& [actionID, cmd] : _ActionMap) @@ -118,10 +118,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_KeyMap.insert(pair); }); - std::for_each(_ActionMap.begin(), _ActionMap.end(), [actionMap](const auto& pair) { - const auto& cmdCopy{ get_self(pair.second)->Copy() }; - actionMap->_ActionMap.insert({ pair.first, *cmdCopy }); - }); + for(const auto& [actionID, cmd] : _ActionMap) + { + actionMap->_ActionMap.insert({ actionID, *(get_self(cmd)->Copy()) }); + } + return actionMap; } @@ -163,7 +164,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // If action is "unbound"/"invalid", you're explicitly unbinding the provided cmd.keys. Model::Command oldCmd{ nullptr }; - const auto actionID{ ActionHash()(cmd.Action()) }; + const auto actionID{ ActionHash()(cmd.ActionAndArgs()) }; auto actionPair{ _ActionMap.find(actionID) }; if (actionPair != _ActionMap.end()) { diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 74b397a7b2e..960db3d974f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -30,15 +30,13 @@ namespace SettingsModelLocalTests namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + typedef size_t InternalActionID; + struct KeyChordHash { std::size_t operator()(const Control::KeyChord& key) const { - std::hash keyHash; - std::hash modifiersHash; - std::size_t hashedKey = keyHash(key.Vkey()); - std::size_t hashedMods = modifiersHash(key.Modifiers()); - return hashedKey ^ hashedMods; + return ::HashProperty(key.Modifiers(), key.Vkey()); } }; @@ -52,17 +50,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct ActionHash { - std::size_t operator()(const Model::ActionAndArgs& actionAndArgs) const + InternalActionID operator()(const Model::ActionAndArgs& actionAndArgs) const { std::hash actionHash; std::size_t hashedAction{ actionHash(actionAndArgs.Action()) }; - std::hash argsHash; - std::size_t hashedArgs{ argsHash(nullptr) }; + std::size_t hashedArgs; if (const auto& args{ actionAndArgs.Args() }) { hashedArgs = args.Hash(); } + else + { + std::hash argsHash; + hashedArgs = argsHash(nullptr); + } return hashedAction ^ hashedArgs; } }; @@ -87,15 +89,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static Windows::System::VirtualKeyModifiers ConvertVKModifiers(Control::KeyModifiers modifiers); private: - std::optional _GetActionByID(size_t actionID) const; + std::optional _GetActionByID(InternalActionID actionID) const; std::optional _GetActionByKeyChordInternal(Control::KeyChord const& keys) const; - void _PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const; + void _PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; Windows::Foundation::Collections::IMap _NestedCommands{ nullptr }; - std::unordered_map _KeyMap; - std::unordered_map _ActionMap; + std::unordered_map _KeyMap; + std::unordered_map _ActionMap; friend class SettingsModelLocalTests::KeyBindingsTests; friend class SettingsModelLocalTests::DeserializationTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 34a96de4b58..f4a75d04ffa 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -6,25 +6,11 @@ import "Command.idl"; namespace Microsoft.Terminal.Settings.Model { [default_interface] runtimeclass ActionMap { - //UInt64 KeybindingCount { get; }; - //UInt64 CommandCount { get; }; - - //Command GetActionByName(String name); Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); Windows.Foundation.Collections.IMapView NameMap { get; }; - - //ActionAndArgs TryLookup(Microsoft.Terminal.Control.KeyChord chord); - - //UInt64 Size(); - // - //void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.Control.KeyChord chord); - //void ClearKeyBinding(Microsoft.Terminal.Control.KeyChord chord); - // - //Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); - //Microsoft.Terminal.Control.KeyChord GetKeyBindingForActionWithArgs(ActionAndArgs actionAndArgs); } } diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 7926034dd85..766cd9260c1 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -43,8 +43,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation continue; } - // TODO CARLOS: We get a serialization error here, but only on settings reload. - // "The application called an interface that was marshalled for a different thread." AddAction(*Command::FromJson(cmdJson, warnings)); } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp index 5f29ce710bf..5cc5435d168 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettings.cpp @@ -809,7 +809,7 @@ bool CascadiaSettings::_HasInvalidColorScheme(const Model::Command& command) } } } - else if (const auto& actionAndArgs = command.Action()) + else if (const auto& actionAndArgs = command.ActionAndArgs()) { if (const auto& realArgs = actionAndArgs.Args().try_as()) { diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 117df962456..8920e6c9e8e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -46,10 +46,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // TODO GH#6900: We probably want ActionAndArgs::Copy here // This is fine for now because SUI can't actually // modify the copy yet. - command->_action = _action; - std::for_each(_keyMappings.begin(), _keyMappings.end(), [command](const Control::KeyChord& keys) { - command->_keyMappings.push_back({ keys.Modifiers(), keys.Vkey() }); - }); + command->_ActionAndArgs = _ActionAndArgs; + for (const auto& keys : _keyMappings) + { + command->_keyMappings.emplace_back(keys.Modifiers(), keys.Vkey()); + } command->_iconPath = _iconPath; command->_IterateOn = _IterateOn; @@ -88,10 +89,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // name was explicitly set, return that value. return hstring{ _name.value() }; } - else if (_action) + else if (_ActionAndArgs) { // generate a name from our action - return get_self(_action)->GenerateName(); + return get_self(_ActionAndArgs)->GenerateName(); } else { @@ -124,6 +125,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void Command::RegisterKey(const Control::KeyChord& keys) { + if (!keys) + { + return; + } + // Check if we registered this key chord before for (auto pos = _keyMappings.begin(); pos < _keyMappings.end(); ++pos) { @@ -140,8 +146,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Move the new KeyChord to the back of the line. _keyMappings.erase(pos); _keyMappings.push_back(*pos); - // TODO CARLOS: tests fail because this is on the wrong thread - //_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); return; } } @@ -149,8 +153,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Add the KeyChord to the back of the line. _keyMappings.push_back(keys); - // TODO CARLOS: tests fail because this is on the wrong thread - //_PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); } // Function Description: @@ -165,13 +167,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) { - // TODO CARLOS: tests fail because this is on the wrong thread - //if (*pos == _keyMappings.back()) - //{ - // // Keys has changed if we just unbound the primary KeyChord - // _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Keys" }); - //} - // Found the KeyChord, remove it. _keyMappings.erase(pos); return; @@ -197,9 +192,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _keyMappings.back(); } - Model::ActionAndArgs Command::Action() const noexcept + hstring Command::KeyChordText() const noexcept { - return _action; + return KeyChordSerialization::ToString(Keys()); } hstring Command::IconPath() const noexcept @@ -289,10 +284,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (result->_subcommands.Size() == 0) { - const std::string x{ json.toStyledString().c_str() }; - OutputDebugString(til::u8u16(x).c_str()); warnings.push_back(SettingsLoadWarnings::FailedToParseSubCommands); - result->_action = make(); + result->_ActionAndArgs = make(); } nested = true; @@ -303,7 +296,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // should also be used for unbinding. // create an "invalid" ActionAndArgs - result->_action = make(); + result->_ActionAndArgs = make(); } JsonUtils::GetValueForKey(json, IconKey, result->_iconPath); @@ -313,7 +306,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto actionJson{ json[JsonKey(ActionKey)] }) { - result->_action = *ActionAndArgs::FromJson(actionJson, warnings); + result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); } else { @@ -321,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // should also be used for unbinding. // create an "invalid" ActionAndArgs - result->_action = make(); + result->_ActionAndArgs = make(); } // GH#4239 - If the user provided more than one key @@ -380,7 +373,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation try { const auto result = Command::FromJson(value, warnings); - if (result->Action().Action() == ShortcutAction::Invalid && !result->HasNestedCommands()) + if (result->ActionAndArgs().Action() == ShortcutAction::Invalid && !result->HasNestedCommands()) { // If there wasn't a parsed command, then try to get the // name from the json blob. If that name currently diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index e1ca2345a44..f4fdd9deadd 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -57,12 +57,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void Name(const hstring& name); Control::KeyChord Keys() const noexcept; + hstring KeyChordText() const noexcept; std::vector KeyMappings() const noexcept; void RegisterKey(const Control::KeyChord& keys); void EraseKey(const Control::KeyChord& keys); - Model::ActionAndArgs Action() const noexcept; - hstring IconPath() const noexcept; void IconPath(const hstring& val); @@ -70,6 +69,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler); WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); + WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs); private: Json::Value _originalJson; @@ -77,7 +77,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector _keyMappings; std::optional _name; std::optional _iconPath; - Model::ActionAndArgs _action{}; static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index c9a3c02f3c4..b749a79fb0a 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -33,9 +33,10 @@ namespace Microsoft.Terminal.Settings.Model Command(); String Name { get; }; - ActionAndArgs Action { get; }; + ActionAndArgs ActionAndArgs { get; }; Microsoft.Terminal.Control.KeyChord Keys { get; }; void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); + String KeyChordText { get; }; String IconPath; diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index dadc48520a5..d0804198cf6 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -105,12 +105,12 @@ static const std::unordered_map vkeyNamePairs { // - hstr: the string to parse into a keychord. // Return Value: // - a newly constructed KeyChord -static KeyChord _FromString(const std::wstring& wstr) +static KeyChord _FromString(const std::wstring_view& wstr) { // Split the string on '+' std::wstring temp; std::vector parts; - std::wstringstream wss(wstr); + std::wstringstream wss(wstr.data()); while (std::getline(wss, temp, L'+')) { @@ -301,7 +301,7 @@ static std::wstring _ToString(const KeyChord& chord) KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) { - return _FromString(std::wstring{ hstr }); + return _FromString(hstr); } winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) @@ -313,12 +313,24 @@ KeyChord ConversionTrait::FromJson(const Json::Value& json) { try { - const std::string keyChordText{ json.isString() ? - json.asString() : - json[0].asString() }; + std::string keyChordText; + if (json.isString()) + { + // "keys": "ctrl+c" + keyChordText = json.asString(); + } + else if (json.isArray() && json.size() == 1) + { + // "keys": [ "ctrl+c" ] + keyChordText = json[0].asString(); + } + else + { + throw winrt::hresult_invalid_argument{}; + } return _FromString(til::u8u16(keyChordText)); } - catch (winrt::hresult_invalid_argument) + catch (...) { return nullptr; } diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index d73ad493411..07aa6e85065 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -174,6 +174,20 @@ std::vector> SafeArrayToOwningVector(SAFEARRAY* safeArray) return result; } +// This is a helper template function for hashing multiple variables in conjunction to each other. +template +static size_t HashProperty(const T& val) +{ + std::hash hashFunc; + return hashFunc(val); +} + +template +static size_t HashProperty(const T& val, Args&&... more) +{ + return HashProperty(val) ^ HashProperty(std::forward(more)...); +} + #define DECLARE_CONVERTER(nameSpace, className) \ namespace nameSpace::implementation \ { \ From 8507ef0471df64d5597ff886b7ce521ac5b0b12b Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Wed, 14 Apr 2021 20:06:50 -0700 Subject: [PATCH 13/31] rename cmd.Action; move HashProperty; fix most tests --- .../LocalTests_SettingsModel/CommandTests.cpp | 126 +++++++++--------- .../DeserializationTests.cpp | 36 +++-- .../LocalTests_SettingsModel/TestUtils.h | 2 +- .../LocalTests_TerminalApp/SettingsTests.cpp | 89 +++++++------ .../TerminalSettingsModel/ActionArgs.h | 2 +- .../TerminalSettingsModel/ActionMap.cpp | 24 +++- .../TerminalSettingsModel/Command.cpp | 7 + src/cascadia/TerminalSettingsModel/Command.h | 2 + src/cascadia/inc/cppwinrt_utils.h | 4 +- 9 files changed, 168 insertions(+), 124 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp index 44526b36382..ce5f06014c6 100644 --- a/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/CommandTests.cpp @@ -106,9 +106,9 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } { @@ -117,9 +117,9 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action()); - VERIFY_IS_NULL(command.Action().Args()); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.ActionAndArgs().Action()); + VERIFY_IS_NULL(command.ActionAndArgs().Args()); } { auto warnings = implementation::Command::LayerJson(commands, commands2Json); @@ -127,9 +127,9 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } { @@ -165,9 +165,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command1"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); @@ -176,9 +176,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command2"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); @@ -187,9 +187,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command4"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); @@ -198,9 +198,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command5"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); @@ -209,9 +209,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command6"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); @@ -239,9 +239,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"command1"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); @@ -268,9 +268,9 @@ namespace SettingsModelLocalTests // this test will break. auto command = commands.Lookup(L"Duplicate tab"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); } } @@ -309,9 +309,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"Split pane"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Automatic, realArgs.SplitStyle()); @@ -319,9 +319,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"Split pane, split: vertical"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); @@ -329,9 +329,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"Split pane, split: horizontal"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); @@ -355,9 +355,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"Split pane"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); // Verify the args have the expected value VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); @@ -410,9 +410,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action0"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -423,9 +423,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action1"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -436,9 +436,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action2"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -449,9 +449,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action3"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -462,9 +462,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action4"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -477,9 +477,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action5"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); @@ -492,9 +492,9 @@ namespace SettingsModelLocalTests { auto command = commands.Lookup(L"action6"); VERIFY_IS_NOT_NULL(command); - VERIFY_IS_NOT_NULL(command.Action()); - VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.Action().Action()); - const auto& realArgs = command.Action().Args().try_as(); + VERIFY_IS_NOT_NULL(command.ActionAndArgs()); + VERIFY_ARE_EQUAL(ShortcutAction::NewWindow, command.ActionAndArgs().Action()); + const auto& realArgs = command.ActionAndArgs().Args().try_as(); VERIFY_IS_NOT_NULL(realArgs); const auto& terminalArgs = realArgs.TerminalArgs(); VERIFY_IS_NOT_NULL(terminalArgs); diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index e07f15f8f81..b47035f83db 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -1918,7 +1918,12 @@ namespace SettingsModelLocalTests const auto settingsObject = VerifyParseSucceeded(badSettings); auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->_actionMap->_KeyMap.size()); + // KeyMap: ctrl+a/b are mapped to "invalid" + // ActionMap: "splitPane" and "invalid" are the only deserialized actions + // NameMap: "splitPane" has no key binding, but it is still added to the name map + VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(2u, settings->_globals->_actionMap->_ActionMap.size()); + VERIFY_ARE_EQUAL(1u, settings->_globals->_actionMap->NameMap().Size()); VERIFY_ARE_EQUAL(4u, settings->_globals->_keybindingsWarnings.size()); VERIFY_ARE_EQUAL(SettingsLoadWarnings::TooManyKeysForChord, settings->_globals->_keybindingsWarnings.at(0)); @@ -1962,7 +1967,10 @@ namespace SettingsModelLocalTests auto settings = implementation::CascadiaSettings::FromJson(settingsObject); - VERIFY_ARE_EQUAL(0u, settings->_globals->_actionMap->_KeyMap.size()); + VERIFY_ARE_EQUAL(3u, settings->_globals->_actionMap->_KeyMap.size()); + VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('a') })); + VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('b') })); + VERIFY_IS_NULL(settings->_globals->_actionMap->GetActionByKeyChord({ KeyModifiers::Ctrl, static_cast('c') })); for (const auto& warning : settings->_globals->_keybindingsWarnings) { @@ -2191,7 +2199,7 @@ namespace SettingsModelLocalTests { auto command = nameMap.TryLookup(L"Split pane, split: vertical"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2207,7 +2215,7 @@ namespace SettingsModelLocalTests { auto command = nameMap.TryLookup(L"ctrl+b"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2223,7 +2231,7 @@ namespace SettingsModelLocalTests { auto command = nameMap.TryLookup(L"ctrl+c"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2239,7 +2247,7 @@ namespace SettingsModelLocalTests { auto command = nameMap.TryLookup(L"Split pane, split: horizontal"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -2424,7 +2432,7 @@ namespace SettingsModelLocalTests VerifyParseSucceeded(settingsJson); VerifyParseSucceeded(settings1Json); - + auto settings = winrt::make_self(); settings->_ParseJsonString(settingsJson, false); settings->LayerJson(settings->_userSettings); @@ -2433,7 +2441,7 @@ namespace SettingsModelLocalTests VERIFY_ARE_EQUAL(3u, settings->_allProfiles.Size()); settings->_ValidateSettings(); - const auto& nameMap{ settings->ActionMap().NameMap() }; + auto nameMap{ settings->ActionMap().NameMap() }; _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); @@ -2444,7 +2452,9 @@ namespace SettingsModelLocalTests settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); - _logCommandNames(settings->ActionMap().NameMap()); + + nameMap = settings->ActionMap().NameMap(); + _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(0u, nameMap.Size()); } @@ -2517,7 +2527,7 @@ namespace SettingsModelLocalTests const auto& actionMap{ settings->ActionMap() }; settings->_ValidateSettings(); - const auto& nameMap{ actionMap.NameMap() }; + auto nameMap{ actionMap.NameMap() }; _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); @@ -2539,7 +2549,9 @@ namespace SettingsModelLocalTests settings->_ParseJsonString(settings1Json, false); settings->LayerJson(settings->_userSettings); settings->_ValidateSettings(); - _logCommandNames(settings->ActionMap().NameMap()); + + nameMap = settings->ActionMap().NameMap(); + _logCommandNames(nameMap); VERIFY_ARE_EQUAL(0u, settings->_warnings.Size()); VERIFY_ARE_EQUAL(1u, nameMap.Size()); @@ -2548,7 +2560,7 @@ namespace SettingsModelLocalTests auto commandProj = nameMap.TryLookup(commandName); VERIFY_IS_NOT_NULL(commandProj); - auto actionAndArgs = commandProj.Action(); + auto actionAndArgs = commandProj.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); diff --git a/src/cascadia/LocalTests_SettingsModel/TestUtils.h b/src/cascadia/LocalTests_SettingsModel/TestUtils.h index 32ea76b7bcd..e16e55441a7 100644 --- a/src/cascadia/LocalTests_SettingsModel/TestUtils.h +++ b/src/cascadia/LocalTests_SettingsModel/TestUtils.h @@ -44,6 +44,6 @@ class TestUtils const auto cmd = actionMap.GetActionByKeyChord(kc); VERIFY_IS_NOT_NULL(cmd, L"Expected to find an action bound to the given KeyChord"); - return cmd.Action(); + return cmd.ActionAndArgs(); }; }; diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index f5711043626..a7cfa529765 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -126,7 +126,7 @@ namespace TerminalAppLocalTests { auto command = nameMap.TryLookup(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -150,7 +150,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile0"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -168,7 +168,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile1"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -186,7 +186,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile2"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -253,7 +253,7 @@ namespace TerminalAppLocalTests { auto command = nameMap.TryLookup(L"Split pane, profile: ${profile.name}"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -277,7 +277,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"Split pane, profile: profile0"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -295,7 +295,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"Split pane, profile: profile1"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -313,7 +313,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"Split pane, profile: profile2"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -382,7 +382,7 @@ namespace TerminalAppLocalTests { auto command = nameMap.TryLookup(L"iterable command ${profile.name}"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -406,7 +406,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile0"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -424,7 +424,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile1\""); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -442,7 +442,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command profile2"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -521,8 +521,9 @@ namespace TerminalAppLocalTests auto rootCommand = expandedCommands.Lookup(L"Connect to ssh..."); VERIFY_IS_NOT_NULL(rootCommand); - auto rootActionAndArgs = rootCommand.Action(); - VERIFY_IS_NULL(rootActionAndArgs); + auto rootActionAndArgs = rootCommand.ActionAndArgs(); + VERIFY_IS_NOT_NULL(rootActionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action()); VERIFY_ARE_EQUAL(2u, rootCommand.NestedCommands().Size()); @@ -530,7 +531,7 @@ namespace TerminalAppLocalTests winrt::hstring commandName{ L"first.com" }; auto command = rootCommand.NestedCommands().Lookup(commandName); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_IS_FALSE(command.HasNestedCommands()); @@ -539,7 +540,7 @@ namespace TerminalAppLocalTests winrt::hstring commandName{ L"second.com" }; auto command = rootCommand.NestedCommands().Lookup(commandName); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_IS_FALSE(command.HasNestedCommands()); @@ -615,23 +616,25 @@ namespace TerminalAppLocalTests auto grandparentCommand = expandedCommands.Lookup(L"grandparent"); VERIFY_IS_NOT_NULL(grandparentCommand); - auto grandparentActionAndArgs = grandparentCommand.Action(); - VERIFY_IS_NULL(grandparentActionAndArgs); + auto grandparentActionAndArgs = grandparentCommand.ActionAndArgs(); + VERIFY_IS_NOT_NULL(grandparentActionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, grandparentActionAndArgs.Action()); VERIFY_ARE_EQUAL(1u, grandparentCommand.NestedCommands().Size()); winrt::hstring parentName{ L"parent" }; auto parent = grandparentCommand.NestedCommands().Lookup(parentName); VERIFY_IS_NOT_NULL(parent); - auto parentActionAndArgs = parent.Action(); - VERIFY_IS_NULL(parentActionAndArgs); + auto parentActionAndArgs = parent.ActionAndArgs(); + VERIFY_IS_NOT_NULL(parentActionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, parentActionAndArgs.Action()); VERIFY_ARE_EQUAL(2u, parent.NestedCommands().Size()); { winrt::hstring childName{ L"child1" }; auto child = parent.NestedCommands().Lookup(childName); VERIFY_IS_NOT_NULL(child); - auto childActionAndArgs = child.Action(); + auto childActionAndArgs = child.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, childActionAndArgs.Action()); @@ -651,7 +654,7 @@ namespace TerminalAppLocalTests winrt::hstring childName{ L"child2" }; auto child = parent.NestedCommands().Lookup(childName); VERIFY_IS_NOT_NULL(child); - auto childActionAndArgs = child.Action(); + auto childActionAndArgs = child.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, childActionAndArgs.Action()); @@ -741,8 +744,9 @@ namespace TerminalAppLocalTests winrt::hstring commandName{ name + L"..." }; auto command = expandedCommands.Lookup(commandName); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); - VERIFY_IS_NULL(actionAndArgs); + auto actionAndArgs = command.ActionAndArgs(); + VERIFY_IS_NOT_NULL(actionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, actionAndArgs.Action()); VERIFY_IS_TRUE(command.HasNestedCommands()); VERIFY_ARE_EQUAL(3u, command.NestedCommands().Size()); @@ -751,7 +755,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -772,7 +776,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -793,7 +797,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -873,8 +877,9 @@ namespace TerminalAppLocalTests auto rootCommand = expandedCommands.Lookup(L"New Tab With Profile..."); VERIFY_IS_NOT_NULL(rootCommand); - auto rootActionAndArgs = rootCommand.Action(); - VERIFY_IS_NULL(rootActionAndArgs); + auto rootActionAndArgs = rootCommand.ActionAndArgs(); + VERIFY_IS_NOT_NULL(rootActionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action()); VERIFY_ARE_EQUAL(3u, rootCommand.NestedCommands().Size()); @@ -883,7 +888,7 @@ namespace TerminalAppLocalTests winrt::hstring commandName{ fmt::format(L"New tab, profile: {}", name) }; auto command = rootCommand.NestedCommands().Lookup(commandName); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action()); @@ -975,8 +980,9 @@ namespace TerminalAppLocalTests auto rootCommand = expandedCommands.Lookup(L"New Pane..."); VERIFY_IS_NOT_NULL(rootCommand); - auto rootActionAndArgs = rootCommand.Action(); - VERIFY_IS_NULL(rootActionAndArgs); + auto rootActionAndArgs = rootCommand.ActionAndArgs(); + VERIFY_IS_NOT_NULL(rootActionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, rootActionAndArgs.Action()); VERIFY_ARE_EQUAL(3u, rootCommand.NestedCommands().Size()); @@ -985,8 +991,9 @@ namespace TerminalAppLocalTests winrt::hstring commandName{ name + L"..." }; auto command = rootCommand.NestedCommands().Lookup(commandName); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); - VERIFY_IS_NULL(actionAndArgs); + auto actionAndArgs = command.ActionAndArgs(); + VERIFY_IS_NOT_NULL(actionAndArgs); + VERIFY_ARE_EQUAL(ShortcutAction::Invalid, actionAndArgs.Action()); VERIFY_IS_TRUE(command.HasNestedCommands()); VERIFY_ARE_EQUAL(3u, command.NestedCommands().Size()); @@ -996,7 +1003,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -1017,7 +1024,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, split: horizontal, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -1038,7 +1045,7 @@ namespace TerminalAppLocalTests winrt::hstring childCommandName{ fmt::format(L"Split pane, split: vertical, profile: {}", name) }; auto childCommand = command.NestedCommands().Lookup(childCommandName); VERIFY_IS_NOT_NULL(childCommand); - auto childActionAndArgs = childCommand.Action(); + auto childActionAndArgs = childCommand.ActionAndArgs(); VERIFY_IS_NOT_NULL(childActionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, childActionAndArgs.Action()); @@ -1114,7 +1121,7 @@ namespace TerminalAppLocalTests { auto command = nameMap.TryLookup(L"iterable command ${scheme.name}"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -1143,7 +1150,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command scheme_0"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -1161,7 +1168,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command scheme_1"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); @@ -1179,7 +1186,7 @@ namespace TerminalAppLocalTests { auto command = expandedCommands.Lookup(L"iterable command scheme_2"); VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.Action(); + auto actionAndArgs = command.ActionAndArgs(); VERIFY_IS_NOT_NULL(actionAndArgs); VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); const auto& realArgs = actionAndArgs.Args().try_as(); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index d8ed43ac4e6..04fc1e6a0d6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -45,7 +45,7 @@ // additional args, this can be nullptr. template<> -static size_t HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args) +_TIL_INLINEPREFIX size_t HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args) { return gsl::narrow_cast(args.Hash()); } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index d021920f44e..320ccb8439f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -106,7 +106,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // This allows us to override whatever our parents tell us. for (const auto& [name, cmd] : _NestedCommands) { - nameMap.insert_or_assign(name, cmd); + //nameMap.insert_or_assign(name, cmd); + if (cmd.HasNestedCommands()) + { + // add a valid cmd + nameMap.insert_or_assign(name, cmd); + } + else + { + // remove the invalid cmd + nameMap.erase(name); + } } } @@ -118,11 +128,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_KeyMap.insert(pair); }); - for(const auto& [actionID, cmd] : _ActionMap) + for (const auto& [actionID, cmd] : _ActionMap) { actionMap->_ActionMap.insert({ actionID, *(get_self(cmd)->Copy()) }); } + for (const auto& [name, cmd] : _NestedCommands) + { + actionMap->_NestedCommands.Insert(name, *(get_self(cmd)->Copy())); + } + return actionMap; } @@ -138,8 +153,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } - // _Always_ add nested commands - if (cmd.HasNestedCommands()) + // Handle nested commands + const auto cmdImpl{ get_self(cmd) }; + if (cmdImpl->IsNestedCommand()) { // But check if it actually has a name to bind to first const auto name{ cmd.Name() }; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 8920e6c9e8e..3dcb9c6886f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -77,6 +77,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _subcommands ? _subcommands.Size() > 0 : false; } + bool Command::IsNestedCommand() const + { + return _nestedCommand; + } + bool Command::HasName() const noexcept { return _name.has_value(); @@ -278,6 +283,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // Initialize our list of subcommands. result->_subcommands = winrt::single_threaded_map(); + result->_nestedCommand = true; auto nestedWarnings = Command::LayerJson(result->_subcommands, nestedCommandsJson); // It's possible that the nested commands have some warnings warnings.insert(warnings.end(), nestedWarnings.begin(), nestedWarnings.end()); @@ -297,6 +303,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // create an "invalid" ActionAndArgs result->_ActionAndArgs = make(); + result->_nestedCommand = true; } JsonUtils::GetValueForKey(json, IconKey, result->_iconPath); diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index f4fdd9deadd..485a3202818 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -50,6 +50,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::vector LayerJson(Windows::Foundation::Collections::IMap& commands, const Json::Value& json); bool HasNestedCommands() const; + bool IsNestedCommand() const; Windows::Foundation::Collections::IMapView NestedCommands() const; bool HasName() const noexcept; @@ -77,6 +78,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector _keyMappings; std::optional _name; std::optional _iconPath; + bool _nestedCommand{ false }; static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 07aa6e85065..5849db7d6c7 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -176,14 +176,14 @@ std::vector> SafeArrayToOwningVector(SAFEARRAY* safeArray) // This is a helper template function for hashing multiple variables in conjunction to each other. template -static size_t HashProperty(const T& val) +size_t HashProperty(const T& val) { std::hash hashFunc; return hashFunc(val); } template -static size_t HashProperty(const T& val, Args&&... more) +size_t HashProperty(const T& val, Args&&... more) { return HashProperty(val) ^ HashProperty(std::forward(more)...); } From dc1de34bb7b57d594054b6b897a5987897d78c64 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 15 Apr 2021 10:53:57 -0700 Subject: [PATCH 14/31] fix failing tests; We're good to go!! --- .../DeserializationTests.cpp | 2 +- .../TerminalSettingsModel/ActionMap.cpp | 67 ++++++++++++------- .../TerminalSettingsModel/ActionMap.h | 3 +- 3 files changed, 44 insertions(+), 28 deletions(-) diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index b47035f83db..c27461e1688 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -2432,7 +2432,7 @@ namespace SettingsModelLocalTests VerifyParseSucceeded(settingsJson); VerifyParseSucceeded(settings1Json); - + auto settings = winrt::make_self(); settings->_ParseJsonString(settingsJson, false); settings->LayerJson(settings->_userSettings); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 320ccb8439f..c2fafbdfa4a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -55,28 +55,60 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // an invalid state for the `ActionMap` Windows::Foundation::Collections::IMapView ActionMap::NameMap() { - if (_NameMapCache) + if (!_NameMapCache) { - return _NameMapCache.GetView(); + // populate _NameMapCache + std::unordered_map nameMap{}; + std::set visitedActionIDs{ ActionHash()(make()) }; + _PopulateNameMapWithNestedCommands(nameMap); + _PopulateNameMapWithStandardCommands(nameMap, visitedActionIDs); + + _NameMapCache = single_threaded_map(std::move(nameMap)); } + return _NameMapCache.GetView(); + } - std::unordered_map nameMap{}; - std::set visitedActionIDs{ ActionHash()(make()) }; - _PopulateNameMap(nameMap, visitedActionIDs); + // Method Description: + // - Populates the provided nameMap with all of our nested commands and our parents nested commands + // - Performs a top-down approach by going to the root first, then recursively adding the nested commands layer-by-layer + // Arguments: + // - nameMap: the nameMap we're populating. This maps the name (hstring) of a command to the command itself. + void ActionMap::_PopulateNameMapWithNestedCommands(std::unordered_map& nameMap) const + { + // Update NameMap with our parents. + // Starting with this means we're doing a top-down approach. + for (const auto& parent : _parents) + { + parent->_PopulateNameMapWithNestedCommands(nameMap); + } - _NameMapCache = single_threaded_map(std::move(nameMap)); - return _NameMapCache.GetView(); + // Add NestedCommands to NameMap _after_ we handle our parents. + // This allows us to override whatever our parents tell us. + for (const auto& [name, cmd] : _NestedCommands) + { + if (cmd.HasNestedCommands()) + { + // add a valid cmd + nameMap.insert_or_assign(name, cmd); + } + else + { + // remove the invalid cmd + nameMap.erase(name); + } + } } // Method Description: // - Populates the provided nameMap with all of our actions and our parents actions // while omitting the actions that were already added before + // - This needs to be a bottom up approach to ensure that we only add each action (identified by action ID) once. // Arguments: // - nameMap: the nameMap we're populating. This maps the name (hstring) of a command to the command itself. // There should only ever by one of each command (identified by the actionID) in the nameMap. // - visitedActionIDs: the actionIDs that we've already added to the nameMap. Commands with a matching actionID // have already been added, and should be ignored. - void ActionMap::_PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const + void ActionMap::_PopulateNameMapWithStandardCommands(std::unordered_map& nameMap, std::set& visitedActionIDs) const { // Update NameMap and visitedActionIDs with our current layer for (const auto& [actionID, cmd] : _ActionMap) @@ -99,24 +131,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Update NameMap and visitedActionIDs with our parents for (const auto& parent : _parents) { - parent->_PopulateNameMap(nameMap, visitedActionIDs); - } - - // Add NestedCommands to NameMap _after_ we handle our parents. - // This allows us to override whatever our parents tell us. - for (const auto& [name, cmd] : _NestedCommands) - { - //nameMap.insert_or_assign(name, cmd); - if (cmd.HasNestedCommands()) - { - // add a valid cmd - nameMap.insert_or_assign(name, cmd); - } - else - { - // remove the invalid cmd - nameMap.erase(name); - } + parent->_PopulateNameMapWithStandardCommands(nameMap, visitedActionIDs); } } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 960db3d974f..0c48edee649 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -92,7 +92,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional _GetActionByID(InternalActionID actionID) const; std::optional _GetActionByKeyChordInternal(Control::KeyChord const& keys) const; - void _PopulateNameMap(std::unordered_map& nameMap, std::set& visitedActionIDs) const; + void _PopulateNameMapWithNestedCommands(std::unordered_map& nameMap) const; + void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap, std::set& visitedActionIDs) const; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; Windows::Foundation::Collections::IMap _NestedCommands{ nullptr }; From 3d3f3555538232126ca28a5628cec2c2b21e7907 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 15 Apr 2021 17:59:44 -0700 Subject: [PATCH 15/31] fix all tests and dropdown bug --- .../DeserializationTests.cpp | 47 ++----------- .../KeyBindingsTests.cpp | 61 +++++++++++++++++ .../TerminalSettingsModel/ActionArgs.h | 59 +++++++++-------- .../TerminalSettingsModel/ActionArgs.idl | 1 + .../TerminalSettingsModel/ActionMap.h | 2 +- .../TerminalSettingsModel/Command.cpp | 8 ++- .../TerminalSettingsModel/HashUtils.h | 66 +++++++++++++++++++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 1 + src/cascadia/inc/cppwinrt_utils.h | 14 ---- 9 files changed, 176 insertions(+), 83 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/HashUtils.h diff --git a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp index c27461e1688..598a71ca82d 100644 --- a/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/DeserializationTests.cpp @@ -2118,7 +2118,7 @@ namespace SettingsModelLocalTests // * A and D share the same name, so they'll only generate a single action. // * F's name is set manually to `null` const auto& nameMap{ actionMap->NameMap() }; - VERIFY_ARE_EQUAL(4u, nameMap.Size()); + VERIFY_ARE_EQUAL(1u, nameMap.Size()); { KeyChord kc{ true, false, false, static_cast('A') }; @@ -2197,36 +2197,14 @@ namespace SettingsModelLocalTests Log::Comment(L"Now verify the commands"); _logCommandNames(nameMap); { + // This was renamed to "ctrl+c" in C. So this does not exist. auto command = nameMap.TryLookup(L"Split pane, split: vertical"); - VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.ActionAndArgs(); - VERIFY_IS_NOT_NULL(actionAndArgs); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NULL(command); } { + // This was renamed to "ctrl+c" in C. So this does not exist. auto command = nameMap.TryLookup(L"ctrl+b"); - VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.ActionAndArgs(); - VERIFY_IS_NOT_NULL(actionAndArgs); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Vertical, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NULL(command); } { auto command = nameMap.TryLookup(L"ctrl+c"); @@ -2245,20 +2223,9 @@ namespace SettingsModelLocalTests VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); } { + // This was renamed to null (aka removed from the name map) in F. So this does not exist. auto command = nameMap.TryLookup(L"Split pane, split: horizontal"); - VERIFY_IS_NOT_NULL(command); - auto actionAndArgs = command.ActionAndArgs(); - VERIFY_IS_NOT_NULL(actionAndArgs); - VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); - const auto& realArgs = actionAndArgs.Args().try_as(); - VERIFY_IS_NOT_NULL(realArgs); - // Verify the args have the expected value - VERIFY_ARE_EQUAL(SplitState::Horizontal, realArgs.SplitStyle()); - VERIFY_IS_NOT_NULL(realArgs.TerminalArgs()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty()); - VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty()); + VERIFY_IS_NULL(command); } } diff --git a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp index 5b41607d425..35fde481bea 100644 --- a/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/LocalTests_SettingsModel/KeyBindingsTests.cpp @@ -51,6 +51,8 @@ namespace SettingsModelLocalTests TEST_METHOD(TestToggleCommandPaletteArgs); TEST_METHOD(TestMoveTabArgs); + TEST_METHOD(TestGetKeyBindingForAction); + TEST_CLASS_SETUP(ClassSetup) { InitializeJsonReader(); @@ -639,4 +641,63 @@ namespace SettingsModelLocalTests VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); } } + + void KeyBindingsTests::TestGetKeyBindingForAction() + { + const std::string bindings0String{ R"([ { "command": "closeWindow", "keys": "ctrl+a" } ])" }; + const std::string bindings1String{ R"([ { "command": { "action": "copy", "singleLine": true }, "keys": "ctrl+b" } ])" }; + const std::string bindings2String{ R"([ { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+c" } ])" }; + + const auto bindings0Json = VerifyParseSucceeded(bindings0String); + const auto bindings1Json = VerifyParseSucceeded(bindings1String); + const auto bindings2Json = VerifyParseSucceeded(bindings2String); + + auto VerifyKeyChordEquality = [](const KeyChord& expected, const KeyChord& actual) { + if (expected) + { + VERIFY_IS_NOT_NULL(actual); + VERIFY_ARE_EQUAL(expected.Modifiers(), actual.Modifiers()); + VERIFY_ARE_EQUAL(expected.Vkey(), actual.Vkey()); + } + else + { + VERIFY_IS_NULL(actual); + } + }; + + auto actionMap = winrt::make_self(); + VERIFY_IS_NOT_NULL(actionMap); + VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); + + { + Log::Comment(L"simple command: no args"); + actionMap->LayerJson(bindings0Json); + VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); + const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) }; + VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast('A') }, kbd); + } + { + Log::Comment(L"command with args"); + actionMap->LayerJson(bindings1Json); + VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); + + auto args{ winrt::make_self() }; + args->SingleLine(true); + + const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) }; + VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast('B') }, kbd); + } + { + Log::Comment(L"command with new terminal args"); + actionMap->LayerJson(bindings2Json); + VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); + + auto newTerminalArgs{ winrt::make_self() }; + newTerminalArgs->ProfileIndex(0); + auto args{ winrt::make_self(*newTerminalArgs) }; + + const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::NewTab, *args) }; + VerifyKeyChordEquality({ KeyModifiers::Ctrl, static_cast('C') }, kbd); + } + } } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 04fc1e6a0d6..0450a62f040 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -34,6 +34,7 @@ #include "../../cascadia/inc/cppwinrt_utils.h" #include "JsonUtils.h" +#include "HashUtils.h" #include "TerminalWarnings.h" #include "TerminalSettingsSerializationHelpers.h" @@ -45,7 +46,13 @@ // additional args, this can be nullptr. template<> -_TIL_INLINEPREFIX size_t HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args) +constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::IActionArgs& args) +{ + return gsl::narrow_cast(args.Hash()); +} + +template<> +constexpr size_t Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(const winrt::Microsoft::Terminal::Settings::Model::NewTerminalArgs& args) { return gsl::narrow_cast(args.Hash()); } @@ -132,7 +139,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Commandline, _StartingDirectory, _TabTitle, _TabColor, _ProfileIndex, _Profile, _SuppressApplicationTitle, _ColorScheme); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Commandline, _StartingDirectory, _TabTitle, _TabColor, _ProfileIndex, _Profile, _SuppressApplicationTitle, _ColorScheme); } }; @@ -176,7 +183,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_SingleLine, _CopyFormatting); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_SingleLine, _CopyFormatting); } }; @@ -214,7 +221,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_TerminalArgs); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_TerminalArgs); } }; @@ -254,7 +261,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_TabIndex); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_TabIndex); } }; @@ -299,7 +306,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_ResizeDirection); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_ResizeDirection); } }; @@ -347,7 +354,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_FocusDirection); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_FocusDirection); } }; @@ -385,7 +392,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Delta); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Delta); } }; @@ -426,7 +433,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Input); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Input); } }; @@ -497,7 +504,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_SplitStyle, _TerminalArgs, _SplitMode, _SplitSize); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_SplitStyle, _TerminalArgs, _SplitMode, _SplitSize); } }; @@ -537,7 +544,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Target); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Target); } }; @@ -581,7 +588,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_SchemeName); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_SchemeName); } }; @@ -619,7 +626,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_TabColor); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_TabColor); } }; @@ -657,7 +664,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Title); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Title); } }; @@ -701,7 +708,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Commandline); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Commandline); } }; @@ -741,7 +748,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Index); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Index); } }; @@ -781,7 +788,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Index); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Index); } }; @@ -828,7 +835,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Direction); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Direction); } }; @@ -866,7 +873,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_RowsToScroll); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_RowsToScroll); } }; @@ -904,7 +911,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_RowsToScroll); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_RowsToScroll); } }; @@ -944,7 +951,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_LaunchMode); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_LaunchMode); } }; @@ -991,7 +998,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Direction); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Direction); } }; @@ -1029,7 +1036,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_TerminalArgs); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_TerminalArgs); } }; @@ -1066,7 +1073,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_SwitcherMode); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_SwitcherMode); } }; @@ -1103,7 +1110,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_SwitcherMode); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_SwitcherMode); } }; @@ -1140,7 +1147,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } size_t Hash() const { - return HashProperty(_Name); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(_Name); } }; } diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index d66db340f81..7837e7250fd 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -106,6 +106,7 @@ namespace Microsoft.Terminal.Settings.Model Boolean Equals(NewTerminalArgs other); String GenerateName(); String ToCommandline(); + UInt64 Hash(); }; [default_interface] runtimeclass ActionEventArgs : IActionEventArgs diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 0c48edee649..444158dabd4 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -36,7 +36,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { std::size_t operator()(const Control::KeyChord& key) const { - return ::HashProperty(key.Modifiers(), key.Vkey()); + return ::Microsoft::Terminal::Settings::Model::HashUtils::HashProperty(key.Modifiers(), key.Vkey()); } }; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 3dcb9c6886f..523b7f259f2 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -111,7 +111,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (!_name.has_value() || _name.value() != value) { _name = value; - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"Name" }); } } @@ -216,7 +215,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (!_iconPath.has_value() || _iconPath.value() != val) { _iconPath = val; - _PropertyChangedHandlers(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L"IconPath" }); } } @@ -249,6 +247,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return JsonUtils::GetValue(name); } } + else if (json.isMember(JsonKey(NameKey))) + { + // { "name": null, "command": "copy" } will land in this case, which + // should also be used for unbinding. + return std::wstring{}; + } return std::nullopt; } diff --git a/src/cascadia/TerminalSettingsModel/HashUtils.h b/src/cascadia/TerminalSettingsModel/HashUtils.h new file mode 100644 index 00000000000..95b0391fedf --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/HashUtils.h @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +/*++ +Module Name: +- HashUtils.h + +Abstract: +- This module is used for hashing data consistently + +Author(s): +- Carlos Zamora (CaZamor) 15-Apr-2021 + +Revision History: +- N/A +--*/ + +#pragma once + +namespace Microsoft::Terminal::Settings::Model::HashUtils +{ + // This is a helper template function for hashing multiple variables in conjunction to each other. + template + constexpr size_t HashProperty(const T& val) + { + std::hash hashFunc; + return hashFunc(val); + } + + template + constexpr size_t HashProperty(const T& val, Args&&... more) + { + return HashProperty(val) ^ HashProperty(std::forward(more)...); + } + + template<> + constexpr size_t HashProperty(const til::color& val) + { + return HashProperty(val.a, val.r, val.g, val.b); + } + +#ifdef WINRT_Windows_Foundation_H + template + constexpr size_t HashProperty(const winrt::Windows::Foundation::IReference& val) + { + return val ? HashProperty(val.Value()) : 0; + } +#endif + +#ifdef WINRT_Windows_UI_H + template<> + constexpr size_t HashProperty(const winrt::Windows::UI::Color& val) + { + return HashProperty(til::color{ val }); + } +#endif + +#ifdef WINRT_Microsoft_Terminal_Core_H + template<> + constexpr size_t HashProperty(const winrt::Microsoft::Terminal::Core::Color& val) + { + return HashProperty(til::color{ val }); + } +#endif + +}; diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index a691c1bf16a..eaba6a59e93 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -46,6 +46,7 @@ + KeyChordSerialization.idl diff --git a/src/cascadia/inc/cppwinrt_utils.h b/src/cascadia/inc/cppwinrt_utils.h index 5849db7d6c7..d73ad493411 100644 --- a/src/cascadia/inc/cppwinrt_utils.h +++ b/src/cascadia/inc/cppwinrt_utils.h @@ -174,20 +174,6 @@ std::vector> SafeArrayToOwningVector(SAFEARRAY* safeArray) return result; } -// This is a helper template function for hashing multiple variables in conjunction to each other. -template -size_t HashProperty(const T& val) -{ - std::hash hashFunc; - return hashFunc(val); -} - -template -size_t HashProperty(const T& val, Args&&... more) -{ - return HashProperty(val) ^ HashProperty(std::forward(more)...); -} - #define DECLARE_CONVERTER(nameSpace, className) \ namespace nameSpace::implementation \ { \ From d6f3864242397e5523b8c20fa4c9d0328cefb32c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 22 Apr 2021 11:41:59 -0700 Subject: [PATCH 16/31] fix old reference to 'cmd.Action' --- src/cascadia/TerminalApp/ActionPreviewHandlers.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp index 8062d107608..f09c9755322 100644 --- a/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp +++ b/src/cascadia/TerminalApp/ActionPreviewHandlers.cpp @@ -42,11 +42,11 @@ namespace winrt::TerminalApp::implementation // - void TerminalPage::_EndPreview() { - if (_lastPreviewedCommand == nullptr || _lastPreviewedCommand.Action() == nullptr) + if (_lastPreviewedCommand == nullptr || _lastPreviewedCommand.ActionAndArgs() == nullptr) { return; } - switch (_lastPreviewedCommand.Action().Action()) + switch (_lastPreviewedCommand.ActionAndArgs().Action()) { case ShortcutAction::SetColorScheme: { @@ -160,17 +160,17 @@ namespace winrt::TerminalApp::implementation void TerminalPage::_PreviewActionHandler(const IInspectable& /*sender*/, const Microsoft::Terminal::Settings::Model::Command& args) { - if (args == nullptr || args.Action() == nullptr) + if (args == nullptr || args.ActionAndArgs() == nullptr) { _EndPreview(); } else { - switch (args.Action().Action()) + switch (args.ActionAndArgs().Action()) { case ShortcutAction::SetColorScheme: { - _PreviewColorScheme(args.Action().Args().try_as()); + _PreviewColorScheme(args.ActionAndArgs().Args().try_as()); break; } } From 17fc18b89337aa424fa682488c9639beafd740c3 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Thu, 22 Apr 2021 17:10:21 -0700 Subject: [PATCH 17/31] fix build --- src/cascadia/TerminalSettingsModel/ActionMap.h | 2 +- src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 444158dabd4..917abec5428 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -58,7 +58,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::size_t hashedArgs; if (const auto& args{ actionAndArgs.Args() }) { - hashedArgs = args.Hash(); + hashedArgs = gsl::narrow_cast(args.Hash()); } else { diff --git a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj index 40152b48502..8b846bc25b2 100644 --- a/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj +++ b/src/cascadia/ut_app/TerminalApp.UnitTests.vcxproj @@ -71,6 +71,13 @@ WindowsApp.lib;%(AdditionalDependencies) + + /INCLUDE:_DllMain@12 + /INCLUDE:DllMain From a961fc26af512ab4ee70cf271e89e89521472c49 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 23 Apr 2021 13:59:22 -0700 Subject: [PATCH 18/31] PR feedback; copy parents too --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 9 +++++++++ src/cascadia/TerminalSettingsModel/Command.cpp | 8 +++++++- src/cascadia/TerminalSettingsModel/Command.h | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index c2fafbdfa4a..d8acfa2e3d7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -139,20 +139,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { auto actionMap{ make_self() }; + // copy _KeyMap (KeyChord --> ID) std::for_each(_KeyMap.begin(), _KeyMap.end(), [actionMap](const auto& pair) { actionMap->_KeyMap.insert(pair); }); + // copy _ActionMap (ID --> Command) for (const auto& [actionID, cmd] : _ActionMap) { actionMap->_ActionMap.insert({ actionID, *(get_self(cmd)->Copy()) }); } + // copy _NestedCommands (Name --> Command) for (const auto& [name, cmd] : _NestedCommands) { actionMap->_NestedCommands.Insert(name, *(get_self(cmd)->Copy())); } + // repeat this for each of our parents + for (const auto& parent : _parents) + { + actionMap->_parents.push_back(std::move(parent->Copy())); + } + return actionMap; } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 523b7f259f2..f16188c8b4f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -72,12 +72,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return _subcommands ? _subcommands.GetView() : nullptr; } + // Function Description: + // - reports if the current command has nested commands + // - This CANNOT detect { "name": "foo", "commands": null } bool Command::HasNestedCommands() const { return _subcommands ? _subcommands.Size() > 0 : false; } - bool Command::IsNestedCommand() const + // Function Description: + // - reports if the current command IS a nested command + // - This CAN be used to detect cases like { "name": "foo", "commands": null } + bool Command::IsNestedCommand() const noexcept { return _nestedCommand; } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 485a3202818..41125b73a20 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -50,7 +50,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::vector LayerJson(Windows::Foundation::Collections::IMap& commands, const Json::Value& json); bool HasNestedCommands() const; - bool IsNestedCommand() const; + bool IsNestedCommand() const noexcept; Windows::Foundation::Collections::IMapView NestedCommands() const; bool HasName() const noexcept; From 0564cae164e8f82509e02e5f0ce13cda591c1bfd Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Fri, 23 Apr 2021 14:29:09 -0700 Subject: [PATCH 19/31] polish the actions page a bit w/ sorting and removing unnecessary converter --- .../TerminalSettingsEditor/Actions.cpp | 9 ++++-- src/cascadia/TerminalSettingsEditor/Actions.h | 8 +++++ .../TerminalSettingsEditor/Actions.xaml | 7 +---- .../TerminalSettingsEditor/Converters.idl | 5 ---- ...Microsoft.Terminal.Settings.Editor.vcxproj | 6 ---- .../NullCheckConverter.cpp | 29 ------------------- .../NullCheckConverter.h | 9 ------ 7 files changed, 15 insertions(+), 58 deletions(-) delete mode 100644 src/cascadia/TerminalSettingsEditor/NullCheckConverter.cpp delete mode 100644 src/cascadia/TerminalSettingsEditor/NullCheckConverter.h diff --git a/src/cascadia/TerminalSettingsEditor/Actions.cpp b/src/cascadia/TerminalSettingsEditor/Actions.cpp index 53137858a42..bd9403cece6 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.cpp +++ b/src/cascadia/TerminalSettingsEditor/Actions.cpp @@ -19,14 +19,15 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { InitializeComponent(); - _filteredActions = winrt::single_threaded_observable_vector(); + _filteredActions = winrt::single_threaded_observable_vector(); } void Actions::OnNavigatedTo(const NavigationEventArgs& e) { _State = e.Parameter().as(); - for (const auto& [k, command] : _State.Settings().GlobalSettings().ActionMap().NameMap()) + std::vector keyBindingList; + for (const auto& [_, command] : _State.Settings().GlobalSettings().ActionMap().NameMap()) { // Filter out nested commands, and commands that aren't bound to a // key. This page is currently just for displaying the actions that @@ -35,8 +36,10 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { continue; } - _filteredActions.Append(command); + keyBindingList.push_back(command); } + std::sort(begin(keyBindingList), end(keyBindingList), CommandComparator{}); + _filteredActions = single_threaded_observable_vector(std::move(keyBindingList)); } Collections::IObservableVector Actions::FilteredActions() diff --git a/src/cascadia/TerminalSettingsEditor/Actions.h b/src/cascadia/TerminalSettingsEditor/Actions.h index 536b7251999..e49c1aebc3f 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.h +++ b/src/cascadia/TerminalSettingsEditor/Actions.h @@ -9,6 +9,14 @@ namespace winrt::Microsoft::Terminal::Settings::Editor::implementation { + struct CommandComparator + { + bool operator()(const Model::Command& lhs, const Model::Command& rhs) const + { + return lhs.Name() < rhs.Name(); + } + }; + struct ActionsPageNavigationState : ActionsPageNavigationStateT { public: diff --git a/src/cascadia/TerminalSettingsEditor/Actions.xaml b/src/cascadia/TerminalSettingsEditor/Actions.xaml index 92916c9c40e..3167229a987 100644 --- a/src/cascadia/TerminalSettingsEditor/Actions.xaml +++ b/src/cascadia/TerminalSettingsEditor/Actions.xaml @@ -19,7 +19,6 @@ - ID) - std::for_each(_KeyMap.begin(), _KeyMap.end(), [actionMap](const auto& pair) { - actionMap->_KeyMap.insert(pair); - }); + for (const auto& [keys, actionID] : _KeyMap) + { + actionMap->_KeyMap.emplace(KeyChord{ keys.Modifiers(), keys.Vkey() }, actionID); + } // copy _ActionMap (ID --> Command) for (const auto& [actionID, cmd] : _ActionMap) { - actionMap->_ActionMap.insert({ actionID, *(get_self(cmd)->Copy()) }); + actionMap->_ActionMap.emplace(actionID, *(get_self(cmd)->Copy())); + } + + // copy _ConsolidatedActions (ID --> Command) + for (const auto& [actionID, cmd] : _ConsolidatedActions) + { + actionMap->_ConsolidatedActions.emplace(actionID, *(get_self(cmd)->Copy())); } // copy _NestedCommands (Name --> Command) @@ -230,7 +237,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation FAIL_FAST_IF(_parents.size() > 1); for (const auto& parent : _parents) { - actionMap->_parents.push_back(std::move(parent->Copy())); + actionMap->_parents.emplace_back(parent->Copy()); } return actionMap; @@ -307,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation else { // add this action in for the first time - _ActionMap.insert({ actionID, cmd }); + _ActionMap.emplace(actionID, cmd); } // Now check if this action was introduced in another layer. @@ -332,7 +339,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { const auto& inheritedCmdImpl{ get_self(inheritedCmd.value()) }; consolidatedCmd = *inheritedCmdImpl->Copy(); - _ConsolidatedActions.insert({ actionID, consolidatedCmd }); + _ConsolidatedActions.emplace(actionID, consolidatedCmd); } } } @@ -422,7 +429,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const auto& conflictingCmdImpl{ get_self(conflictingCmd) }; const auto& conflictingCmdCopy{ conflictingCmdImpl->Copy() }; conflictingCmdCopy->EraseKey(keys); - _ConsolidatedActions.insert({ conflictingActionID, *conflictingCmdCopy }); + _ConsolidatedActions.emplace(conflictingActionID, *conflictingCmdCopy); } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 14f40b1c863..f87af881b64 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -136,28 +136,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } - // Check if we registered this key chord before - for (auto pos = _keyMappings.begin(); pos < _keyMappings.end(); ++pos) - { - if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) - { - // KeyChord was already registered. - if (*pos == _keyMappings.back()) - { - // It's already the latest key registered. - return; - } - else - { - // Move the new KeyChord to the back of the line. - _keyMappings.erase(pos); - _keyMappings.push_back(*pos); - return; - } - } - } - - // Add the KeyChord to the back of the line. + // Remove the KeyChord and add it to the back of the line. + // This makes it so that the main key chord associated with this + // command is updated. + EraseKey(keys); _keyMappings.push_back(keys); } diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index d0804198cf6..a9b35af009e 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -319,7 +319,7 @@ KeyChord ConversionTrait::FromJson(const Json::Value& json) // "keys": "ctrl+c" keyChordText = json.asString(); } - else if (json.isArray() && json.size() == 1) + else if (json.isArray() && json.size() == 1 && json[0].isString()) { // "keys": [ "ctrl+c" ] keyChordText = json[0].asString(); @@ -338,7 +338,7 @@ KeyChord ConversionTrait::FromJson(const Json::Value& json) bool ConversionTrait::CanConvert(const Json::Value& json) { - return json.isString() || (json.isArray() && json.size() == 1); + return json.isString() || (json.isArray() && json.size() == 1 && json[0].isString()); } Json::Value ConversionTrait::ToJson(const KeyChord& val) From efe5bd61b3af62315fc49a6abe34c11a0e984700 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Mon, 3 May 2021 15:00:30 -0700 Subject: [PATCH 27/31] Apply feedback from Dustin and Leonard --- .../TerminalSettingsEditor/KeyChordConverter.h | 9 --------- .../ActionMapSerialization.cpp | 2 +- src/cascadia/TerminalSettingsModel/Command.cpp | 13 ++++--------- src/cascadia/TerminalSettingsModel/HashUtils.h | 6 +++++- .../KeyChordSerialization.cpp | 15 ++++++++------- 5 files changed, 18 insertions(+), 27 deletions(-) delete mode 100644 src/cascadia/TerminalSettingsEditor/KeyChordConverter.h diff --git a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h b/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h deleted file mode 100644 index dd65a07cfbd..00000000000 --- a/src/cascadia/TerminalSettingsEditor/KeyChordConverter.h +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#pragma once - -#include "NullCheckToVisibilityConverter.g.h" -#include "../inc/cppwinrt_utils.h" - -DECLARE_CONVERTER(winrt::Microsoft::Terminal::Settings::Editor, NullCheckToVisibilityConverter); diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 766cd9260c1..cda5f929689 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -50,7 +50,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Takes the KeyModifier flags from Terminal and maps them to the WinRT types which are used by XAML + // - Takes the KeyModifier flags from Terminal and maps them to the Windows WinRT types // Return Value: // - a Windows::System::VirtualKeyModifiers object with the flags of which modifiers used. Windows::System::VirtualKeyModifiers ActionMap::ConvertVKModifiers(KeyModifiers modifiers) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index f87af881b64..5f1828d2101 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -151,15 +151,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void Command::EraseKey(const Control::KeyChord& keys) { - for (auto pos = _keyMappings.begin(); pos < _keyMappings.end(); ++pos) - { - if (keys.Modifiers() == pos->Modifiers() && keys.Vkey() == pos->Vkey()) - { - // Found the KeyChord, remove it. - _keyMappings.erase(pos); - return; - } - } + _keyMappings.erase(std::remove_if(_keyMappings.begin(), _keyMappings.end(), [&keys](const Control::KeyChord& iterKey) { + return keys.Modifiers() == iterKey.Modifiers() && keys.Vkey() == iterKey.Vkey(); + }), + _keyMappings.end()); } // Function Description: diff --git a/src/cascadia/TerminalSettingsModel/HashUtils.h b/src/cascadia/TerminalSettingsModel/HashUtils.h index 95b0391fedf..f0b81499507 100644 --- a/src/cascadia/TerminalSettingsModel/HashUtils.h +++ b/src/cascadia/TerminalSettingsModel/HashUtils.h @@ -30,7 +30,11 @@ namespace Microsoft::Terminal::Settings::Model::HashUtils template constexpr size_t HashProperty(const T& val, Args&&... more) { - return HashProperty(val) ^ HashProperty(std::forward(more)...); + // Inspired by boost::hash_combine, which causes this effect... + // seed ^= hash_value(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); + // Source: https://www.boost.org/doc/libs/1_35_0/doc/html/boost/hash_combine_id241013.html + const auto seed{ HashProperty(val) }; + return seed ^ (0x9e3779b9 + (seed << 6) + (seed >> 2) + HashProperty(std::forward(more)...)); } template<> diff --git a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp index a9b35af009e..25dec03d0d4 100644 --- a/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp @@ -105,12 +105,13 @@ static const std::unordered_map vkeyNamePairs { // - hstr: the string to parse into a keychord. // Return Value: // - a newly constructed KeyChord -static KeyChord _FromString(const std::wstring_view& wstr) +static KeyChord _fromString(const std::wstring_view& wstr) { // Split the string on '+' std::wstring temp; std::vector parts; - std::wstringstream wss(wstr.data()); + std::wstringstream wss; + wss << wstr; while (std::getline(wss, temp, L'+')) { @@ -219,7 +220,7 @@ static KeyChord _FromString(const std::wstring_view& wstr) // names listed in the vkeyNamePairs vector above, or is one of 0-9a-z. // Return Value: // - a string which is an equivalent serialization of this object. -static std::wstring _ToString(const KeyChord& chord) +static std::wstring _toString(const KeyChord& chord) { if (!chord) { @@ -301,12 +302,12 @@ static std::wstring _ToString(const KeyChord& chord) KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr) { - return _FromString(hstr); + return _fromString(hstr); } winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord) { - return hstring{ _ToString(chord) }; + return hstring{ _toString(chord) }; } KeyChord ConversionTrait::FromJson(const Json::Value& json) @@ -328,7 +329,7 @@ KeyChord ConversionTrait::FromJson(const Json::Value& json) { throw winrt::hresult_invalid_argument{}; } - return _FromString(til::u8u16(keyChordText)); + return _fromString(til::u8u16(keyChordText)); } catch (...) { @@ -343,7 +344,7 @@ bool ConversionTrait::CanConvert(const Json::Value& json) Json::Value ConversionTrait::ToJson(const KeyChord& val) { - return til::u16u8(_ToString(val)); + return til::u16u8(_toString(val)); } std::string ConversionTrait::TypeDescription() const From e3b0f48fec43734816fed7927738445af43e3a1c Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 4 May 2021 14:10:58 -0700 Subject: [PATCH 28/31] apply Leonard's feedback --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 5 +---- src/cascadia/TerminalSettingsModel/Command.cpp | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 090c7b401cd..3ba520bbda9 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -210,10 +210,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto actionMap{ make_self() }; // copy _KeyMap (KeyChord --> ID) - for (const auto& [keys, actionID] : _KeyMap) - { - actionMap->_KeyMap.emplace(KeyChord{ keys.Modifiers(), keys.Vkey() }, actionID); - } + actionMap->_KeyMap = _KeyMap; // copy _ActionMap (ID --> Command) for (const auto& [actionID, cmd] : _ActionMap) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 5f1828d2101..0f954225977 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -43,10 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto command{ winrt::make_self() }; command->_name = _name; command->_ActionAndArgs = *get_self(_ActionAndArgs)->Copy(); - for (const auto& keys : _keyMappings) - { - command->_keyMappings.emplace_back(keys.Modifiers(), keys.Vkey()); - } + command->_keyMappings = _keyMappings; command->_iconPath = _iconPath; command->_IterateOn = _IterateOn; From 28479dd96fe80df376dcc95ef5326b293ec00891 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 4 May 2021 18:04:16 -0700 Subject: [PATCH 29/31] address DHowett comments --- .../TerminalSettingsModel/ActionMap.cpp | 277 +++++++++++------- .../TerminalSettingsModel/ActionMap.h | 43 ++- .../TerminalSettingsModel/ActionMap.idl | 2 +- 3 files changed, 192 insertions(+), 130 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 3ba520bbda9..2fcfe0e2359 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -11,6 +11,23 @@ using namespace winrt::Microsoft::Terminal::Control; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs) + { + size_t hashedAction{ HashUtils::HashProperty(actionAndArgs.Action()) }; + + size_t hashedArgs{}; + if (const auto& args{ actionAndArgs.Args() }) + { + hashedArgs = gsl::narrow_cast(args.Hash()); + } + else + { + std::hash argsHash; + hashedArgs = argsHash(nullptr); + } + return hashedAction ^ hashedArgs; + } + ActionMap::ActionMap() : _NestedCommands{ single_threaded_map() } { @@ -30,23 +47,23 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - If the command cannot be found in this layer, nullopt. std::optional ActionMap::_GetActionByID(const InternalActionID actionID) const { - // Check the consolidated actions - auto cmdPair{ _ConsolidatedActions.find(actionID) }; - if (cmdPair != _ConsolidatedActions.end()) + // Check the masked actions + const auto maskedPair{ _MaskedActions.find(actionID) }; + if (maskedPair != _MaskedActions.end()) { // ActionMap should never point to nullptr - FAIL_FAST_IF_NULL(cmdPair->second); + FAIL_FAST_IF_NULL(maskedPair->second); - // consolidated actions cannot contain nested or invalid commands, + // masked actions cannot contain nested or invalid commands, // so we can just return it directly. - return cmdPair->second; + return maskedPair->second; } // Check current layer - cmdPair = _ActionMap.find(actionID); - if (cmdPair != _ActionMap.end()) + const auto actionMapPair{ _ActionMap.find(actionID) }; + if (actionMapPair != _ActionMap.end()) { - const auto& cmd{ cmdPair->second }; + auto& cmd{ actionMapPair->second }; // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); @@ -118,12 +135,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // There should only ever by one of each command (identified by the actionID) in the nameMap. void ActionMap::_PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const { - const ActionHash actionHasher{}; - std::unordered_set visitedActionIDs{ actionHasher(make()) }; + std::unordered_set visitedActionIDs; for (const auto& cmd : _GetCumulativeActions()) { + // skip over all invalid actions + if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) + { + continue; + } + // Only populate NameMap with actions that haven't been visited already. - const auto actionID{ actionHasher(cmd.ActionAndArgs()) }; + const auto actionID{ Hash(cmd.ActionAndArgs()) }; if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { const auto& name{ cmd.Name() }; @@ -145,10 +167,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // First, add actions from our current layer std::vector cumulativeActions; - cumulativeActions.reserve(_ConsolidatedActions.size() + _ActionMap.size()); + cumulativeActions.reserve(_MaskedActions.size() + _ActionMap.size()); - // Consolidated actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. - std::transform(_ConsolidatedActions.begin(), _ConsolidatedActions.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { + // masked actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. + std::transform(_MaskedActions.begin(), _MaskedActions.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { return actionPair.second; }); std::transform(_ActionMap.begin(), _ActionMap.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { @@ -171,7 +193,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (!_GlobalHotkeysCache) { - const ActionHash actionHasher{}; std::unordered_set visitedActionIDs{}; std::unordered_map globalHotkeys; for (const auto& cmd : _GetCumulativeActions()) @@ -182,7 +203,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (actionAndArgs.Action() == ShortcutAction::GlobalSummon || actionAndArgs.Action() == ShortcutAction::QuakeMode) { // (2) haven't been visited already - const auto actionID{ actionHasher(actionAndArgs) }; + const auto actionID{ Hash(actionAndArgs) }; if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { const auto& cmdImpl{ get_self(cmd) }; @@ -218,10 +239,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_ActionMap.emplace(actionID, *(get_self(cmd)->Copy())); } - // copy _ConsolidatedActions (ID --> Command) - for (const auto& [actionID, cmd] : _ConsolidatedActions) + // copy _MaskedActions (ID --> Command) + for (const auto& [actionID, cmd] : _MaskedActions) { - actionMap->_ConsolidatedActions.emplace(actionID, *(get_self(cmd)->Copy())); + actionMap->_MaskedActions.emplace(actionID, *(get_self(cmd)->Copy())); } // copy _NestedCommands (Name --> Command) @@ -252,6 +273,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } + // invalidate caches + _NameMapCache = nullptr; + _GlobalHotkeysCache = nullptr; + // Handle nested commands const auto cmdImpl{ get_self(cmd) }; if (cmdImpl->IsNestedCommand()) @@ -277,69 +302,78 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // cmd.Keys and cmd.Action have a many-to-one relationship. // If cmd.Keys is empty, we don't care. // If action is "unbound"/"invalid", you're explicitly unbinding the provided cmd.keys. - // NOTE: If we're unbinding a command from a different layer, we must use ConsolidatedActions + // NOTE: If we're unbinding a command from a different layer, we must use maskedActions // to keep track of what key mappings are still valid. - // _TryUpdateActionMap may update oldCmd and consolidatedCmd + // _TryUpdateActionMap may update oldCmd and maskedCmd Model::Command oldCmd{ nullptr }; - Model::Command consolidatedCmd{ nullptr }; - _TryUpdateActionMap(cmd, oldCmd, consolidatedCmd); + Model::Command maskedCmd{ nullptr }; + _TryUpdateActionMap(cmd, oldCmd, maskedCmd); - _TryUpdateName(cmd, oldCmd, consolidatedCmd); - _TryUpdateKeyChord(cmd, oldCmd, consolidatedCmd); + _TryUpdateName(cmd, oldCmd, maskedCmd); + _TryUpdateKeyChord(cmd, oldCmd, maskedCmd); } // Method Description: // - Try to add the new command to _ActionMap. // - If the command was added previously in this layer, populate oldCmd. - // - If the command was added previously in another layer, populate consolidatedCmd. + // - If the command was added previously in another layer, populate maskedCmd. // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action found in _ActionMap, if one already exists - // - consolidatedAction: the action found in a parent layer, if one already exists - void ActionMap::_TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd) + // - maskedAction: the action found in a parent layer, if one already exists + void ActionMap::_TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& maskedCmd) { - const auto actionID{ ActionHash{}(cmd.ActionAndArgs()) }; - + // Example: + // { "command": "copy", "keys": "ctrl+c" } --> add the action in for the first time + // { "command": "copy", "keys": "ctrl+shift+c" } --> update oldCmd + const auto actionID{ Hash(cmd.ActionAndArgs()) }; const auto& actionPair{ _ActionMap.find(actionID) }; - if (actionPair != _ActionMap.end()) - { - // We're adding an action that already exists in our layer. - // Record it so that we update it with any new information. - oldCmd = actionPair->second; - } - else + if (actionPair == _ActionMap.end()) { // add this action in for the first time _ActionMap.emplace(actionID, cmd); } - - // Now check if this action was introduced in another layer. - const auto& consolidatedActionPair{ _ConsolidatedActions.find(actionID) }; - if (consolidatedActionPair != _ConsolidatedActions.end()) + else { - // We're adding an action that already existed on a different layer. + // We're adding an action that already exists in our layer. // Record it so that we update it with any new information. - consolidatedCmd = consolidatedActionPair->second; + oldCmd = actionPair->second; } - else + + // Masked Actions + // + // Example: + // parent: { "command": "copy", "keys": "ctrl+c" } --> add the action to parent._ActionMap + // current: { "command": "copy", "keys": "ctrl+shift+c" } --> look through parents for the "ctrl+c" binding, add it to _MaskedActions + // { "command": "copy", "keys": "ctrl+ins" } --> this should already be in _MaskedActions + + // Now check if this action was introduced in another layer. + const auto& maskedActionPair{ _MaskedActions.find(actionID) }; + if (maskedActionPair == _MaskedActions.end()) { - // Check if we need to add this to our list of consolidated commands. + // Check if we need to add this to our list of masked commands. FAIL_FAST_IF(_parents.size() > 1); for (const auto& parent : _parents) { // NOTE: This only checks the layer above us, but that's ok. - // If we had to find one from a layer above that, parent->_ConsolidatedActions + // If we had to find one from a layer above that, parent->_MaskedActions // would have found it, so we inherit it for free! const auto& inheritedCmd{ parent->_GetActionByID(actionID) }; if (inheritedCmd.has_value() && inheritedCmd.value()) { const auto& inheritedCmdImpl{ get_self(inheritedCmd.value()) }; - consolidatedCmd = *inheritedCmdImpl->Copy(); - _ConsolidatedActions.emplace(actionID, consolidatedCmd); + maskedCmd = *inheritedCmdImpl->Copy(); + _MaskedActions.emplace(actionID, maskedCmd); } } } + else + { + // We're adding an action that already existed on a different layer. + // Record it so that we update it with any new information. + maskedCmd = maskedActionPair->second; + } } // Method Description: @@ -347,9 +381,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action that already exists in our internal state. May be null. - // - consolidatedCmd: the consolidated action that already exists in our internal state. May be null. - void ActionMap::_TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd) + // - maskedCmd: the masked action that already exists in our internal state. May be null. + void ActionMap::_TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskedCmd) { + // Example: + // { "name": "foo", "command": "copy" } --> we are setting a name, update oldCmd and maskedCmd + // { "command": "copy" } --> no change to name, exit early const auto cmdImpl{ get_self(cmd) }; if (!cmdImpl->HasName()) { @@ -357,8 +394,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return; } - // If we have a Command in our _ActionMap that we're trying to update, - // update it. + // Update oldCmd: + // If we have a Command in our _ActionMap that we're trying to update, + // update it. const auto newName{ cmd.Name() }; if (oldCmd) { @@ -372,16 +410,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - // The same goes for consolidated actions. - if (consolidatedCmd) + // Update maskedCmd: + if (maskedCmd) { // This command has a name, check if it's new. - if (newName != consolidatedCmd.Name()) + if (newName != maskedCmd.Name()) { // The new name differs from the old name, // update our name. - auto consolidatedCmdImpl{ get_self(consolidatedCmd) }; - consolidatedCmdImpl->Name(newName); + auto maskedCmdImpl{ get_self(maskedCmd) }; + maskedCmdImpl->Name(newName); } } @@ -394,59 +432,90 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action that already exists in our internal state. May be null. - // - consolidatedCmd: the consolidated action that already exists in our internal state. May be null. - void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd) + // - maskedCmd: the masked action that already exists in our internal state. May be null. + void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskedCmd) { - if (const auto keys{ cmd.Keys() }) + // Example: + // { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord, update oldCmd and maskedCmd + // { "name": "foo", "command": "copy" } --> no change to keys, exit early + const auto keys{ cmd.Keys() }; + if (!keys) { - const auto oldKeyPair{ _KeyMap.find(keys) }; - if (oldKeyPair != _KeyMap.end()) + // the user is not trying to update the keys. + return; + } + + // Handle collisions + const auto oldKeyPair{ _KeyMap.find(keys) }; + if (oldKeyPair != _KeyMap.end()) + { + // Collision: The key chord was already in use. + // + // Example: + // { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (different branch) + // { "command": "paste", "keys": "ctrl+c" } --> Collision! (this branch) + // + // Remove the old one. (unbind "copy" in the example above) + const auto actionPair{ _ActionMap.find(oldKeyPair->second) }; + const auto conflictingCmd{ actionPair->second }; + const auto conflictingCmdImpl{ get_self(conflictingCmd) }; + conflictingCmdImpl->EraseKey(keys); + } + else if (const auto& conflictingCmd{ GetActionByKeyChord(keys) }) + { + // Collision with ancestor: The key chord was already in use, but by an action in another layer + // + // Example: + // parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (different branch) + // current: { "command": "paste", "keys": "ctrl+c" } --> Collision with ancestor! (this branch, sub-branch 1) + // { "command": "unbound", "keys": "ctrl+c" } --> Collision with masked action! (this branch, sub-branch 2) + const auto conflictingActionID{ Hash(conflictingCmd.ActionAndArgs()) }; + const auto maskedCmdPair{ _MaskedActions.find(conflictingActionID) }; + if (maskedCmdPair == _MaskedActions.end()) { - // Collision: The key chord was already in use. - // Remove the old one. - const auto& actionPair{ _ActionMap.find(oldKeyPair->second) }; - const auto& conflictingCmd{ actionPair->second }; - const auto& conflictingCmdImpl{ get_self(conflictingCmd) }; - conflictingCmdImpl->EraseKey(keys); + // This is the first time we're colliding with an action from a different layer, + // so let's add this action to _MaskedActions and update it appropriately. + // Create a copy of the conflicting action, + // and erase the conflicting key chord from the copy. + const auto conflictingCmdImpl{ get_self(conflictingCmd) }; + const auto conflictingCmdCopy{ conflictingCmdImpl->Copy() }; + conflictingCmdCopy->EraseKey(keys); + _MaskedActions.emplace(conflictingActionID, *conflictingCmdCopy); } - else if (const auto& conflictingCmd{ GetActionByKeyChord(keys) }) + else { - // Collision: The key chord was already in use, but by an action in another layer - const auto conflictingActionID{ ActionHash{}(conflictingCmd.ActionAndArgs()) }; - const auto& consolidatedCmdPair{ _ConsolidatedActions.find(conflictingActionID) }; - if (consolidatedCmdPair != _ConsolidatedActions.end()) - { - const auto& consolidatedCmdImpl{ get_self(consolidatedCmdPair->second) }; - consolidatedCmdImpl->EraseKey(keys); - } - else - { - // Create a copy of the conflicting action, - // and erase the conflicting key chord from the copy. - const auto& conflictingCmdImpl{ get_self(conflictingCmd) }; - const auto& conflictingCmdCopy{ conflictingCmdImpl->Copy() }; - conflictingCmdCopy->EraseKey(keys); - _ConsolidatedActions.emplace(conflictingActionID, *conflictingCmdCopy); - } + // We've collided with this action before. Let's resolve a collision with a masked action. + const auto maskedCmdImpl{ get_self(maskedCmdPair->second) }; + maskedCmdImpl->EraseKey(keys); } + } - // Assign the new action. - const auto actionID{ ActionHash{}(cmd.ActionAndArgs()) }; - _KeyMap.insert_or_assign(keys, actionID); + // Assign the new action in the _KeyMap. + const auto actionID{ Hash(cmd.ActionAndArgs()) }; + _KeyMap.insert_or_assign(keys, actionID); - if (oldCmd) - { - // Update inner Command with new key chord - auto oldCmdImpl{ get_self(oldCmd) }; - oldCmdImpl->RegisterKey(keys); - } + // Additive operation: + // Register the new key chord with oldCmd (an existing _ActionMap entry) + // Example: + // { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (section above) + // { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (oldCmd) + if (oldCmd) + { + // Update inner Command with new key chord + auto oldCmdImpl{ get_self(oldCmd) }; + oldCmdImpl->RegisterKey(keys); + } - if (consolidatedCmd) - { - // Update inner Command with new key chord - auto consolidatedCmdImpl{ get_self(consolidatedCmd) }; - consolidatedCmdImpl->RegisterKey(keys); - } + // Additive operation: + // Register the new key chord with maskedCmd (an existing _MaskedAction entry) + // Example: + // parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" to parent._ActionMap (different branch in a different layer) + // current: { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (maskedCmd) + if (maskedCmd) + { + // Update inner Command with new key chord + auto maskedCmdImpl{ get_self(maskedCmd) }; + maskedCmdImpl->RegisterKey(keys); } } @@ -460,7 +529,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::Command ActionMap::GetActionByKeyChord(Control::KeyChord const& keys) const { // Check the current layer - const auto& cmd{ _GetActionByKeyChordInternal(keys) }; + const auto cmd{ _GetActionByKeyChordInternal(keys) }; if (cmd.has_value()) { return *cmd; @@ -535,7 +604,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Check our internal state. const ActionAndArgs& actionAndArgs{ myAction, myArgs }; - const auto hash{ ActionHash{}(actionAndArgs) }; + const auto hash{ Hash(actionAndArgs) }; if (const auto& cmd{ _GetActionByID(hash) }) { return cmd.value().Keys(); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 0b8257d66c5..f97705f0a40 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -28,9 +28,14 @@ namespace SettingsModelLocalTests class TerminalSettingsTests; } +//inline bool operator==(const winrt::Microsoft::Terminal::Control::KeyChord& lhs, const winrt::Microsoft::Terminal::Control::KeyChord& rhs) +//{ +// return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey(); +//} + namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - typedef size_t InternalActionID; + using InternalActionID = size_t; struct KeyChordHash { @@ -48,27 +53,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; - struct ActionHash - { - InternalActionID operator()(const Model::ActionAndArgs& actionAndArgs) const - { - std::hash actionHash; - std::size_t hashedAction{ actionHash(actionAndArgs.Action()) }; - - std::size_t hashedArgs; - if (const auto& args{ actionAndArgs.Args() }) - { - hashedArgs = gsl::narrow_cast(args.Hash()); - } - else - { - std::hash argsHash; - hashedArgs = argsHash(nullptr); - } - return hashedAction ^ hashedArgs; - } - }; - struct ActionMap : ActionMapT, IInheritable { ActionMap(); @@ -107,9 +91,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_map _KeyMap; std::unordered_map _ActionMap; - // These are actions that were consolidated across multiple layers. - // They don't need to be serialized. - std::unordered_map _ConsolidatedActions; + // Masked Actions: + // These are actions that were introduced in an ancestor, + // but were unbound in the current layer. + // _ActionMap shows a Command with keys that were added in this layer, + // whereas _MaskedActions provides a view that encompasses all of + // the valid associated key chords. + // Maintaining this map allows us to return a valid Command + // in GetKeyBindingForAction. + // Additionally, these commands to not need to be serialized, + // whereas those in _ActionMap do. These actions provide more data + // than is necessary to be serialized. + std::unordered_map _MaskedActions; friend class SettingsModelLocalTests::KeyBindingsTests; friend class SettingsModelLocalTests::DeserializationTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 5d1c67fe9fc..11022ecbc9b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -11,7 +11,7 @@ namespace Microsoft.Terminal.Settings.Model Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); - Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); + [method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); Windows.Foundation.Collections.IMapView NameMap { get; }; Windows.Foundation.Collections.IMapView GlobalHotkeys(); From ad42c2da689f48ac0e540e513fbe20f6309bc05f Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 4 May 2021 20:32:47 -0700 Subject: [PATCH 30/31] moar DHowett comments --- .../TerminalSettingsModel/ActionMap.cpp | 117 +++++++++--------- .../TerminalSettingsModel/ActionMap.h | 13 +- 2 files changed, 65 insertions(+), 65 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 2fcfe0e2359..36c2f751b6d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -47,16 +47,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - If the command cannot be found in this layer, nullopt. std::optional ActionMap::_GetActionByID(const InternalActionID actionID) const { - // Check the masked actions - const auto maskedPair{ _MaskedActions.find(actionID) }; - if (maskedPair != _MaskedActions.end()) + // Check the masking actions + const auto maskingPair{ _MaskingActions.find(actionID) }; + if (maskingPair != _MaskingActions.end()) { // ActionMap should never point to nullptr - FAIL_FAST_IF_NULL(maskedPair->second); + FAIL_FAST_IF_NULL(maskingPair->second); - // masked actions cannot contain nested or invalid commands, + // masking actions cannot contain nested or invalid commands, // so we can just return it directly. - return maskedPair->second; + return maskingPair->second; } // Check current layer @@ -167,10 +167,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // First, add actions from our current layer std::vector cumulativeActions; - cumulativeActions.reserve(_MaskedActions.size() + _ActionMap.size()); + cumulativeActions.reserve(_MaskingActions.size() + _ActionMap.size()); - // masked actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. - std::transform(_MaskedActions.begin(), _MaskedActions.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { + // masking actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. + std::transform(_MaskingActions.begin(), _MaskingActions.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { return actionPair.second; }); std::transform(_ActionMap.begin(), _ActionMap.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { @@ -239,10 +239,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_ActionMap.emplace(actionID, *(get_self(cmd)->Copy())); } - // copy _MaskedActions (ID --> Command) - for (const auto& [actionID, cmd] : _MaskedActions) + // copy _MaskingActions (ID --> Command) + for (const auto& [actionID, cmd] : _MaskingActions) { - actionMap->_MaskedActions.emplace(actionID, *(get_self(cmd)->Copy())); + actionMap->_MaskingActions.emplace(actionID, *(get_self(cmd)->Copy())); } // copy _NestedCommands (Name --> Command) @@ -302,27 +302,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // cmd.Keys and cmd.Action have a many-to-one relationship. // If cmd.Keys is empty, we don't care. // If action is "unbound"/"invalid", you're explicitly unbinding the provided cmd.keys. - // NOTE: If we're unbinding a command from a different layer, we must use maskedActions + // NOTE: If we're unbinding a command from a different layer, we must use maskingActions // to keep track of what key mappings are still valid. - // _TryUpdateActionMap may update oldCmd and maskedCmd + // _TryUpdateActionMap may update oldCmd and maskingCmd Model::Command oldCmd{ nullptr }; - Model::Command maskedCmd{ nullptr }; - _TryUpdateActionMap(cmd, oldCmd, maskedCmd); + Model::Command maskingCmd{ nullptr }; + _TryUpdateActionMap(cmd, oldCmd, maskingCmd); - _TryUpdateName(cmd, oldCmd, maskedCmd); - _TryUpdateKeyChord(cmd, oldCmd, maskedCmd); + _TryUpdateName(cmd, oldCmd, maskingCmd); + _TryUpdateKeyChord(cmd, oldCmd, maskingCmd); } // Method Description: // - Try to add the new command to _ActionMap. // - If the command was added previously in this layer, populate oldCmd. - // - If the command was added previously in another layer, populate maskedCmd. + // - If the command was added previously in another layer, populate maskingCmd. // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action found in _ActionMap, if one already exists - // - maskedAction: the action found in a parent layer, if one already exists - void ActionMap::_TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& maskedCmd) + // - maskingAction: the action found in a parent layer, if one already exists + void ActionMap::_TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& maskingCmd) { // Example: // { "command": "copy", "keys": "ctrl+c" } --> add the action in for the first time @@ -336,35 +336,35 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - // We're adding an action that already exists in our layer. + // This is an action that we already have a mutable "masking" record for. // Record it so that we update it with any new information. oldCmd = actionPair->second; } - // Masked Actions + // Masking Actions // // Example: // parent: { "command": "copy", "keys": "ctrl+c" } --> add the action to parent._ActionMap - // current: { "command": "copy", "keys": "ctrl+shift+c" } --> look through parents for the "ctrl+c" binding, add it to _MaskedActions - // { "command": "copy", "keys": "ctrl+ins" } --> this should already be in _MaskedActions + // current: { "command": "copy", "keys": "ctrl+shift+c" } --> look through parents for the "ctrl+c" binding, add it to _MaskingActions + // { "command": "copy", "keys": "ctrl+ins" } --> this should already be in _MaskingActions // Now check if this action was introduced in another layer. - const auto& maskedActionPair{ _MaskedActions.find(actionID) }; - if (maskedActionPair == _MaskedActions.end()) + const auto& maskingActionPair{ _MaskingActions.find(actionID) }; + if (maskingActionPair == _MaskingActions.end()) { - // Check if we need to add this to our list of masked commands. + // Check if we need to add this to our list of masking commands. FAIL_FAST_IF(_parents.size() > 1); for (const auto& parent : _parents) { // NOTE: This only checks the layer above us, but that's ok. - // If we had to find one from a layer above that, parent->_MaskedActions + // If we had to find one from a layer above that, parent->_MaskingActions // would have found it, so we inherit it for free! const auto& inheritedCmd{ parent->_GetActionByID(actionID) }; if (inheritedCmd.has_value() && inheritedCmd.value()) { const auto& inheritedCmdImpl{ get_self(inheritedCmd.value()) }; - maskedCmd = *inheritedCmdImpl->Copy(); - _MaskedActions.emplace(actionID, maskedCmd); + maskingCmd = *inheritedCmdImpl->Copy(); + _MaskingActions.emplace(actionID, maskingCmd); } } } @@ -372,7 +372,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // We're adding an action that already existed on a different layer. // Record it so that we update it with any new information. - maskedCmd = maskedActionPair->second; + maskingCmd = maskingActionPair->second; } } @@ -381,11 +381,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action that already exists in our internal state. May be null. - // - maskedCmd: the masked action that already exists in our internal state. May be null. - void ActionMap::_TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskedCmd) + // - maskingCmd: the masking action that already exists in our internal state. May be null. + void ActionMap::_TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskingCmd) { // Example: - // { "name": "foo", "command": "copy" } --> we are setting a name, update oldCmd and maskedCmd + // { "name": "foo", "command": "copy" } --> we are setting a name, update oldCmd and maskingCmd // { "command": "copy" } --> no change to name, exit early const auto cmdImpl{ get_self(cmd) }; if (!cmdImpl->HasName()) @@ -410,16 +410,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - // Update maskedCmd: - if (maskedCmd) + // Update maskingCmd: + // We have a Command that is masking one from a parent layer. + // We need to ensure that this has the correct name. That way, + // we can return an accumulated view of a Command at this layer. + // This differs from oldCmd which is mainly used for serialization + // by recording the delta of the Command in this layer. + if (maskingCmd) { // This command has a name, check if it's new. - if (newName != maskedCmd.Name()) + if (newName != maskingCmd.Name()) { // The new name differs from the old name, // update our name. - auto maskedCmdImpl{ get_self(maskedCmd) }; - maskedCmdImpl->Name(newName); + auto maskingCmdImpl{ get_self(maskingCmd) }; + maskingCmdImpl->Name(newName); } } @@ -432,11 +437,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Arguments: // - cmd: the action we're trying to register // - oldCmd: the action that already exists in our internal state. May be null. - // - maskedCmd: the masked action that already exists in our internal state. May be null. - void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskedCmd) + // - maskingCmd: the masking action that already exists in our internal state. May be null. + void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& maskingCmd) { // Example: - // { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord, update oldCmd and maskedCmd + // { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord, update oldCmd and maskingCmd // { "name": "foo", "command": "copy" } --> no change to keys, exit early const auto keys{ cmd.Keys() }; if (!keys) @@ -468,25 +473,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Example: // parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (different branch) // current: { "command": "paste", "keys": "ctrl+c" } --> Collision with ancestor! (this branch, sub-branch 1) - // { "command": "unbound", "keys": "ctrl+c" } --> Collision with masked action! (this branch, sub-branch 2) + // { "command": "unbound", "keys": "ctrl+c" } --> Collision with masking action! (this branch, sub-branch 2) const auto conflictingActionID{ Hash(conflictingCmd.ActionAndArgs()) }; - const auto maskedCmdPair{ _MaskedActions.find(conflictingActionID) }; - if (maskedCmdPair == _MaskedActions.end()) + const auto maskingCmdPair{ _MaskingActions.find(conflictingActionID) }; + if (maskingCmdPair == _MaskingActions.end()) { // This is the first time we're colliding with an action from a different layer, - // so let's add this action to _MaskedActions and update it appropriately. + // so let's add this action to _MaskingActions and update it appropriately. // Create a copy of the conflicting action, // and erase the conflicting key chord from the copy. const auto conflictingCmdImpl{ get_self(conflictingCmd) }; const auto conflictingCmdCopy{ conflictingCmdImpl->Copy() }; conflictingCmdCopy->EraseKey(keys); - _MaskedActions.emplace(conflictingActionID, *conflictingCmdCopy); + _MaskingActions.emplace(conflictingActionID, *conflictingCmdCopy); } else { - // We've collided with this action before. Let's resolve a collision with a masked action. - const auto maskedCmdImpl{ get_self(maskedCmdPair->second) }; - maskedCmdImpl->EraseKey(keys); + // We've collided with this action before. Let's resolve a collision with a masking action. + const auto maskingCmdImpl{ get_self(maskingCmdPair->second) }; + maskingCmdImpl->EraseKey(keys); } } @@ -507,15 +512,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Additive operation: - // Register the new key chord with maskedCmd (an existing _MaskedAction entry) + // Register the new key chord with maskingCmd (an existing _maskingAction entry) // Example: // parent: { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" to parent._ActionMap (different branch in a different layer) - // current: { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (maskedCmd) - if (maskedCmd) + // current: { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (maskingCmd) + if (maskingCmd) { // Update inner Command with new key chord - auto maskedCmdImpl{ get_self(maskedCmd) }; - maskedCmdImpl->RegisterKey(keys); + auto maskingCmdImpl{ get_self(maskingCmd) }; + maskingCmdImpl->RegisterKey(keys); } } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index f97705f0a40..b968712ee42 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -28,11 +28,6 @@ namespace SettingsModelLocalTests class TerminalSettingsTests; } -//inline bool operator==(const winrt::Microsoft::Terminal::Control::KeyChord& lhs, const winrt::Microsoft::Terminal::Control::KeyChord& rhs) -//{ -// return lhs.Modifiers() == rhs.Modifiers() && lhs.Vkey() == rhs.Vkey(); -//} - namespace winrt::Microsoft::Terminal::Settings::Model::implementation { using InternalActionID = size_t; @@ -91,18 +86,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_map _KeyMap; std::unordered_map _ActionMap; - // Masked Actions: + // Masking Actions: // These are actions that were introduced in an ancestor, - // but were unbound in the current layer. + // but were edited (or unbound) in the current layer. // _ActionMap shows a Command with keys that were added in this layer, - // whereas _MaskedActions provides a view that encompasses all of + // whereas _MaskingActions provides a view that encompasses all of // the valid associated key chords. // Maintaining this map allows us to return a valid Command // in GetKeyBindingForAction. // Additionally, these commands to not need to be serialized, // whereas those in _ActionMap do. These actions provide more data // than is necessary to be serialized. - std::unordered_map _MaskedActions; + std::unordered_map _MaskingActions; friend class SettingsModelLocalTests::KeyBindingsTests; friend class SettingsModelLocalTests::DeserializationTests; From 900226a678e1c409ecce16fd16228912ebcad834 Mon Sep 17 00:00:00 2001 From: Carlos Zamora Date: Tue, 4 May 2021 21:18:54 -0700 Subject: [PATCH 31/31] find & replace the wrong one :( --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 36c2f751b6d..a2507e24f9c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -336,7 +336,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - // This is an action that we already have a mutable "masking" record for. + // We're adding an action that already exists in our layer. // Record it so that we update it with any new information. oldCmd = actionPair->second; } @@ -370,7 +370,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - // We're adding an action that already existed on a different layer. + // This is an action that we already have a mutable "masking" record for. // Record it so that we update it with any new information. maskingCmd = maskingActionPair->second; }