terminal/src/cascadia/TerminalSettingsModel/KeyChordSerialization.cpp

373 lines
14 KiB
C++
Raw Normal View History

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "KeyChordSerialization.h"
Introduce TerminalSettingsModel project (#7667) Introduces a new TerminalSettingsModel (TSM) project. This project is responsible for (de)serializing and exposing Windows Terminal's settings as WinRT objects. ## References #885: TSM epic #1564: Settings UI is dependent on this for data binding and settings access #6904: TSM Spec In the process of ripping out TSM from TerminalApp, a few other changes were made to make this possible: 1. AppLogic's `ApplicationDisplayName` and `ApplicationVersion` was moved to `CascadiaSettings` - These are defined as static functions. They also no longer check if `AppLogic::Current()` is nullptr. 2. `enum LaunchMode` was moved from TerminalApp to TSM 3. `AzureConnectionType` and `TelnetConnectionType` were moved from the profile generators to their respective TerminalConnections 4. CascadiaSettings' `SettingsPath` and `DefaultSettingsPath` are exposed as `hstring` instead of `std::filesystem::path` 5. `Command::ExpandCommands()` was exposed via the IDL - This required some of the warnings to be saved to an `IVector` instead of `std::vector`, among some other small changes. 6. The localization resources had to be split into two halves. - Resource file linked in init.cpp. Verified at runtime thanks to the StaticResourceLoader. 7. Added constructors to some `ActionArgs` 8. Utils.h/cpp were moved to `cascadia/inc`. `JsonKey()` was moved to `JsonUtils`. Both TermApp and TSM need access to Utils.h/cpp. A large amount of work includes moving to the new namespace (`TerminalApp` --> `Microsoft::Terminal::Settings::Model`). Fixing the tests had its own complications. Testing required us to split up TSM into a DLL and LIB, similar to TermApp. Discussion on creating a non-local test variant can be found in #7743. Closes #885
2020-10-06 18:56:59 +02:00
#include "KeyChordSerialization.g.cpp"
#include <til/static_map.h>
Rename `Microsoft.Terminal.TerminalControl` to `.Control`; Split into dll & lib (#9472) **BE NOT AFRAID**. I know that there's 107 files in this PR, but almost all of it is just find/replacing `TerminalControl` with `Control`. This is the start of the work to move TermControl into multiple pieces, for #5000. The PR starts this work by: * Splits `TerminalControl` into separate lib and dll projects. We'll want control tests in the future, and for that, we'll need a lib. * Moves `ICoreSettings` back into the `Microsoft.Terminal.Core` namespace. We'll have other types in there soon too. * I could not tell you why this works suddenly. New VS versions? New cppwinrt version? Maybe we're just better at dealing with mdmerge bugs these days. * RENAMES `Microsoft.Terminal.TerminalControl` to `Microsoft.Terminal.Control`. This touches pretty much every file in the sln. Sorry about that (not sorry). An upcoming PR will move much of the logic in TermControl into a new `ControlCore` class that we'll add in `Microsoft.Terminal.Core`. `ControlCore` will then be unittest-able in the `UnitTests_TerminalCore`, which will help prevent regressions like #9455 ## Detailed Description of the Pull Request / Additional comments You're really gonna want to clean the sln first, then merge this into your branch, then rebuild. It's very likely that old winmds will get left behind. If you see something like ``` Error MDM2007 Cannot create type Microsoft.Terminal.TerminalControl.KeyModifiers in read-only metadata file Microsoft.Terminal.TerminalControl. ``` then that's what happened to you.
2021-03-17 21:47:24 +01:00
using namespace winrt::Microsoft::Terminal::Control;
Introduce TerminalSettingsModel project (#7667) Introduces a new TerminalSettingsModel (TSM) project. This project is responsible for (de)serializing and exposing Windows Terminal's settings as WinRT objects. ## References #885: TSM epic #1564: Settings UI is dependent on this for data binding and settings access #6904: TSM Spec In the process of ripping out TSM from TerminalApp, a few other changes were made to make this possible: 1. AppLogic's `ApplicationDisplayName` and `ApplicationVersion` was moved to `CascadiaSettings` - These are defined as static functions. They also no longer check if `AppLogic::Current()` is nullptr. 2. `enum LaunchMode` was moved from TerminalApp to TSM 3. `AzureConnectionType` and `TelnetConnectionType` were moved from the profile generators to their respective TerminalConnections 4. CascadiaSettings' `SettingsPath` and `DefaultSettingsPath` are exposed as `hstring` instead of `std::filesystem::path` 5. `Command::ExpandCommands()` was exposed via the IDL - This required some of the warnings to be saved to an `IVector` instead of `std::vector`, among some other small changes. 6. The localization resources had to be split into two halves. - Resource file linked in init.cpp. Verified at runtime thanks to the StaticResourceLoader. 7. Added constructors to some `ActionArgs` 8. Utils.h/cpp were moved to `cascadia/inc`. `JsonKey()` was moved to `JsonUtils`. Both TermApp and TSM need access to Utils.h/cpp. A large amount of work includes moving to the new namespace (`TerminalApp` --> `Microsoft::Terminal::Settings::Model`). Fixing the tests had its own complications. Testing required us to split up TSM into a DLL and LIB, similar to TermApp. Discussion on creating a non-local test variant can be found in #7743. Closes #885
2020-10-06 18:56:59 +02:00
using namespace winrt::Microsoft::Terminal::Settings::Model::implementation;
Introduce ActionMap to Terminal Settings Model (#9621) This entirely removes `KeyMapping` from the settings model, and builds on the work done in #9543 to consolidate all actions (key bindings and commands) into a unified data structure (`ActionMap`). ## References #9428 - Spec #6900 - Actions page Closes #7441 ## Detailed Description of the Pull Request / Additional comments The important thing here is to remember that we're shifting our philosophy of how to interact/represent actions. Prior to this, the actions arrays in the JSON would be deserialized twice: once for key bindings, and again for commands. By thinking of every entry in the relevant JSON as a `Command`, we can remove a lot of the context switching between working with a key binding vs a command palette item. #9543 allows us to make that shift. Given the work in that PR, we can now deserialize all of the relevant information from each JSON action item. This allows us to simplify `ActionMap::FromJson` to simply iterate over each JSON action item, deserialize it, and add it to our `ActionMap`. Internally, our `ActionMap` operates as discussed in #9428 by maintaining a `_KeyMap` that points to an action ID, and using that action ID to retrieve the `Command` from the `_ActionMap`. Adding actions to the `ActionMap` automatically accounts for name/key-chord collisions. A `NameMap` can be constructed when requested; this is for the Command Palette. Querying the `ActionMap` is fairly straightforward. Helper functions were needed to be able to distinguish an explicit unbinding vs the command not being found in the current layer. Internally, we store explicitly unbound names/key-chords as `ShortcutAction::Invalid` commands. However, we return `nullptr` when a query points to an unbound command. This is done to hide this complexity away from any caller. The command palette still needs special handling for nested and iterable commands. Thankfully, the expansion of iterable commands is performed on an `IMapView`, so we can just expose `NameMap` as a consolidation of `ActionMap`'s `NameMap` with its parents. The same can be said for exposing key chords in nested commands. ## Validation Steps Performed All local tests pass.
2021-05-05 06:50:13 +02:00
using namespace Microsoft::Terminal::Settings::Model::JsonUtils;
using VirtualKeyModifiers = winrt::Windows::System::VirtualKeyModifiers;
constexpr std::wstring_view CTRL_KEY{ L"ctrl" };
constexpr std::wstring_view SHIFT_KEY{ L"shift" };
constexpr std::wstring_view ALT_KEY{ L"alt" };
constexpr std::wstring_view WIN_KEY{ L"win" };
// If you modify this list you should modify the
// KeyChordSegment description in profiles.schema.json.
#define VKEY_NAME_PAIRS(XX) \
XX(VK_RETURN, L"enter") \
XX(VK_TAB, L"tab") \
XX(VK_SPACE, L"space") \
XX(VK_BACK, L"backspace") \
XX(VK_APPS, L"menu", L"app") \
XX(VK_INSERT, L"insert") \
XX(VK_DELETE, L"delete") \
XX(VK_HOME, L"home") \
XX(VK_END, L"end") \
XX(VK_NEXT, L"pgdn", L"pagedown") \
XX(VK_PRIOR, L"pgup", L"pageup") \
XX(VK_ESCAPE, L"esc", L"escape") \
XX(VK_LEFT, L"left") \
XX(VK_RIGHT, L"right") \
XX(VK_UP, L"up") \
XX(VK_DOWN, L"down") \
XX(VK_F1, L"f1") \
XX(VK_F2, L"f2") \
XX(VK_F3, L"f3") \
XX(VK_F4, L"f4") \
XX(VK_F5, L"f5") \
XX(VK_F6, L"f6") \
XX(VK_F7, L"f7") \
XX(VK_F8, L"f8") \
XX(VK_F9, L"f9") \
XX(VK_F10, L"f10") \
XX(VK_F11, L"f11") \
XX(VK_F12, L"f12") \
XX(VK_F13, L"f13") \
XX(VK_F14, L"f14") \
XX(VK_F15, L"f15") \
XX(VK_F16, L"f16") \
XX(VK_F17, L"f17") \
XX(VK_F18, L"f18") \
XX(VK_F19, L"f19") \
XX(VK_F20, L"f20") \
XX(VK_F21, L"f21") \
XX(VK_F22, L"f22") \
XX(VK_F23, L"f23") \
XX(VK_F24, L"f24") \
XX(VK_ADD, L"numpad_plus", L"numpad_add") \
XX(VK_SUBTRACT, L"numpad_minus", L"numpad_subtract") \
XX(VK_MULTIPLY, L"numpad_multiply") \
XX(VK_DIVIDE, L"numpad_divide") \
XX(VK_DECIMAL, L"numpad_period", L"numpad_decimal") \
XX(VK_NUMPAD0, L"numpad0", L"numpad_0") \
XX(VK_NUMPAD1, L"numpad1", L"numpad_1") \
XX(VK_NUMPAD2, L"numpad2", L"numpad_2") \
XX(VK_NUMPAD3, L"numpad3", L"numpad_3") \
XX(VK_NUMPAD4, L"numpad4", L"numpad_4") \
XX(VK_NUMPAD5, L"numpad5", L"numpad_5") \
XX(VK_NUMPAD6, L"numpad6", L"numpad_6") \
XX(VK_NUMPAD7, L"numpad7", L"numpad_7") \
XX(VK_NUMPAD8, L"numpad8", L"numpad_8") \
XX(VK_NUMPAD9, L"numpad9", L"numpad_9") \
XX(VK_OEM_PLUS, L"plus") /* '+' any country */ \
XX(VK_OEM_COMMA, L"comma") /* ',' any country */ \
XX(VK_OEM_MINUS, L"minus") /* '-' any country */ \
XX(VK_OEM_PERIOD, L"period") /* '.' any country */ \
XX(VK_BROWSER_BACK, L"browser_back") \
XX(VK_BROWSER_FORWARD, L"browser_forward") \
XX(VK_BROWSER_REFRESH, L"browser_refresh") \
XX(VK_BROWSER_STOP, L"browser_stop") \
XX(VK_BROWSER_SEARCH, L"browser_search") \
XX(VK_BROWSER_FAVORITES, L"browser_favorites") \
XX(VK_BROWSER_HOME, L"browser_home")
constexpr std::wstring_view vkeyPrefix{ L"vk(" };
constexpr std::wstring_view scanCodePrefix{ L"sc(" };
constexpr std::wstring_view codeSuffix{ L")" };
// Parses a vk(nnn) or sc(nnn) key chord part.
// If the part doesn't contain either of these two this function returns 0.
// For invalid arguments we throw an exception.
static int32_t parseNumericCode(const std::wstring_view& str, const std::wstring_view& prefix, const std::wstring_view& suffix)
{
if (!til::ends_with(str, suffix) || !til::starts_with(str, prefix))
{
return 0;
}
const auto value = til::from_wchars({ str.data() + prefix.size(), str.size() - prefix.size() - suffix.size() });
if (value > 0 && value < 256)
{
return gsl::narrow_cast<int32_t>(value);
}
throw winrt::hresult_invalid_argument(L"Invalid numeric argument to vk() or sc()");
}
// Function Description:
// - Deserializes the given string into a new KeyChord instance. If this
// fails to translate the string into a keychord, it will throw a
// hresult_invalid_argument exception.
// - The string should fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// names listed in the vkeyNamePairs vector above, or is one of 0-9a-zA-Z.
// Arguments:
// - hstr: the string to parse into a keychord.
// Return Value:
// - a newly constructed KeyChord
static KeyChord _fromString(std::wstring_view wstr)
{
using nameToVkeyPair = std::pair<std::wstring_view, int32_t>;
static const til::static_map nameToVkey{
// The above VKEY_NAME_PAIRS macro contains a list of key-binding names for each virtual key.
// This god-awful macro inverts VKEY_NAME_PAIRS and creates a static map of key-binding names to virtual keys.
// clang-format off
#define GENERATOR_1(vkey, name1) nameToVkeyPair{ name1, vkey },
#define GENERATOR_2(vkey, name1, name2) nameToVkeyPair{ name1, vkey }, nameToVkeyPair{ name2, vkey },
#define GENERATOR_3(vkey, name1, name2, name3) nameToVkeyPair{ name1, vkey }, nameToVkeyPair{ name2, vkey }, nameToVkeyPair{ name3, vkey },
#define GENERATOR_N(vkey, name1, name2, name3, MACRO, ...) MACRO
#define GENERATOR(...) GENERATOR_N(__VA_ARGS__, GENERATOR_3, GENERATOR_2, GENERATOR_1)(__VA_ARGS__)
VKEY_NAME_PAIRS(GENERATOR)
#undef GENERATOR_1
#undef GENERATOR_2
#undef GENERATOR_3
#undef GENERATOR_N
#undef GENERATOR
// clang-format on
};
VirtualKeyModifiers modifiers = VirtualKeyModifiers::None;
int32_t vkey = 0;
int32_t scanCode = 0;
while (!wstr.empty())
{
const auto part = til::prefix_split(wstr, L"+");
if (til::equals_insensitive_ascii(part, CTRL_KEY))
{
modifiers |= VirtualKeyModifiers::Control;
}
else if (til::equals_insensitive_ascii(part, ALT_KEY))
{
modifiers |= VirtualKeyModifiers::Menu;
}
else if (til::equals_insensitive_ascii(part, SHIFT_KEY))
{
modifiers |= VirtualKeyModifiers::Shift;
}
else if (til::equals_insensitive_ascii(part, WIN_KEY))
{
modifiers |= VirtualKeyModifiers::Windows;
}
else
{
if (vkey || scanCode)
{
throw winrt::hresult_invalid_argument(L"Key bindings like Ctrl+A+B are not valid");
}
// Characters 0-9, a-z, A-Z directly map to virtual keys.
if (part.size() == 1)
{
const auto wch = til::toupper_ascii(part[0]);
if ((wch >= L'0' && wch <= L'9') || (wch >= L'A' && wch <= L'Z'))
{
vkey = static_cast<int32_t>(wch);
continue;
}
}
// vk() allows a user to specify a virtual key code
// and sc() allows them to specify a scan code manually.
//
// ctrl+vk(0x09) for instance is the same as ctrl+tab, while win+sc(41) specifies
// a key binding which is (seemingly) always bound to the key below Esc.
vkey = parseNumericCode(part, vkeyPrefix, codeSuffix);
if (vkey)
{
continue;
}
scanCode = parseNumericCode(part, scanCodePrefix, codeSuffix);
if (scanCode)
{
continue;
}
// nameToVkey contains a few more mappings like "F11".
if (const auto it = nameToVkey.find(part); it != nameToVkey.end())
{
vkey = it->second;
continue;
}
// If we haven't found a key, attempt a keyboard mapping
if (part.size() == 1)
{
const auto oemVk = VkKeyScanW(part[0]);
if (oemVk != -1)
{
vkey = oemVk & 0xff;
const auto oemModifiers = oemVk >> 8;
// NOTE: WI_UpdateFlag _replaces_ a bit. This code _adds_ a bit.
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Shift, WI_IsFlagSet(oemModifiers, 1U));
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Control, WI_IsFlagSet(oemModifiers, 2U));
WI_SetFlagIf(modifiers, VirtualKeyModifiers::Menu, WI_IsFlagSet(oemModifiers, 4U));
continue;
}
}
throw winrt::hresult_invalid_argument(L"Invalid key binding");
}
}
return KeyChord{ modifiers, vkey, scanCode };
}
// Function Description:
// - Serialize this keychord into a string representation.
// - The string will fit the format "[ctrl+][alt+][shift+]<keyName>",
// where each modifier is optional, and keyName is either one of the
// 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.
Introduce ActionMap to Terminal Settings Model (#9621) This entirely removes `KeyMapping` from the settings model, and builds on the work done in #9543 to consolidate all actions (key bindings and commands) into a unified data structure (`ActionMap`). ## References #9428 - Spec #6900 - Actions page Closes #7441 ## Detailed Description of the Pull Request / Additional comments The important thing here is to remember that we're shifting our philosophy of how to interact/represent actions. Prior to this, the actions arrays in the JSON would be deserialized twice: once for key bindings, and again for commands. By thinking of every entry in the relevant JSON as a `Command`, we can remove a lot of the context switching between working with a key binding vs a command palette item. #9543 allows us to make that shift. Given the work in that PR, we can now deserialize all of the relevant information from each JSON action item. This allows us to simplify `ActionMap::FromJson` to simply iterate over each JSON action item, deserialize it, and add it to our `ActionMap`. Internally, our `ActionMap` operates as discussed in #9428 by maintaining a `_KeyMap` that points to an action ID, and using that action ID to retrieve the `Command` from the `_ActionMap`. Adding actions to the `ActionMap` automatically accounts for name/key-chord collisions. A `NameMap` can be constructed when requested; this is for the Command Palette. Querying the `ActionMap` is fairly straightforward. Helper functions were needed to be able to distinguish an explicit unbinding vs the command not being found in the current layer. Internally, we store explicitly unbound names/key-chords as `ShortcutAction::Invalid` commands. However, we return `nullptr` when a query points to an unbound command. This is done to hide this complexity away from any caller. The command palette still needs special handling for nested and iterable commands. Thankfully, the expansion of iterable commands is performed on an `IMapView`, so we can just expose `NameMap` as a consolidation of `ActionMap`'s `NameMap` with its parents. The same can be said for exposing key chords in nested commands. ## Validation Steps Performed All local tests pass.
2021-05-05 06:50:13 +02:00
static std::wstring _toString(const KeyChord& chord)
{
using vkeyToNamePair = std::pair<int32_t, std::wstring_view>;
static const til::static_map vkeyToName{
// The above VKEY_NAME_PAIRS macro contains a list of key-binding strings for each virtual key.
// This macro picks the first (most preferred) name and creates a static map of virtual keys to key-binding names.
#define GENERATOR(vkey, name1, ...) vkeyToNamePair{ vkey, name1 },
VKEY_NAME_PAIRS(GENERATOR)
#undef GENERATOR
};
Introduce ActionMap to Terminal Settings Model (#9621) This entirely removes `KeyMapping` from the settings model, and builds on the work done in #9543 to consolidate all actions (key bindings and commands) into a unified data structure (`ActionMap`). ## References #9428 - Spec #6900 - Actions page Closes #7441 ## Detailed Description of the Pull Request / Additional comments The important thing here is to remember that we're shifting our philosophy of how to interact/represent actions. Prior to this, the actions arrays in the JSON would be deserialized twice: once for key bindings, and again for commands. By thinking of every entry in the relevant JSON as a `Command`, we can remove a lot of the context switching between working with a key binding vs a command palette item. #9543 allows us to make that shift. Given the work in that PR, we can now deserialize all of the relevant information from each JSON action item. This allows us to simplify `ActionMap::FromJson` to simply iterate over each JSON action item, deserialize it, and add it to our `ActionMap`. Internally, our `ActionMap` operates as discussed in #9428 by maintaining a `_KeyMap` that points to an action ID, and using that action ID to retrieve the `Command` from the `_ActionMap`. Adding actions to the `ActionMap` automatically accounts for name/key-chord collisions. A `NameMap` can be constructed when requested; this is for the Command Palette. Querying the `ActionMap` is fairly straightforward. Helper functions were needed to be able to distinguish an explicit unbinding vs the command not being found in the current layer. Internally, we store explicitly unbound names/key-chords as `ShortcutAction::Invalid` commands. However, we return `nullptr` when a query points to an unbound command. This is done to hide this complexity away from any caller. The command palette still needs special handling for nested and iterable commands. Thankfully, the expansion of iterable commands is performed on an `IMapView`, so we can just expose `NameMap` as a consolidation of `ActionMap`'s `NameMap` with its parents. The same can be said for exposing key chords in nested commands. ## Validation Steps Performed All local tests pass.
2021-05-05 06:50:13 +02:00
if (!chord)
{
return {};
}
const auto modifiers = chord.Modifiers();
const auto vkey = chord.Vkey();
const auto scanCode = chord.ScanCode();
std::wstring buffer;
// Add modifiers
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Windows))
{
buffer.append(WIN_KEY);
buffer.push_back(L'+');
}
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Control))
{
buffer.append(CTRL_KEY);
buffer.push_back(L'+');
}
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Menu))
{
buffer.append(ALT_KEY);
buffer.push_back(L'+');
}
if (WI_IsFlagSet(modifiers, VirtualKeyModifiers::Shift))
{
buffer.append(SHIFT_KEY);
buffer.push_back(L'+');
}
if (scanCode)
{
buffer.append(scanCodePrefix);
buffer.append(std::to_wstring(scanCode));
buffer.append(codeSuffix);
return buffer;
}
// Quick lookup: ranges of vkeys that correlate directly to a key.
if ((vkey >= L'0' && vkey <= L'9') || (vkey >= L'A' && vkey <= L'Z'))
{
buffer.push_back(til::tolower_ascii(gsl::narrow_cast<wchar_t>(vkey)));
return buffer;
}
if (const auto it = vkeyToName.find(vkey); it != vkeyToName.end())
{
buffer.append(it->second);
return buffer;
}
const auto mappedChar = MapVirtualKeyW(vkey, MAPVK_VK_TO_CHAR);
if (mappedChar != 0)
{
buffer.push_back(gsl::narrow_cast<wchar_t>(mappedChar));
return buffer;
}
if (vkey)
{
buffer.append(vkeyPrefix);
buffer.append(std::to_wstring(vkey));
buffer.append(codeSuffix);
return buffer;
}
return {};
Introduce ActionMap to Terminal Settings Model (#9621) This entirely removes `KeyMapping` from the settings model, and builds on the work done in #9543 to consolidate all actions (key bindings and commands) into a unified data structure (`ActionMap`). ## References #9428 - Spec #6900 - Actions page Closes #7441 ## Detailed Description of the Pull Request / Additional comments The important thing here is to remember that we're shifting our philosophy of how to interact/represent actions. Prior to this, the actions arrays in the JSON would be deserialized twice: once for key bindings, and again for commands. By thinking of every entry in the relevant JSON as a `Command`, we can remove a lot of the context switching between working with a key binding vs a command palette item. #9543 allows us to make that shift. Given the work in that PR, we can now deserialize all of the relevant information from each JSON action item. This allows us to simplify `ActionMap::FromJson` to simply iterate over each JSON action item, deserialize it, and add it to our `ActionMap`. Internally, our `ActionMap` operates as discussed in #9428 by maintaining a `_KeyMap` that points to an action ID, and using that action ID to retrieve the `Command` from the `_ActionMap`. Adding actions to the `ActionMap` automatically accounts for name/key-chord collisions. A `NameMap` can be constructed when requested; this is for the Command Palette. Querying the `ActionMap` is fairly straightforward. Helper functions were needed to be able to distinguish an explicit unbinding vs the command not being found in the current layer. Internally, we store explicitly unbound names/key-chords as `ShortcutAction::Invalid` commands. However, we return `nullptr` when a query points to an unbound command. This is done to hide this complexity away from any caller. The command palette still needs special handling for nested and iterable commands. Thankfully, the expansion of iterable commands is performed on an `IMapView`, so we can just expose `NameMap` as a consolidation of `ActionMap`'s `NameMap` with its parents. The same can be said for exposing key chords in nested commands. ## Validation Steps Performed All local tests pass.
2021-05-05 06:50:13 +02:00
}
KeyChord KeyChordSerialization::FromString(const winrt::hstring& hstr)
{
return _fromString(hstr);
}
winrt::hstring KeyChordSerialization::ToString(const KeyChord& chord)
{
return hstring{ _toString(chord) };
}
KeyChord ConversionTrait<KeyChord>::FromJson(const Json::Value& json)
{
try
{
std::string keyChordText;
if (json.isString())
{
// "keys": "ctrl+c"
keyChordText = json.asString();
}
else if (json.isArray() && json.size() == 1 && json[0].isString())
{
// "keys": [ "ctrl+c" ]
keyChordText = json[0].asString();
}
else
{
throw winrt::hresult_invalid_argument{};
}
return _fromString(til::u8u16(keyChordText));
}
catch (...)
{
return nullptr;
}
}
bool ConversionTrait<KeyChord>::CanConvert(const Json::Value& json)
{
return json.isString() || (json.isArray() && json.size() == 1 && json[0].isString());
}
Json::Value ConversionTrait<KeyChord>::ToJson(const KeyChord& val)
{
return til::u16u8(_toString(val));
}
std::string ConversionTrait<KeyChord>::TypeDescription() const
{
return "key chord";
}