From 90627b3ae5c4f0e3be694351adeb795c5e9eef8e Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 11:35:26 -0800 Subject: [PATCH 01/76] add origin tag --- src/cascadia/TerminalSettingsModel/ActionMap.h | 3 +-- .../TerminalSettingsModel/ActionMapSerialization.cpp | 11 ++--------- .../CascadiaSettingsSerialization.cpp | 7 ++++--- src/cascadia/TerminalSettingsModel/Command.cpp | 11 +++++++---- src/cascadia/TerminalSettingsModel/Command.h | 5 ++++- src/cascadia/TerminalSettingsModel/Command.idl | 1 + .../TerminalSettingsModel/GlobalAppSettings.cpp | 12 ++++++------ .../TerminalSettingsModel/GlobalAppSettings.h | 6 +++--- 8 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 937d79f66cb..6fea0d4d7a6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -66,8 +66,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void AddAction(const Model::Command& cmd); // JSON - static com_ptr FromJson(const Json::Value& json); - std::vector LayerJson(const Json::Value& json, const bool withKeybindings = true); + std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson() const; // modification diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 248a0023d00..094847dc740 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -19,13 +19,6 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { - com_ptr ActionMap::FromJson(const Json::Value& json) - { - auto result = make_self(); - result->LayerJson(json); - return result; - } - // Method Description: // - Deserialize an ActionMap from the array `json`. The json array should contain // an array of serialized `Command` objects. @@ -35,7 +28,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - 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, const bool withKeybindings) + std::vector ActionMap::LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { // 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 @@ -50,7 +43,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation continue; } - AddAction(*Command::FromJson(cmdJson, warnings, withKeybindings)); + AddAction(*Command::FromJson(cmdJson, warnings, origin, withKeybindings)); } return warnings; diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index e3796a9b7d6..1b07112a230 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -350,7 +350,8 @@ void SettingsLoader::FinalizeLayering() if (userSettings.globals->EnableColorSelection()) { const auto json = _parseJson(EnableColorSelectionSettingsJson); - const auto globals = GlobalAppSettings::FromJson(json.root); + // todo: figure out the correct origin tag here + const auto globals = GlobalAppSettings::FromJson(json.root, OriginTag::None); userSettings.globals->AddLeastImportantParent(globals); } @@ -616,7 +617,7 @@ void SettingsLoader::_parse(const OriginTag origin, const winrt::hstring& source settings.clear(); { - settings.globals = GlobalAppSettings::FromJson(json.root); + settings.globals = GlobalAppSettings::FromJson(json.root, origin); for (const auto& schemeJson : json.colorSchemes) { @@ -704,7 +705,7 @@ void SettingsLoader::_parseFragment(const winrt::hstring& source, const std::str // Parse out actions from the fragment. Manually opt-out of keybinding // parsing - fragments shouldn't be allowed to bind actions to keys // directly. We may want to revisit circa GH#2205 - settings.globals->LayerActionsFrom(json.root, false); + settings.globals->LayerActionsFrom(json.root, OriginTag::Fragment, false); } { diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 3b2f4075c5d..bc2e1b7b083 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -259,9 +259,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - the newly constructed Command object. winrt::com_ptr Command::FromJson(const Json::Value& json, std::vector& warnings, + const OriginTag origin, const bool parseKeys) { auto result = winrt::make_self(); + result->_Origin = origin; auto nested = false; JsonUtils::GetValueForKey(json, IterateOnKey, result->_IterateOn); @@ -274,7 +276,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); + auto nestedWarnings = Command::LayerJson(result->_subcommands, nestedCommandsJson, origin); // It's possible that the nested commands have some warnings warnings.insert(warnings.end(), nestedWarnings.begin(), nestedWarnings.end()); @@ -362,7 +364,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Return Value: // - A vector containing any warnings detected while parsing std::vector Command::LayerJson(IMap& commands, - const Json::Value& json) + const Json::Value& json, + const OriginTag origin) { std::vector warnings; @@ -372,7 +375,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { try { - const auto result = Command::FromJson(value, warnings); + const auto result = Command::FromJson(value, warnings, origin); if (result->ActionAndArgs().Action() == ShortcutAction::Invalid && !result->HasNestedCommands()) { // If there wasn't a parsed command, then try to get the @@ -583,7 +586,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // warnings, but ultimately, we don't care about warnings during // expansion. std::vector unused; - if (auto newCmd{ Command::FromJson(newJsonValue, unused) }) + if (auto newCmd{ Command::FromJson(newJsonValue, unused, expandable->_Origin) }) { newCommands.push_back(*newCmd); } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index a432fe9a370..5e94ae721c8 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -40,6 +40,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static winrt::com_ptr FromJson(const Json::Value& json, std::vector& warnings, + const OriginTag origin, const bool parseKeys = true); static void ExpandCommands(Windows::Foundation::Collections::IMap& commands, @@ -47,7 +48,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IVectorView schemes); static std::vector LayerJson(Windows::Foundation::Collections::IMap& commands, - const Json::Value& json); + const Json::Value& json, + const OriginTag origin); Json::Value ToJson() const; bool HasNestedCommands() const; @@ -75,6 +77,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs); + WINRT_PROPERTY(OriginTag, Origin); private: Json::Value _originalJson; diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index e92f459e69d..2ed4a7a37d5 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -36,6 +36,7 @@ namespace Microsoft.Terminal.Settings.Model Command(); String Name { get; }; + OriginTag Origin; ActionAndArgs ActionAndArgs { get; }; Microsoft.Terminal.Control.KeyChord Keys { get; }; void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index a720c261b39..7238d38c0cd 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -117,14 +117,14 @@ winrt::Microsoft::Terminal::Settings::Model::ActionMap GlobalAppSettings::Action // - json: an object which should be a serialization of a GlobalAppSettings object. // Return Value: // - a new GlobalAppSettings instance created from the values in `json` -winrt::com_ptr GlobalAppSettings::FromJson(const Json::Value& json) +winrt::com_ptr GlobalAppSettings::FromJson(const Json::Value& json, const OriginTag origin) { auto result = winrt::make_self(); - result->LayerJson(json); + result->LayerJson(json, origin); return result; } -void GlobalAppSettings::LayerJson(const Json::Value& json) +void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origin) { JsonUtils::GetValueForKey(json, DefaultProfileKey, _UnparsedDefaultProfile); // GH#8076 - when adding enum values to this key, we also changed it from @@ -137,19 +137,19 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) MTSM_GLOBAL_SETTINGS(GLOBAL_SETTINGS_LAYER_JSON) #undef GLOBAL_SETTINGS_LAYER_JSON - LayerActionsFrom(json, true); + LayerActionsFrom(json, origin, true); JsonUtils::GetValueForKey(json, LegacyReloadEnvironmentVariablesKey, _legacyReloadEnvironmentVariables); } -void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const bool withKeybindings) +void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey }; for (const auto& jsonKey : bindingsKeys) { if (auto bindings{ json[JsonKey(jsonKey)] }) { - auto warnings = _actionMap->LayerJson(bindings, withKeybindings); + auto warnings = _actionMap->LayerJson(bindings, origin, withKeybindings); // It's possible that the user provided keybindings have some warnings // in them - problems that we should alert the user to, but we can diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 79f40342254..d8100161f5c 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -48,9 +48,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::ActionMap ActionMap() const noexcept; - static com_ptr FromJson(const Json::Value& json); - void LayerJson(const Json::Value& json); - void LayerActionsFrom(const Json::Value& json, const bool withKeybindings = true); + static com_ptr FromJson(const Json::Value& json, const OriginTag origin); + void LayerJson(const Json::Value& json, const OriginTag origin); + void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson() const; From 9dff28f23d069230b5383fc8473d101c64dd17a9 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 13:51:18 -0800 Subject: [PATCH 02/76] update calls in tests --- .../TerminalSettingsModel/Command.idl | 3 +- .../UnitTests_SettingsModel/CommandTests.cpp | 26 +++--- .../KeyBindingsTests.cpp | 80 +++++++++---------- 3 files changed, 54 insertions(+), 55 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index 2ed4a7a37d5..c9cec5012a4 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -31,12 +31,11 @@ namespace Microsoft.Terminal.Settings.Model ShortcutAction Action; }; - [default_interface] runtimeclass Command + [default_interface] runtimeclass Command : ISettingsModelObject { Command(); String Name { get; }; - OriginTag Origin; ActionAndArgs ActionAndArgs { get; }; Microsoft.Terminal.Control.KeyChord Keys { get; }; void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); diff --git a/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp index f68b0224a47..8db1141736f 100644 --- a/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/CommandTests.cpp @@ -48,19 +48,19 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(1u, commands.Size()); { - auto warnings = implementation::Command::LayerJson(commands, commands1Json); + auto warnings = implementation::Command::LayerJson(commands, commands1Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(2u, commands.Size()); { - auto warnings = implementation::Command::LayerJson(commands, commands2Json); + auto warnings = implementation::Command::LayerJson(commands, commands2Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); } VERIFY_ARE_EQUAL(4u, commands.Size()); @@ -82,7 +82,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); @@ -93,7 +93,7 @@ namespace SettingsModelUnitTests VERIFY_IS_NOT_NULL(realArgs); } { - auto warnings = implementation::Command::LayerJson(commands, commands1Json); + auto warnings = implementation::Command::LayerJson(commands, commands1Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); @@ -103,7 +103,7 @@ namespace SettingsModelUnitTests VERIFY_IS_NULL(command.ActionAndArgs().Args()); } { - auto warnings = implementation::Command::LayerJson(commands, commands2Json); + auto warnings = implementation::Command::LayerJson(commands, commands2Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); auto command = commands.Lookup(L"action0"); @@ -115,7 +115,7 @@ namespace SettingsModelUnitTests } { // This last command should "unbind" the action. - auto warnings = implementation::Command::LayerJson(commands, commands3Json); + auto warnings = implementation::Command::LayerJson(commands, commands3Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(0u, commands.Size()); } @@ -143,7 +143,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(9u, commands.Size()); @@ -261,7 +261,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(3u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); @@ -288,7 +288,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); { - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); @@ -329,7 +329,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); // There are only 5 commands here: all of the `"none"`, `"auto"`, @@ -399,7 +399,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(1u, commands.Size()); @@ -462,7 +462,7 @@ namespace SettingsModelUnitTests auto commands = winrt::single_threaded_map(); VERIFY_ARE_EQUAL(0u, commands.Size()); - auto warnings = implementation::Command::LayerJson(commands, commands0Json); + auto warnings = implementation::Command::LayerJson(commands, commands0Json, OriginTag::None); VERIFY_ARE_EQUAL(0u, warnings.size()); VERIFY_ARE_EQUAL(9u, commands.Size()); diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index 8eed7276f1b..c5f4be49045 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -120,13 +120,13 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); } @@ -143,21 +143,21 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); } void KeyBindingsTests::HashDeduplication() { const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])")); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])")); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); } @@ -180,51 +180,51 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); 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); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); Log::Comment(NoThrowString().Format( L"Try unbinding a key using `null` to unbind the key")); // First add back a good binding - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting - actionMap->LayerJson(bindings3Json); + actionMap->LayerJson(bindings3Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); 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); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting - actionMap->LayerJson(bindings4Json); + actionMap->LayerJson(bindings4Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); 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); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); // Then try layering in the bad setting - actionMap->LayerJson(bindings5Json); + actionMap->LayerJson(bindings5Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); Log::Comment(NoThrowString().Format( L"Try unbinding a key that wasn't bound at all")); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); VERIFY_IS_NULL(actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Control, static_cast('C'), 0 })); } @@ -244,13 +244,13 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); } @@ -277,7 +277,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(10u, actionMap->_KeyMap.size()); { @@ -405,7 +405,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); { @@ -454,7 +454,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); { @@ -495,7 +495,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); { @@ -522,7 +522,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(6u, actionMap->_KeyMap.size()); { @@ -580,7 +580,7 @@ namespace SettingsModelUnitTests const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); - VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson, OriginTag::None);, std::exception); } } @@ -595,7 +595,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); { @@ -617,7 +617,7 @@ namespace SettingsModelUnitTests { const std::string bindingsInvalidString{ R"([{ "keys": ["up"], "command": "moveTab" }])" }; auto actionMapNoArgs = winrt::make_self(); - actionMapNoArgs->LayerJson(bindingsInvalidString); + actionMapNoArgs->LayerJson(bindingsInvalidString, OriginTag::None); VERIFY_ARE_EQUAL(0u, actionMapNoArgs->_KeyMap.size()); } { @@ -625,7 +625,7 @@ namespace SettingsModelUnitTests const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); - VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson, OriginTag::None);, std::exception); } } @@ -641,7 +641,7 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); { @@ -673,7 +673,7 @@ namespace SettingsModelUnitTests const auto bindingsInvalidJson = VerifyParseSucceeded(bindingsInvalidString); auto invalidActionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, invalidActionMap->_KeyMap.size()); - VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson);, std::exception); + VERIFY_THROWS(invalidActionMap->LayerJson(bindingsInvalidJson, OriginTag::None);, std::exception); } } @@ -707,14 +707,14 @@ namespace SettingsModelUnitTests { Log::Comment(L"simple command: no args"); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) }; VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast('A'), 0 }, kbd); } { Log::Comment(L"command with args"); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); auto args{ winrt::make_self() }; @@ -725,7 +725,7 @@ namespace SettingsModelUnitTests } { Log::Comment(L"command with new terminal args"); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(3u, actionMap->_KeyMap.size()); auto newTerminalArgs{ winrt::make_self() }; @@ -737,7 +737,7 @@ namespace SettingsModelUnitTests } { Log::Comment(L"command with hidden args"); - actionMap->LayerJson(bindings3Json); + actionMap->LayerJson(bindings3Json, OriginTag::None); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; @@ -762,13 +762,13 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_ARE_EQUAL(0u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings0Json); + actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - actionMap->LayerJson(bindings1Json); + actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size(), L"Layering the second action should replace the first one."); - actionMap->LayerJson(bindings2Json); + actionMap->LayerJson(bindings2Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); } @@ -777,7 +777,7 @@ namespace SettingsModelUnitTests const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!"); const auto actionMap = winrt::make_self(); - actionMap->LayerJson(json); + actionMap->LayerJson(json, OriginTag::None); const auto action = actionMap->GetActionByKeyChord({ VirtualKeyModifiers::Shift, 0, 255 }); VERIFY_IS_NOT_NULL(action); From 8bcbd0bd423e7c4c45d03a1b21ba34fa3452a3b5 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 14:20:14 -0800 Subject: [PATCH 03/76] fix tests --- src/cascadia/TerminalSettingsModel/GlobalAppSettings.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index d8100161f5c..aa7d8c0147f 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -48,7 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::ActionMap ActionMap() const noexcept; - static com_ptr FromJson(const Json::Value& json, const OriginTag origin); + static com_ptr FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None); void LayerJson(const Json::Value& json, const OriginTag origin); void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); From 052d06368671ce1feaaf2e61dd1d7e3826dbe680 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 14:36:45 -0800 Subject: [PATCH 04/76] ah one of the tests uses this --- src/cascadia/TerminalSettingsModel/ActionMap.h | 1 + .../TerminalSettingsModel/ActionMapSerialization.cpp | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 6fea0d4d7a6..de9b9ca2361 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -66,6 +66,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void AddAction(const Model::Command& cmd); // JSON + static com_ptr FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None); std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson() const; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 094847dc740..c03fecd82f5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -19,6 +19,13 @@ using namespace winrt::Microsoft::Terminal::Settings::Model; namespace winrt::Microsoft::Terminal::Settings::Model::implementation { + com_ptr ActionMap::FromJson(const Json::Value& json, const OriginTag origin) + { + auto result = make_self(); + result->LayerJson(json, origin); + return result; + } + // Method Description: // - Deserialize an ActionMap from the array `json`. The json array should contain // an array of serialized `Command` objects. From 8cc82de489f550748247632113576c438fdd94b8 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 15:42:38 -0800 Subject: [PATCH 05/76] generated --- .../TerminalSettingsModel/CascadiaSettingsSerialization.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 1b07112a230..494499ae336 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -350,8 +350,7 @@ void SettingsLoader::FinalizeLayering() if (userSettings.globals->EnableColorSelection()) { const auto json = _parseJson(EnableColorSelectionSettingsJson); - // todo: figure out the correct origin tag here - const auto globals = GlobalAppSettings::FromJson(json.root, OriginTag::None); + const auto globals = GlobalAppSettings::FromJson(json.root, OriginTag::Generated); userSettings.globals->AddLeastImportantParent(globals); } From 642d0ab2b7ae79cae049f6efce91785937f33a60 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 5 Mar 2024 15:47:49 -0800 Subject: [PATCH 06/76] inbox makes more sense --- .../TerminalSettingsModel/CascadiaSettingsSerialization.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 494499ae336..0a9cc49cbfd 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -350,7 +350,7 @@ void SettingsLoader::FinalizeLayering() if (userSettings.globals->EnableColorSelection()) { const auto json = _parseJson(EnableColorSelectionSettingsJson); - const auto globals = GlobalAppSettings::FromJson(json.root, OriginTag::Generated); + const auto globals = GlobalAppSettings::FromJson(json.root, OriginTag::InBox); userSettings.globals->AddLeastImportantParent(globals); } From 66fe08f964e11b283344914e75a34c1d6df76d91 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 6 Mar 2024 17:24:39 -0800 Subject: [PATCH 07/76] default ids --- src/cascadia/TerminalSettingsModel/Command.h | 1 + .../TerminalSettingsModel/Command.idl | 1 + .../TerminalSettingsModel/defaults.json | 246 +++++++++--------- 3 files changed, 125 insertions(+), 123 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 5e94ae721c8..c2c881b9b36 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -78,6 +78,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs); WINRT_PROPERTY(OriginTag, Origin); + WINRT_PROPERTY(hstring, ID); private: Json::Value _originalJson; diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index c9cec5012a4..0a808d3de62 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -36,6 +36,7 @@ namespace Microsoft.Terminal.Settings.Model Command(); String Name { get; }; + String ID; ActionAndArgs ActionAndArgs { get; }; Microsoft.Terminal.Control.KeyChord Keys { get; }; void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 360f37afaad..7a9c4aad79b 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -354,143 +354,143 @@ "actions": [ // Application-level Keys - { "command": "closeWindow", "keys": "alt+f4" }, - { "command": "toggleFullscreen", "keys": "alt+enter" }, - { "command": "toggleFullscreen", "keys": "f11" }, - { "command": "toggleFocusMode" }, - { "command": "toggleAlwaysOnTop" }, - { "command": "openNewTabDropdown", "keys": "ctrl+shift+space" }, - { "command": { "action": "openSettings", "target": "settingsUI" }, "keys": "ctrl+," }, - { "command": { "action": "openSettings", "target": "settingsFile" }, "keys": "ctrl+shift+," }, - { "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+," }, - { "command": "find", "keys": "ctrl+shift+f" }, - { "command": { "action": "findMatch", "direction": "next" } }, - { "command": { "action": "findMatch", "direction": "prev" } }, - { "command": "toggleShaderEffects" }, - { "command": "openTabColorPicker" }, - { "command": "renameTab" }, - { "command": "openTabRenamer" }, - { "command": "commandPalette", "keys":"ctrl+shift+p" }, - { "command": "identifyWindow" }, - { "command": "openWindowRenamer" }, - { "command": "quakeMode", "keys":"win+sc(41)" }, - { "command": "openSystemMenu", "keys": "alt+space" }, - { "command": "quit" }, - { "command": "restoreLastClosed" }, - { "command": "openAbout" }, + { "command": "closeWindow", "keys": "alt+f4", "id": "Terminal.CloseWindow" }, + { "command": "toggleFullscreen", "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" }, + { "command": "toggleFullscreen", "keys": "f11", "id": "Terminal.ToggleFullscreen" }, + { "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" }, + { "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" }, + { "command": "openNewTabDropdown", "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" }, + { "command": { "action": "openSettings", "target": "settingsUI" }, "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, + { "command": { "action": "openSettings", "target": "settingsFile" }, "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, + { "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, + { "command": "find", "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, + { "command": { "action": "findMatch", "direction": "next" }, "id": "Terminal.FindNextMatch" }, + { "command": { "action": "findMatch", "direction": "prev" }, "id": "Terminal.FindPrevMatch" }, + { "command": "toggleShaderEffects", "id": "Terminal.ToggleShaderEffects" }, + { "command": "openTabColorPicker", "id": "Terminal.OpenTabColorPicker" }, + { "command": "renameTab", "id": "Terminal.RenameTab" }, + { "command": "openTabRenamer", "id": "Terminal.OpenTabRenamer" }, + { "command": "commandPalette", "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, + { "command": "identifyWindow", "id": "Terminal.IdentifyWindow" }, + { "command": "openWindowRenamer", "id": "Terminal.OpenWindowRenamer" }, + { "command": "quakeMode", "keys":"win+sc(41)", "id": "Terminal.QuakeMode" }, + { "command": "openSystemMenu", "keys": "alt+space", "id": "Terminal.OpenSystemMenu" }, + { "command": "quit", "id": "Terminal.Quit" }, + { "command": "restoreLastClosed", "id": "Terminal.RestoreLastClosed" }, + { "command": "openAbout", "id": "Terminal.OpenAboutDialog" }, // Tab Management // "command": "closeTab" is unbound by default. // The closeTab command closes a tab without confirmation, even if it has multiple panes. - { "command": "closeOtherTabs" }, - { "command": "closeTabsAfter" }, - { "command": { "action" : "moveTab", "direction": "forward" }}, - { "command": { "action" : "moveTab", "direction": "backward" }}, - { "command": "newTab", "keys": "ctrl+shift+t" }, - { "command": "newWindow", "keys": "ctrl+shift+n" }, - { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1" }, - { "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2" }, - { "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3" }, - { "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4" }, - { "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5" }, - { "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6" }, - { "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7" }, - { "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8" }, - { "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9" }, - { "command": "duplicateTab", "keys": "ctrl+shift+d" }, - { "command": "nextTab", "keys": "ctrl+tab" }, - { "command": "prevTab", "keys": "ctrl+shift+tab" }, - { "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1" }, - { "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2" }, - { "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3" }, - { "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4" }, - { "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5" }, - { "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6" }, - { "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7" }, - { "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8" }, - { "command": { "action": "switchToTab", "index": 4294967295 }, "keys": "ctrl+alt+9" }, - { "command": { "action": "moveTab", "window": "new" }, }, + { "command": "closeOtherTabs", "id": "Terminal.CloseOtherTabs" }, + { "command": "closeTabsAfter", "id": "Terminal.CloseTabsAfter" }, + { "command": { "action" : "moveTab", "direction": "forward" }, "id": "Terminal.MoveTabForward" }, + { "command": { "action" : "moveTab", "direction": "backward" }, "id": "Terminal.MoveTabBackward" }, + { "command": "newTab", "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" }, + { "command": "newWindow", "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" }, + { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" }, + { "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" }, + { "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" }, + { "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" }, + { "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" }, + { "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" }, + { "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" }, + { "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" }, + { "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" }, + { "command": "duplicateTab", "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" }, + { "command": "nextTab", "keys": "ctrl+tab", "id": "Terminal.NextTab" }, + { "command": "prevTab", "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" }, + { "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" }, + { "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" }, + { "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" }, + { "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" }, + { "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" }, + { "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" }, + { "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" }, + { "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" }, + { "command": { "action": "switchToTab", "index": 4294967295 }, "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" }, + { "command": { "action": "moveTab", "window": "new" }, "id": "Terminal.MoveTabToNewWindow" }, // Pane Management - { "command": "closeOtherPanes" }, - { "command": "closePane", "keys": "ctrl+shift+w" }, - { "command": { "action": "splitPane", "split": "up" } }, - { "command": { "action": "splitPane", "split": "down" }, "keys": "alt+shift+-" }, - { "command": { "action": "splitPane", "split": "left" } }, - { "command": { "action": "splitPane", "split": "right" }, "keys": "alt+shift+plus" }, - { "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down" }, - { "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left" }, - { "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right" }, - { "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up" }, - { "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down" }, - { "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left" }, - { "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right" }, - { "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up" }, - { "command": { "action": "moveFocus", "direction": "previous" }, "keys": "ctrl+alt+left"}, - { "command": { "action": "moveFocus", "direction": "previousInOrder" } }, - { "command": { "action": "moveFocus", "direction": "nextInOrder" } }, - { "command": { "action": "moveFocus", "direction": "first" } }, - { "command": { "action": "moveFocus", "direction": "parent" } }, - { "command": { "action": "moveFocus", "direction": "child" } }, - { "command": { "action": "swapPane", "direction": "down" } }, - { "command": { "action": "swapPane", "direction": "left" } }, - { "command": { "action": "swapPane", "direction": "right" } }, - { "command": { "action": "swapPane", "direction": "up" } }, - { "command": { "action": "swapPane", "direction": "previous"} }, - { "command": { "action": "swapPane", "direction": "previousInOrder"} }, - { "command": { "action": "swapPane", "direction": "nextInOrder"} }, - { "command": { "action": "swapPane", "direction": "first" } }, - { "command": "toggleBroadcastInput" }, - { "command": "togglePaneZoom" }, - { "command": "toggleSplitOrientation" }, - { "command": "toggleReadOnlyMode" }, - { "command": "enableReadOnlyMode" }, - { "command": "disableReadOnlyMode" }, - { "command": { "action": "movePane", "index": 0 } }, - { "command": { "action": "movePane", "index": 1 } }, - { "command": { "action": "movePane", "index": 2 } }, - { "command": { "action": "movePane", "index": 3 } }, - { "command": { "action": "movePane", "index": 4 } }, - { "command": { "action": "movePane", "index": 5 } }, - { "command": { "action": "movePane", "index": 6 } }, - { "command": { "action": "movePane", "index": 7 } }, - { "command": { "action": "movePane", "index": 8 } }, - { "command": { "action": "movePane", "window": "new" }, }, - { "command": "restartConnection" }, + { "command": "closeOtherPanes", "id": "Terminal.CloseOtherPanes" }, + { "command": "closePane", "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, + { "command": { "action": "splitPane", "split": "up" }, "id": "Terminal.SplitPaneUp" }, + { "command": { "action": "splitPane", "split": "down" }, "keys": "alt+shift+-", "id": "Terminal.SplitPaneDown" }, + { "command": { "action": "splitPane", "split": "left" }, "id": "Terminal.SplitPaneLeft" }, + { "command": { "action": "splitPane", "split": "right" }, "keys": "alt+shift+plus", "id": "Terminal.SplitPaneRight" }, + { "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, + { "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, + { "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, + { "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" }, + { "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down", "id": "Terminal.MoveFocusDown" }, + { "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left", "id": "Terminal.MoveFocusLeft" }, + { "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right", "id": "Terminal.MoveFocusRight" }, + { "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up", "id": "Terminal.MoveFocusUp" }, + { "command": { "action": "moveFocus", "direction": "previous" }, "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" }, + { "command": { "action": "moveFocus", "direction": "previousInOrder" }, "id": "Terminal.MoveFocusPreviousInOrder" }, + { "command": { "action": "moveFocus", "direction": "nextInOrder" }, "id": "Terminal.MoveFocusNextInOrder" }, + { "command": { "action": "moveFocus", "direction": "first" }, "id": "Terminal.MoveFocusFirst" }, + { "command": { "action": "moveFocus", "direction": "parent" }, "id": "Terminal.MoveFocusParent" }, + { "command": { "action": "moveFocus", "direction": "child" }, "id": "Terminal.MoveFocusChild" }, + { "command": { "action": "swapPane", "direction": "down" }, "id": "Terminal.SwapPaneDown" }, + { "command": { "action": "swapPane", "direction": "left" }, "id": "Terminal.SwapPaneLeft" }, + { "command": { "action": "swapPane", "direction": "right" }, "id": "Terminal.SwapPaneRight" }, + { "command": { "action": "swapPane", "direction": "up" }, "id": "Terminal.SwapPaneUp" }, + { "command": { "action": "swapPane", "direction": "previous"}, "id": "Terminal.SwapPanePrevious" }, + { "command": { "action": "swapPane", "direction": "previousInOrder"}, "id": "Terminal.SwapPanePreviousInOrder" }, + { "command": { "action": "swapPane", "direction": "nextInOrder"}, "id": "Terminal.SwapPaneNextInOrder" }, + { "command": { "action": "swapPane", "direction": "first" }, "id": "Terminal.SwapPaneFirst" }, + { "command": "toggleBroadcastInput", "id": "Terminal.ToggleBroadcastInput" }, + { "command": "togglePaneZoom", "id": "Terminal.TogglePaneZoom" }, + { "command": "toggleSplitOrientation", "id": "Terminal.ToggleSplitOrientation" }, + { "command": "toggleReadOnlyMode", "id": "Terminal.ToggleReadOnlyMode" }, + { "command": "enableReadOnlyMode", "id": "Terminal.EnableReadOnlyMode" }, + { "command": "disableReadOnlyMode", "id": "Terminal.DisableReadOnlyMode" }, + { "command": { "action": "movePane", "index": 0 }, "id": "Terminal.MovePaneToTab0" }, + { "command": { "action": "movePane", "index": 1 }, "id": "Terminal.MovePaneToTab1" }, + { "command": { "action": "movePane", "index": 2 }, "id": "Terminal.MovePaneToTab2" }, + { "command": { "action": "movePane", "index": 3 }, "id": "Terminal.MovePaneToTab3" }, + { "command": { "action": "movePane", "index": 4 }, "id": "Terminal.MovePaneToTab4" }, + { "command": { "action": "movePane", "index": 5 }, "id": "Terminal.MovePaneToTab5" }, + { "command": { "action": "movePane", "index": 6 }, "id": "Terminal.MovePaneToTab6" }, + { "command": { "action": "movePane", "index": 7 }, "id": "Terminal.MovePaneToTab7" }, + { "command": { "action": "movePane", "index": 8 }, "id": "Terminal.MovePaneToTab8" }, + { "command": { "action": "movePane", "window": "new" }, "id": "Terminal.MovePaneToNewWindow" }, + { "command": "restartConnection", "id": "Terminal.RestartConnection" }, // Clipboard Integration - { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" }, - { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert" }, - { "command": { "action": "copy", "singleLine": false }, "keys": "enter" }, - { "command": "paste", "keys": "ctrl+shift+v" }, - { "command": "paste", "keys": "shift+insert" }, - { "command": "selectAll", "keys": "ctrl+shift+a" }, - { "command": "markMode", "keys": "ctrl+shift+m" }, - { "command": "toggleBlockSelection" }, - { "command": "switchSelectionEndpoint" }, - { "command": "expandSelectionToWord" }, - { "command": "showContextMenu", "keys": "menu" }, + { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" }, + { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" }, + { "command": { "action": "copy", "singleLine": false }, "keys": "enter", "id": "Terminal.CopySelectedText" }, + { "command": "paste", "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" }, + { "command": "paste", "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" }, + { "command": "selectAll", "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" }, + { "command": "markMode", "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" }, + { "command": "toggleBlockSelection", "id": "Terminal.ToggleBlockSelection" }, + { "command": "switchSelectionEndpoint", "id": "Terminal.SwitchSelectionEndpoint" }, + { "command": "expandSelectionToWord", "id": "Terminal.ExpandSelectionToWord" }, + { "command": "showContextMenu", "keys": "menu", "id": "Terminal.ShowContextMenu" }, // Web Search - { "command": { "action": "searchWeb" }, "name": { "key": "SearchWebCommandKey" } }, + { "command": { "action": "searchWeb" }, "name": { "key": "SearchWebCommandKey" }, "id": "Terminal.SearchWeb" }, // Scrollback - { "command": "scrollDown", "keys": "ctrl+shift+down" }, - { "command": "scrollDownPage", "keys": "ctrl+shift+pgdn" }, - { "command": "scrollUp", "keys": "ctrl+shift+up" }, - { "command": "scrollUpPage", "keys": "ctrl+shift+pgup" }, - { "command": "scrollToTop", "keys": "ctrl+shift+home" }, - { "command": "scrollToBottom", "keys": "ctrl+shift+end" }, - { "command": { "action": "clearBuffer", "clear": "all" } }, - { "command": "exportBuffer" }, + { "command": "scrollDown", "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" }, + { "command": "scrollDownPage", "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" }, + { "command": "scrollUp", "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" }, + { "command": "scrollUpPage", "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, + { "command": "scrollToTop", "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, + { "command": "scrollToBottom", "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, + { "command": { "action": "clearBuffer", "clear": "all" }, "id": "Terminal.ClearBuffer" }, + { "command": "exportBuffer", "id": "Terminal.ExportBuffer" }, // Visual Adjustments - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus" }, - { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+minus" }, - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+numpad_plus" }, - { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+numpad_minus" }, - { "command": "resetFontSize", "keys": "ctrl+0" }, - { "command": "resetFontSize", "keys": "ctrl+numpad_0" }, + { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" }, + { "command": "resetFontSize", "keys": "ctrl+0", "id": "Terminal.ResetFontSize" }, + { "command": "resetFontSize", "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" }, // Other commands { From db528c94fc1eee3668b5bb37966618cfc4a097b7 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 26 Mar 2024 11:29:12 -0700 Subject: [PATCH 08/76] generate IDs for user commands --- .../TerminalSettingsModel/Command.cpp | 69 ++++++++++++++++++- src/cascadia/TerminalSettingsModel/Command.h | 6 +- .../TerminalSettingsModel/Command.idl | 2 +- .../Resources/en-US/Resources.resw | 3 + 4 files changed, 76 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 516d767481b..ea34348cf85 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -9,6 +9,7 @@ #include "KeyChordSerialization.h" #include #include "TerminalSettingsSerializationHelpers.h" +#include "CascadiaSettings.h" using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Windows::Foundation::Collections; @@ -21,6 +22,7 @@ namespace winrt } static constexpr std::string_view NameKey{ "name" }; +static constexpr std::string_view IDKey{ "id" }; static constexpr std::string_view IconKey{ "icon" }; static constexpr std::string_view ActionKey{ "command" }; static constexpr std::string_view ArgsKey{ "args" }; @@ -40,7 +42,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { auto command{ winrt::make_self() }; command->_name = _name; - command->_Origin = OriginTag::User; + command->_Origin = _Origin; + command->_ID = _ID; command->_ActionAndArgs = *get_self(_ActionAndArgs)->Copy(); command->_keyMappings = _keyMappings; command->_iconPath = _iconPath; @@ -57,6 +60,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation command->_subcommands.Insert(kv.Key(), *subCmd->Copy()); } } + return command; } @@ -115,6 +119,44 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + hstring Command::ID() const noexcept + { + return hstring{ _ID }; + } + + // Function Description: + // - generate an ID for this command and populate the _ID field + // - this function _will_ overwrite an existing ID if there is one, it is + // on the caller to make sure that either there was no ID or the overwrite is okay + // - this function should only be called to generate IDs for user-created commands + void Command::_generateID() + { + if (_ActionAndArgs) + { + // lambda function to remove whitespace and capitalize each letter after a removed space + auto removeWhitespaceAndCapitalize = [](wchar_t& x, bool& capitalizeNext) { + if (std::iswspace(x)) + { + capitalizeNext = true; // Capitalize the next character + return true; // Remove the whitespace + } + else if (capitalizeNext) + { + x = std::towupper(x); // Capitalize the letter + capitalizeNext = false; // Reset flag + } + return false; // Keep the character + }; + + std::wstring noWhitespaceName{ get_self(_ActionAndArgs)->GenerateName() }; + bool capitalizeNext; + noWhitespaceName.erase(std::remove_if(noWhitespaceName.begin(), noWhitespaceName.end(), [&capitalizeNext, removeWhitespaceAndCapitalize](wchar_t& x) { + return removeWhitespaceAndCapitalize(x, capitalizeNext); + }), noWhitespaceName.end()); + _ID = RS_(L"OriginTagUser") + L"." + noWhitespaceName; + } + } + void Command::Name(const hstring& value) { if (!_name.has_value() || _name.value() != value) @@ -307,6 +349,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto actionJson{ json[JsonKey(ActionKey)] }) { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); + + // we might need to generate an ID, check these: + // 1. the action is valid + // 2. there isn't already an ID + // 3. the origin is User + if (result->_ActionAndArgs.Action() != ShortcutAction::Invalid) + { + if (const auto id{ json[JsonKey("id")] }) + { + result->_ID = JsonUtils::GetValue(id); + } + else if (origin == OriginTag::User) + { + result->_generateID(); + } + } } else { @@ -424,6 +482,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value cmdJson{ Json::ValueType::objectValue }; JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); JsonUtils::SetValueForKey(cmdJson, NameKey, _name); + if (!_ID.empty()) + { + JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); + } if (_ActionAndArgs) { @@ -444,6 +506,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // First iteration also writes icon and name JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); JsonUtils::SetValueForKey(cmdJson, NameKey, _name); + if (!_ID.empty()) + { + JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); + } } if (_ActionAndArgs) @@ -455,7 +521,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation cmdList.append(cmdJson); } } - return cmdList; } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index c2c881b9b36..4a1969ab80e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -61,6 +61,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring Name() const noexcept; void Name(const hstring& name); + hstring ID() const noexcept; + Control::KeyChord Keys() const noexcept; hstring KeyChordText() const noexcept; std::vector KeyMappings() const noexcept; @@ -78,16 +80,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None); WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs); WINRT_PROPERTY(OriginTag, Origin); - WINRT_PROPERTY(hstring, ID); private: Json::Value _originalJson; Windows::Foundation::Collections::IMap _subcommands{ nullptr }; std::vector _keyMappings; std::optional _name; + std::wstring _ID; std::optional _iconPath; bool _nestedCommand{ false }; + void _generateID(); + static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, Windows::Foundation::Collections::IVectorView schemes); diff --git a/src/cascadia/TerminalSettingsModel/Command.idl b/src/cascadia/TerminalSettingsModel/Command.idl index 0a808d3de62..aa23458f55d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.idl +++ b/src/cascadia/TerminalSettingsModel/Command.idl @@ -36,7 +36,7 @@ namespace Microsoft.Terminal.Settings.Model Command(); String Name { get; }; - String ID; + String ID { get; }; ActionAndArgs ActionAndArgs { get; }; Microsoft.Terminal.Control.KeyChord Keys { get; }; void RegisterKey(Microsoft.Terminal.Control.KeyChord keys); diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index d6d6d9565f9..8528372249d 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -192,6 +192,9 @@ Open system menu + + User + Command Prompt This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt" From b43191d2c52773c63d9d24eeab09b6dc7bc57213 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 26 Mar 2024 11:44:47 -0700 Subject: [PATCH 09/76] spacing --- src/cascadia/TerminalSettingsModel/Command.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 9758b6e8ede..efbb8fb7ae5 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -150,7 +150,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool capitalizeNext; noWhitespaceName.erase(std::remove_if(noWhitespaceName.begin(), noWhitespaceName.end(), [&capitalizeNext, removeWhitespaceAndCapitalize](wchar_t& x) { return removeWhitespaceAndCapitalize(x, capitalizeNext); - }), noWhitespaceName.end()); + }), + noWhitespaceName.end()); _ID = RS_(L"OriginTagUser") + L"." + noWhitespaceName; } } From 2093660ac126e0babb749dfa1809a65541900268 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 26 Mar 2024 11:45:44 -0700 Subject: [PATCH 10/76] line --- src/cascadia/TerminalSettingsModel/Command.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index efbb8fb7ae5..9e657833ac7 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -58,7 +58,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation command->_subcommands.Insert(kv.Key(), *subCmd->Copy()); } } - return command; } From 6c3253968f8ee9ac46f0256e4d2c14c06731bb51 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 15:37:19 -0700 Subject: [PATCH 11/76] string of numbers is unsightly but it works --- .../TerminalSettingsModel/ActionAndArgs.cpp | 23 ++++++++++++ .../TerminalSettingsModel/ActionAndArgs.h | 1 + .../TerminalSettingsModel/Command.cpp | 36 +------------------ src/cascadia/TerminalSettingsModel/Command.h | 2 -- 4 files changed, 25 insertions(+), 37 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index e90a2b8dcfa..bf567689ac6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -448,6 +448,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return found != GeneratedActionNames.end() ? found->second : L""; } + // Function Description: + // - This will generate an ID for this ActionAndArgs, based on the ShortcutAction and the Args + // - It will always create the same ID if the ShortcutAction and the Args are the same + // - Note: this should only be called for User-created actions + // - Example: The "SendInput 'abc'" action will have the generated ID "User.sendInput." + // Return Value: + // - The ID, based on the ShortcutAction and the Args + winrt::hstring ActionAndArgs::GenerateID() const + { + if (_Action != ShortcutAction::Invalid) + { + auto actionKeyString = ActionToStringMap.find(_Action)->second; + auto result = RS_(L"OriginTagUser") + L"." + std::wstring{ actionKeyString.begin(), actionKeyString.end() }; + if (_Args) + { + // If there are args, append the hash of the args + result = result + L"." + std::to_wstring(_Args.Hash()); + } + return result; + } + return L""; + } + winrt::hstring ActionAndArgs::Serialize(const winrt::Windows::Foundation::Collections::IVector& args) { Json::Value json{ Json::objectValue }; diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h index a2afefaa493..9c841ca727c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.h @@ -27,6 +27,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation com_ptr Copy() const; hstring GenerateName() const; + hstring GenerateID() const; WINRT_PROPERTY(ShortcutAction, Action, ShortcutAction::Invalid); WINRT_PROPERTY(IActionArgs, Args, nullptr); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 9e657833ac7..f76ab010ecd 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -121,40 +121,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hstring{ _ID }; } - // Function Description: - // - generate an ID for this command and populate the _ID field - // - this function _will_ overwrite an existing ID if there is one, it is - // on the caller to make sure that either there was no ID or the overwrite is okay - // - this function should only be called to generate IDs for user-created commands - void Command::_generateID() - { - if (_ActionAndArgs) - { - // lambda function to remove whitespace and capitalize each letter after a removed space - auto removeWhitespaceAndCapitalize = [](wchar_t& x, bool& capitalizeNext) { - if (std::iswspace(x)) - { - capitalizeNext = true; // Capitalize the next character - return true; // Remove the whitespace - } - else if (capitalizeNext) - { - x = std::towupper(x); // Capitalize the letter - capitalizeNext = false; // Reset flag - } - return false; // Keep the character - }; - - std::wstring noWhitespaceName{ get_self(_ActionAndArgs)->GenerateName() }; - bool capitalizeNext; - noWhitespaceName.erase(std::remove_if(noWhitespaceName.begin(), noWhitespaceName.end(), [&capitalizeNext, removeWhitespaceAndCapitalize](wchar_t& x) { - return removeWhitespaceAndCapitalize(x, capitalizeNext); - }), - noWhitespaceName.end()); - _ID = RS_(L"OriginTagUser") + L"." + noWhitespaceName; - } - } - void Command::Name(const hstring& value) { if (!_name.has_value() || _name.value() != value) @@ -360,7 +326,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else if (origin == OriginTag::User) { - result->_generateID(); + result->_ID = get_self(result->_ActionAndArgs)->GenerateID(); } } } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 4a1969ab80e..26668070a66 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -90,8 +90,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional _iconPath; bool _nestedCommand{ false }; - void _generateID(); - static std::vector _expandCommand(Command* const expandable, Windows::Foundation::Collections::IVectorView profiles, Windows::Foundation::Collections::IVectorView schemes); From eccd87f30354c702c14799b3fc3d828324450c99 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 15:38:50 -0700 Subject: [PATCH 12/76] update comment --- src/cascadia/TerminalSettingsModel/Command.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index f76ab010ecd..66eec46cdbf 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -314,7 +314,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); - // we might need to generate an ID, check these: + // we need to generate an ID if all these are true: // 1. the action is valid // 2. there isn't already an ID // 3. the origin is User @@ -485,6 +485,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation cmdList.append(cmdJson); } } + return cmdList; } From 44510dce1b6ae6ec21ce16d39ffa2c79a7b3c0a5 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 17:14:21 -0700 Subject: [PATCH 13/76] move id generation to fixupusersettings --- .../TerminalSettingsModel/ActionMap.cpp | 5 +++++ .../TerminalSettingsModel/ActionMap.h | 1 + .../CascadiaSettingsSerialization.cpp | 16 ++++++++++++++ .../TerminalSettingsModel/Command.cpp | 21 +++++-------------- src/cascadia/TerminalSettingsModel/Command.h | 1 + 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 76565dff444..d5d29fe576d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -795,6 +795,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } + std::unordered_map ActionMap::AllActions() + { + return _ActionMap; + } + // Method Description: // - Rebinds a key binding to a new key chord // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index de9b9ca2361..80fb121dc7d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -71,6 +71,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ToJson() const; // modification + std::unordered_map AllActions(); bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); void DeleteKeyBinding(const Control::KeyChord& keys); void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 244c200dad8..8bd4aacb26b 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -504,6 +504,22 @@ bool SettingsLoader::FixupUserSettings() fixedUp = true; } + // we need to generate an ID for a command in the user settings if it doesn't already have one + auto actionMap{ winrt::get_self(userSettings.globals->ActionMap()) }; + for (auto actionPair : actionMap->AllActions()) + { + auto cmdImpl{ winrt::get_self(actionPair.second) }; + if (cmdImpl->ID().empty()) + { + auto actionAndArgsImpl{ winrt::get_self(cmdImpl->ActionAndArgs()) }; + if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) + { + cmdImpl->ID(generatedID); + fixedUp = true; + } + } + } + return fixedUp; } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 66eec46cdbf..b35d08218ef 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -121,6 +121,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hstring{ _ID }; } + void Command::ID(hstring id) + { + _ID = id; + } + void Command::Name(const hstring& value) { if (!_name.has_value() || _name.value() != value) @@ -313,22 +318,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto actionJson{ json[JsonKey(ActionKey)] }) { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); - - // we need to generate an ID if all these are true: - // 1. the action is valid - // 2. there isn't already an ID - // 3. the origin is User - if (result->_ActionAndArgs.Action() != ShortcutAction::Invalid) - { - if (const auto id{ json[JsonKey("id")] }) - { - result->_ID = JsonUtils::GetValue(id); - } - else if (origin == OriginTag::User) - { - result->_ID = get_self(result->_ActionAndArgs)->GenerateID(); - } - } } else { diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 26668070a66..f6c63e631f6 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -62,6 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void Name(const hstring& name); hstring ID() const noexcept; + void ID(hstring id); Control::KeyChord Keys() const noexcept; hstring KeyChordText() const noexcept; From 10d1fc8d60c7d97cad1e5178c068ada24bc82a3e Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 17:30:55 -0700 Subject: [PATCH 14/76] this way is better --- .../TerminalSettingsModel/ActionMap.cpp | 19 +++++++++++++++++-- .../TerminalSettingsModel/ActionMap.h | 2 +- .../CascadiaSettingsSerialization.cpp | 14 +------------- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index d5d29fe576d..5e82a0d978e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -795,9 +795,24 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } - std::unordered_map ActionMap::AllActions() + // Note: this should only be called for the action map in the user's settings file + bool ActionMap::GenerateIDsForActions() { - return _ActionMap; + bool fixedUp{ false }; + for (auto actionPair : _ActionMap) + { + auto cmdImpl{ winrt::get_self(actionPair.second) }; + if (cmdImpl->ID().empty()) + { + auto actionAndArgsImpl{ winrt::get_self(cmdImpl->ActionAndArgs()) }; + if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) + { + cmdImpl->ID(generatedID); + fixedUp = true; + } + } + } + return fixedUp; } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 80fb121dc7d..880b9989ff6 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -71,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ToJson() const; // modification - std::unordered_map AllActions(); + bool GenerateIDsForActions(); bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); void DeleteKeyBinding(const Control::KeyChord& keys); void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 8bd4aacb26b..c5a8eb2bbb5 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -506,19 +506,7 @@ bool SettingsLoader::FixupUserSettings() // we need to generate an ID for a command in the user settings if it doesn't already have one auto actionMap{ winrt::get_self(userSettings.globals->ActionMap()) }; - for (auto actionPair : actionMap->AllActions()) - { - auto cmdImpl{ winrt::get_self(actionPair.second) }; - if (cmdImpl->ID().empty()) - { - auto actionAndArgsImpl{ winrt::get_self(cmdImpl->ActionAndArgs()) }; - if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) - { - cmdImpl->ID(generatedID); - fixedUp = true; - } - } - } + fixedUp = actionMap->GenerateIDsForActions() || fixedUp; return fixedUp; } From 71bf90f2952062915544362a4a206e77f6401982 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 18:02:17 -0700 Subject: [PATCH 15/76] even better, also get the ID from json --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 7 +------ src/cascadia/TerminalSettingsModel/Command.cpp | 11 +++++++++-- src/cascadia/TerminalSettingsModel/Command.h | 2 +- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 5e82a0d978e..6fac74e1204 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -804,12 +804,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto cmdImpl{ winrt::get_self(actionPair.second) }; if (cmdImpl->ID().empty()) { - auto actionAndArgsImpl{ winrt::get_self(cmdImpl->ActionAndArgs()) }; - if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) - { - cmdImpl->ID(generatedID); - fixedUp = true; - } + fixedUp = cmdImpl->GenerateID() || fixedUp; } } return fixedUp; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index b35d08218ef..63c6ab1776f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -121,9 +121,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hstring{ _ID }; } - void Command::ID(hstring id) + bool Command::GenerateID() { - _ID = id; + auto actionAndArgsImpl{ winrt::get_self(_ActionAndArgs) }; + if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) + { + _ID = generatedID; + return true; + } + return false; } void Command::Name(const hstring& value) @@ -276,6 +282,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { auto result = winrt::make_self(); result->_Origin = origin; + JsonUtils::GetValueForKey(json, IDKey, result->_ID); auto nested = false; JsonUtils::GetValueForKey(json, IterateOnKey, result->_IterateOn); diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index f6c63e631f6..f22e35348c7 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -62,7 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void Name(const hstring& name); hstring ID() const noexcept; - void ID(hstring id); + bool GenerateID(); Control::KeyChord Keys() const noexcept; hstring KeyChordText() const noexcept; From d57c7a1f0352d829a55875e50a4c92976c616d0d Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 27 Mar 2024 18:04:12 -0700 Subject: [PATCH 16/76] move this --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 6fac74e1204..750a4ee5e43 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -795,9 +795,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } - // Note: this should only be called for the action map in the user's settings file bool ActionMap::GenerateIDsForActions() { + // Note: this should ONLY be called for the action map in the user's settings file bool fixedUp{ false }; for (auto actionPair : _ActionMap) { From 5c2307c531e3ec5e6452a1dfc897ef55c2f63eda Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 28 Mar 2024 11:48:51 -0700 Subject: [PATCH 17/76] fix test --- src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 455c0b78c27..80d1513de33 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -493,7 +493,7 @@ namespace SettingsModelUnitTests } ], "actions": [ - { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "keys": "ctrl+k" } + { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id" : "User.sendInput.15093368865568800249", "keys": "ctrl+k" } ], "theme": "system", "themes": [] From 9fc69721c9c6f10cd9d8fc396341d424ff71589c Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 29 Mar 2024 13:40:53 -0700 Subject: [PATCH 18/76] add tests --- .../SerializationTests.cpp | 126 ++++++++++++++++++ 1 file changed, 126 insertions(+) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 80d1513de33..7c26228c096 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -36,6 +36,10 @@ namespace SettingsModelUnitTests TEST_METHOD(RoundtripUserModifiedColorSchemeCollisionUnusedByProfiles); TEST_METHOD(RoundtripUserDeletedColorSchemeCollision); + TEST_METHOD(RoundtripGenerateActionID); + TEST_METHOD(DoNotGenerateActionID); + TEST_METHOD(RoundtripActionIDsAreSame); + private: // Method Description: // - deserializes and reserializes a json string representing a settings object model of type T @@ -948,4 +952,126 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); } + + void SerializationTests::RoundtripGenerateActionID() + { + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "command": "closePane", + "keys": "ctrl+shift+w" + } + ] + })" }; + + + // Key difference: the close pane action now has a generated ID + static constexpr std::string_view newSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "command": "closePane", + "keys": "ctrl+shift+w", + "id" : "User.closePane" + } + ] + })" }; + + implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + oldLoader.MergeInboxIntoUserSettings(); + oldLoader.FinalizeLayering(); + VERIFY_IS_TRUE(oldLoader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto oldSettings = winrt::make_self(std::move(oldLoader)); + const auto oldResult{ oldSettings->ToJson() }; + + implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + newLoader.FixupUserSettings(); + const auto newSettings = winrt::make_self(std::move(newLoader)); + const auto newResult{ newSettings->ToJson() }; + + VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); + } + + void SerializationTests::DoNotGenerateActionID() + { + // for iterable commands, nested commands, and user-defined actions that already have + // an ID, we do not need to generate an ID + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "command": "closePane", + "keys": "ctrl+shift+w", + "id": "thisIsMyClosePane" + }, + { + "iterateOn": "profiles", + "icon": "${profile.icon}", + "name": "${profile.name}", + "command": { "action": "newTab", "profile": "${profile.name}" } + }, + { + "name": "Change font size...", + "commands": [ + { "command": { "action": "adjustFontSize", "delta": 1.0 } }, + { "command": { "action": "adjustFontSize", "delta": -1.0 } }, + { "command": "resetFontSize" }, + ] + } + ] + })" }; + + implementation::SettingsLoader oldLoader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + oldLoader.MergeInboxIntoUserSettings(); + oldLoader.FinalizeLayering(); + VERIFY_IS_FALSE(oldLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); + } + + void SerializationTests::RoundtripActionIDsAreSame() + { + static constexpr std::string_view settingsJson1{ R"( + { + "actions": [ + { + "name": "foo", + "command": { "action": "sendInput", "input": "VT Griese Mode" }, + "keys": "ctrl+shift+w" + } + ] + })" }; + + // Both settings files define the same action, so the generated ID should be the same for both + static constexpr std::string_view settingsJson2{ R"( + { + "actions": [ + { + "name": "foo", + "command": { "action": "sendInput", "input": "VT Griese Mode" }, + "keys": "ctrl+shift+w" + } + ] + })" }; + + implementation::SettingsLoader loader1{ settingsJson1, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader1.MergeInboxIntoUserSettings(); + loader1.FinalizeLayering(); + VERIFY_IS_TRUE(loader1.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings1 = winrt::make_self(std::move(loader1)); + const auto result1{ settings1->ToJson() }; + + implementation::SettingsLoader loader2{ settingsJson2, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader2.MergeInboxIntoUserSettings(); + loader2.FinalizeLayering(); + VERIFY_IS_TRUE(loader2.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings2 = winrt::make_self(std::move(loader2)); + const auto result2{ settings2->ToJson() }; + + VERIFY_ARE_EQUAL(toString(result1), toString(result2)); + } } From dca7df50c88c37d08d020607d315db5ca93a7958 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 29 Mar 2024 13:49:33 -0700 Subject: [PATCH 19/76] excess line --- src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 7c26228c096..2c1a48097a3 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -966,7 +966,6 @@ namespace SettingsModelUnitTests ] })" }; - // Key difference: the close pane action now has a generated ID static constexpr std::string_view newSettingsJson{ R"( { From dd25ed762f3663f930842b1b101ed3d3422f2f12 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 1 Apr 2024 10:23:23 -0700 Subject: [PATCH 20/76] change tests --- .../UnitTests_SettingsModel/SerializationTests.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 2c1a48097a3..30cd15f574a 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -960,21 +960,23 @@ namespace SettingsModelUnitTests "actions": [ { "name": "foo", - "command": "closePane", + "command": { "action": "sendInput", "input": "just some input" }, "keys": "ctrl+shift+w" } ] })" }; - // Key difference: the close pane action now has a generated ID + // Key differences: - the sendInput action now has a generated ID + // - this generated ID was created at the time of writing this test, + // and should remain robust (i.e. everytime we hash the args we should get the same result) static constexpr std::string_view newSettingsJson{ R"( { "actions": [ { "name": "foo", - "command": "closePane", + "command": { "action": "sendInput", "input": "just some input" }, "keys": "ctrl+shift+w", - "id" : "User.closePane" + "id" : "User.sendInput.3448838294654165202" } ] })" }; @@ -1039,7 +1041,7 @@ namespace SettingsModelUnitTests "actions": [ { "name": "foo", - "command": { "action": "sendInput", "input": "VT Griese Mode" }, + "command": { "action": "sendInput", "input": "this is some other input string" }, "keys": "ctrl+shift+w" } ] @@ -1051,7 +1053,7 @@ namespace SettingsModelUnitTests "actions": [ { "name": "foo", - "command": { "action": "sendInput", "input": "VT Griese Mode" }, + "command": { "action": "sendInput", "input": "this is some other input string" }, "keys": "ctrl+shift+w" } ] From 6e293a5ee8504747286c9c54adc396a66afb84e9 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 1 Apr 2024 10:26:54 -0700 Subject: [PATCH 21/76] Everytime --- src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 30cd15f574a..3bae542bc5c 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -968,7 +968,7 @@ namespace SettingsModelUnitTests // Key differences: - the sendInput action now has a generated ID // - this generated ID was created at the time of writing this test, - // and should remain robust (i.e. everytime we hash the args we should get the same result) + // and should remain robust (i.e. every time we hash the args we should get the same result) static constexpr std::string_view newSettingsJson{ R"( { "actions": [ From bdf42c2d9c2ca4a925087603c3dd2fe1be127373 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 11 Apr 2024 16:05:35 -0700 Subject: [PATCH 22/76] first round of nits --- src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp | 6 +++--- .../TerminalSettingsModel/Resources/en-US/Resources.resw | 3 --- .../UnitTests_SettingsModel/SerializationTests.cpp | 8 ++++---- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 90e6e92c777..e4365587c16 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -462,13 +462,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (_Action != ShortcutAction::Invalid) { auto actionKeyString = ActionToStringMap.find(_Action)->second; - auto result = RS_(L"OriginTagUser") + L"." + std::wstring{ actionKeyString.begin(), actionKeyString.end() }; + auto result = fmt::format(L"User.{}", std::wstring{ actionKeyString.begin(), actionKeyString.end() }); if (_Args) { // If there are args, append the hash of the args - result = result + L"." + std::to_wstring(_Args.Hash()); + fmt::format_to(std::back_inserter(result), L".{}", _Args.Hash()); } - return result; + return winrt::hstring{ result }; } return L""; } diff --git a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw index 327945bc7f3..6179099bdf5 100644 --- a/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalSettingsModel/Resources/en-US/Resources.resw @@ -192,9 +192,6 @@ Open system menu - - User - Command Prompt This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt" diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 68f9e406d39..ba4c6f2a1fb 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -37,8 +37,8 @@ namespace SettingsModelUnitTests TEST_METHOD(RoundtripUserDeletedColorSchemeCollision); TEST_METHOD(RoundtripGenerateActionID); - TEST_METHOD(DoNotGenerateActionID); - TEST_METHOD(RoundtripActionIDsAreSame); + TEST_METHOD(NoGeneratedIDsForIterableAndNestedCommands); + TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands); private: // Method Description: @@ -996,7 +996,7 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); } - void SerializationTests::DoNotGenerateActionID() + void SerializationTests::NoGeneratedIDsForIterableAndNestedCommands() { // for iterable commands, nested commands, and user-defined actions that already have // an ID, we do not need to generate an ID @@ -1032,7 +1032,7 @@ namespace SettingsModelUnitTests VERIFY_IS_FALSE(oldLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); } - void SerializationTests::RoundtripActionIDsAreSame() + void SerializationTests::GeneratedActionIDsEqualForIdenticalCommands() { static constexpr std::string_view settingsJson1{ R"( { From 12f3aa9d06eb1b98da2a4412f90da6e4a84f9dbb Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 12 Apr 2024 15:04:23 -0700 Subject: [PATCH 23/76] truncate and hex, debug assert --- .../TerminalSettingsModel/ActionAndArgs.cpp | 13 +++++++++++-- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 6 +++++- .../UnitTests_SettingsModel/SerializationTests.cpp | 4 ++-- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index e4365587c16..19bc08458ea 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -465,8 +465,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto result = fmt::format(L"User.{}", std::wstring{ actionKeyString.begin(), actionKeyString.end() }); if (_Args) { - // If there are args, append the hash of the args - fmt::format_to(std::back_inserter(result), L".{}", _Args.Hash()); + // If there are args, we need to append the hash of the args + // However, to make it a little more presentable we + // 1. truncate the hash to 32 bits + // 2. convert it to a hex string + // there is a _tiny_ chance of collision because of the truncate but unlikely for + // the number of commands a user is expected to have + const auto argsHash32 = static_cast(_Args.Hash() & 0xFFFFFFFF); + std::wstringstream stream; + stream << std::hex << std::uppercase << argsHash32; + const auto argsHash32InHex = stream.str(); + fmt::format_to(std::back_inserter(result), L".{}", argsHash32InHex); } return winrt::hstring{ result }; } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 750a4ee5e43..220cd825565 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -797,11 +797,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool ActionMap::GenerateIDsForActions() { - // Note: this should ONLY be called for the action map in the user's settings file bool fixedUp{ false }; for (auto actionPair : _ActionMap) { auto cmdImpl{ winrt::get_self(actionPair.second) }; + + // Note: this function should ONLY be called for the action map in the user's settings file + // this debug assert should verify that for debug builds + assert(cmdImpl->Origin() == OriginTag::User); + if (cmdImpl->ID().empty()) { fixedUp = cmdImpl->GenerateID() || fixedUp; diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index ba4c6f2a1fb..39c16ff5264 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -495,7 +495,7 @@ namespace SettingsModelUnitTests } ], "actions": [ - { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id" : "User.sendInput.15093368865568800249", "keys": "ctrl+k" } + { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id": "User.sendInput.E02B3DF9", "keys": "ctrl+k" } ], "theme": "system", "themes": [] @@ -974,7 +974,7 @@ namespace SettingsModelUnitTests "name": "foo", "command": { "action": "sendInput", "input": "just some input" }, "keys": "ctrl+shift+w", - "id" : "User.sendInput.3448838294654165202" + "id" : "User.sendInput.A020D2" } ] })" }; From aa4921268e1531b49b24c141dd3620d99db6b8cb Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 12 Apr 2024 15:08:53 -0700 Subject: [PATCH 24/76] null check --- src/cascadia/TerminalSettingsModel/Command.cpp | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 63c6ab1776f..c20be960a67 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -123,11 +123,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool Command::GenerateID() { - auto actionAndArgsImpl{ winrt::get_self(_ActionAndArgs) }; - if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) + if (_ActionAndArgs) { - _ID = generatedID; - return true; + auto actionAndArgsImpl{ winrt::get_self(_ActionAndArgs) }; + if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) + { + _ID = generatedID; + return true; + } } return false; } From 5ee630ec82b795328d696b042e8d2f5f1127544f Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 12 Apr 2024 15:16:36 -0700 Subject: [PATCH 25/76] fmt is smart --- src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 19bc08458ea..798ac12752c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -472,10 +472,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // there is a _tiny_ chance of collision because of the truncate but unlikely for // the number of commands a user is expected to have const auto argsHash32 = static_cast(_Args.Hash() & 0xFFFFFFFF); - std::wstringstream stream; - stream << std::hex << std::uppercase << argsHash32; - const auto argsHash32InHex = stream.str(); - fmt::format_to(std::back_inserter(result), L".{}", argsHash32InHex); + // {0:X} formats the truncated hash to an uppercase hex string + fmt::format_to(std::back_inserter(result), L".{0:X}", argsHash32); } return winrt::hstring{ result }; } From 360b92e56764033d1f4e943abde803bc056066f1 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 17 Apr 2024 16:38:52 -0700 Subject: [PATCH 26/76] fmt_compile, fix test --- src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp | 2 +- .../UnitTests_SettingsModel/SerializationTests.cpp | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 798ac12752c..3ad4278d968 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -462,7 +462,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (_Action != ShortcutAction::Invalid) { auto actionKeyString = ActionToStringMap.find(_Action)->second; - auto result = fmt::format(L"User.{}", std::wstring{ actionKeyString.begin(), actionKeyString.end() }); + auto result = fmt::format(FMT_COMPILE(L"User.{}"), actionKeyString); if (_Args) { // If there are args, we need to append the hash of the args diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 39c16ff5264..c7bbf5f6b38 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -16,6 +16,12 @@ using namespace WEX::Common; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Control; +#if defined(_M_IX86) +#define sendInputID "56911147" +#else +#define sendInputID "A020D2" +#endif + namespace SettingsModelUnitTests { class SerializationTests : public JsonTestClass @@ -974,7 +980,7 @@ namespace SettingsModelUnitTests "name": "foo", "command": { "action": "sendInput", "input": "just some input" }, "keys": "ctrl+shift+w", - "id" : "User.sendInput.A020D2" + "id" : "User.sendInput.)" sendInputID R"(" } ] })" }; From 5e70911a68b2c5482cbe0682c615d61a680c9274 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 17 Apr 2024 16:49:58 -0700 Subject: [PATCH 27/76] remove 0 --- src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp index 3ad4278d968..7bba13bc4cc 100644 --- a/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionAndArgs.cpp @@ -473,7 +473,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // the number of commands a user is expected to have const auto argsHash32 = static_cast(_Args.Hash() & 0xFFFFFFFF); // {0:X} formats the truncated hash to an uppercase hex string - fmt::format_to(std::back_inserter(result), L".{0:X}", argsHash32); + fmt::format_to(std::back_inserter(result), FMT_COMPILE(L".{:X}"), argsHash32); } return winrt::hstring{ result }; } From ca3eb87301044355f0afb995f4d0822a847b19cd Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 17 Apr 2024 16:58:04 -0700 Subject: [PATCH 28/76] rename and comment --- .../UnitTests_SettingsModel/SerializationTests.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index c7bbf5f6b38..8d9c55b6f89 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -16,10 +16,12 @@ using namespace WEX::Common; using namespace winrt::Microsoft::Terminal::Settings::Model; using namespace winrt::Microsoft::Terminal::Control; +// Different architectures will hash the same SendInput command to a different ID +// Check for the correct ID based on the architecture #if defined(_M_IX86) -#define sendInputID "56911147" +#define SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH "56911147" #else -#define sendInputID "A020D2" +#define SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH "A020D2" #endif namespace SettingsModelUnitTests @@ -980,7 +982,7 @@ namespace SettingsModelUnitTests "name": "foo", "command": { "action": "sendInput", "input": "just some input" }, "keys": "ctrl+shift+w", - "id" : "User.sendInput.)" sendInputID R"(" + "id" : "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"(" } ] })" }; From 85933e2231c0ecf465894efa88d886cee73d93a9 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 23 Apr 2024 09:49:27 -0700 Subject: [PATCH 29/76] midpoint --- .../TerminalSettingsModel/ActionMap.cpp | 170 ++++++++++++++++++ .../TerminalSettingsModel/ActionMap.h | 27 ++- .../ActionMapSerialization.cpp | 76 +++++++- .../TerminalSettingsModel/Command.cpp | 12 ++ .../GlobalAppSettings.cpp | 21 ++- .../TerminalSettingsModel/GlobalAppSettings.h | 3 + .../TerminalSettingsModel/KeysMap.cpp | 27 +++ src/cascadia/TerminalSettingsModel/KeysMap.h | 51 ++++++ .../TerminalSettingsModel/KeysMap.idl | 16 ++ .../KeysMapSerialization.cpp | 48 +++++ ...crosoft.Terminal.Settings.ModelLib.vcxproj | 10 ++ ...Terminal.Settings.ModelLib.vcxproj.filters | 1 + .../Microsoft.Terminal.Settings.Model.vcxproj | 1 + 13 files changed, 443 insertions(+), 20 deletions(-) create mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.cpp create mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.h create mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.idl create mode 100644 src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 220cd825565..2bcd590952b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -102,6 +102,29 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return std::nullopt; } + std::optional ActionMap::_GetActionByID2(const winrt::hstring actionID) const + { + // todo: check this + // Check current layer + const auto actionMapPair{ _ActionMap2.find(actionID) }; + if (actionMapPair != _ActionMap2.end()) + { + auto& cmd{ actionMapPair->second }; + + // ActionMap should never point to nullptr + FAIL_FAST_IF_NULL(cmd); + + return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? + nullptr : // explicitly unbound + cmd; + } + + // todo: we should check parents here right + + // We don't have an answer + return std::nullopt; + } + static void RegisterShortcutAction(ShortcutAction shortcutAction, std::unordered_map& list, std::unordered_set& visited) { const auto actionAndArgs{ make_self(shortcutAction) }; @@ -170,6 +193,38 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + void ActionMap::_PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const + { + // todo: check this + // Update AvailableActions and visitedActionIDs with our current layer + for (const auto& [actionID, cmd] : _ActionMap2) + { + if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + { + // Only populate AvailableActions with actions that haven't been visited already. + if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + { + const auto& name{ cmd.Name() }; + if (!name.empty()) + { + // Update AvailableActions. + const auto actionAndArgsImpl{ get_self(cmd.ActionAndArgs()) }; + availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy()); + } + + // Record that we already handled adding this action to the NameMap. + visitedActionIDs.insert(actionID); + } + } + } + + // Update NameMap and visitedActionIDs with our parents + for (const auto& parent : _parents) + { + parent->_PopulateAvailableActionsWithStandardCommands2(availableActions, visitedActionIDs); + } + } + // Method Description: // - Retrieves a map of command names to the commands themselves // - These commands should not be modified directly because they may result in @@ -285,6 +340,31 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cumulativeActions; } + // Method Description: + // - Provides an accumulated list of actions that are exposed. The accumulated list includes actions added in this layer, followed by actions added by our parents. + std::vector ActionMap::_GetCumulativeActions2() const noexcept + { + // check this + // First, add actions from our current layer + std::vector cumulativeActions2; + cumulativeActions2.reserve(_ActionMap2.size()); + + // masking actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. + std::transform(_ActionMap2.begin(), _ActionMap2.end(), std::back_inserter(cumulativeActions2), [](std::pair actionPair) { + return actionPair.second; + }); + + // Now, add the accumulated actions from our parents + for (const auto& parent : _parents) + { + const auto parentActions{ parent->_GetCumulativeActions() }; + cumulativeActions2.reserve(cumulativeActions2.size() + parentActions.size()); + cumulativeActions2.insert(cumulativeActions2.end(), parentActions.begin(), parentActions.end()); + } + + return cumulativeActions2; + } + IMapView ActionMap::GlobalHotkeys() { if (!_GlobalHotkeysCache) @@ -469,12 +549,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // to keep track of what key mappings are still valid. // _TryUpdateActionMap may update oldCmd and maskingCmd + Model::Command oldCmd{ nullptr }; Model::Command maskingCmd{ nullptr }; _TryUpdateActionMap(cmd, oldCmd, maskingCmd); _TryUpdateName(cmd, oldCmd, maskingCmd); _TryUpdateKeyChord(cmd, oldCmd, maskingCmd); + + _TryUpdateActionMap2(cmd); + // I don't think we need a _TryUpdateName with the new implementation? + // we might still need it for legacy case... + _TryUpdateKeyChord2(cmd); } // Method Description: @@ -538,6 +624,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + void ActionMap::_TryUpdateActionMap2(const Model::Command& cmd) + { + // todo: check this + // Example: + // { "command": "copy", "id": "User.MyAction" } --> add the action in for the first time + // { "command": "paste", "id": "User.MyAction" } --> overwrite the "User.MyAction" command + + // only add to the _ActionMap if there is an ID and the shortcut action is valid + // (if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that) + if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + { + // any existing command with the same id in this layer will get overwritten + _ActionMap2.insert_or_assign(cmdID, cmd); + } + } + // Method Description: // - Update our internal state with the name of the newly registered action // Arguments: @@ -686,6 +788,59 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + void ActionMap::_TryUpdateKeyChord2(const Model::Command& cmd) + { + // Example (this is a legacy case, where the keys are provided in the same block as the command): + // { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord + // { "name": "foo", "command": "copy" } --> no change to keys, exit early + const auto keys{ cmd.Keys() }; + if (!keys) + { + // the user is not trying to update the keys. + return; + } + + // Handle collisions + const auto oldKeyPair{ _KeyMap2.find(keys) }; + if (oldKeyPair != _KeyMap2.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) + + // if oldKeyPair->second is empty, that means this keychord was unbound and is now being rebound + // no collision logic needed - we will simply reassign it in the _KeyMap + if (!oldKeyPair->second.empty()) + { + const auto actionPair{ _ActionMap2.find(oldKeyPair->second) }; + const auto conflictingCmd{ actionPair->second }; + const auto conflictingCmdImpl{ get_self(conflictingCmd) }; + conflictingCmdImpl->EraseKey(keys); + } + } + + // Assign the new action in the _KeyMap + // However, there's a strange edge case here - since we're parsing a legacy or modern block, + // the user might have { "command": null, "id": "someID", "keys": "ctrl+c" } + // i.e. they provided an ID for a null command + // in this case, we do _not_ want to use the id they provided, we want to use an empty id + // (empty id in the _KeyMap indicates the the keychord was explicitly unbound) + if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) + { + _KeyMap2.insert_or_assign(keys, L""); + } + else + { + _KeyMap2.insert_or_assign(keys, cmd.ID()); + } + + cmd.RegisterKey(keys); + } + // Method Description: // - Determines whether the given key chord is explicitly unbound // Arguments: @@ -747,6 +902,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return std::nullopt; } + // Method Description: + // - 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: + // - 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::_GetActionByKeyChordInternal2(const Control::KeyChord& /*keys*/) const + { + // todo: complete this + return std::nullopt; + } + // Method Description: // - Retrieves the key chord for the provided action // Arguments: diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 880b9989ff6..131acdee919 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -18,6 +18,7 @@ Author(s): #include "ActionMap.g.h" #include "IInheritable.h" #include "Command.h" +#include "KeysMap.h" // fwdecl unittest classes namespace SettingsModelUnitTests @@ -31,22 +32,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { using InternalActionID = size_t; - struct KeyChordHash - { - inline std::size_t operator()(const Control::KeyChord& key) const - { - return static_cast(key.Hash()); - } - }; - - struct KeyChordEquality - { - inline bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const - { - return lhs.Equals(rhs); - } - }; - struct ActionMap : ActionMapT, IInheritable { // views @@ -84,14 +69,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: std::optional _GetActionByID(const InternalActionID actionID) const; + std::optional _GetActionByID2(const winrt::hstring actionID) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; + std::optional _GetActionByKeyChordInternal2(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); void _PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; + void _PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; std::vector _GetCumulativeActions() const noexcept; + std::vector _GetCumulativeActions2() const noexcept; void _TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd); void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd); @@ -124,6 +113,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // than is necessary to be serialized. std::unordered_map _MaskingActions; + void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); + void _TryUpdateActionMap2(const Model::Command& cmd); + void _TryUpdateKeyChord2(const Model::Command& cmd); + std::unordered_map _KeyMap2; + std::unordered_map _ActionMap2; + friend class SettingsModelUnitTests::KeyBindingsTests; friend class SettingsModelUnitTests::DeserializationTests; friend class SettingsModelUnitTests::TerminalSettingsTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index c03fecd82f5..fa2094bffe2 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -35,6 +35,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - json: an array of Json::Value's to deserialize into our ActionMap. // Return value: // - a list of warnings encountered while deserializing the json + // todo: update this description std::vector ActionMap::LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { // It's possible that the user provided keybindings have some warnings in @@ -43,14 +44,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // settings phase, so we'll collect them now. std::vector warnings; - for (const auto& cmdJson : json) + for (const auto& jsonBlock : json) { - if (!cmdJson.isObject()) + if (!jsonBlock.isObject()) { continue; } - AddAction(*Command::FromJson(cmdJson, warnings, origin, withKeybindings)); + // the json block may be 1 of 3 things: + // - the legacy style command block, that has the action, args and keys in it + // - the modern style command block, that has the action, args and an ID + // - the modern style keys block, that has the keys and an ID + + // if the block contains a "command" field, it is either a legacy or modern style command block + // and we can call Command::FromJson on it (Command::FromJson can handle parsing both legacy or modern) + + // if there is no "command" field, then it is a modern style keys block + // todo: use the CommandsKey / ActionKey static string view in Command.cpp somehow + if (jsonBlock.isMember(JsonKey("commands")) || jsonBlock.isMember(JsonKey("command"))) + { + AddAction(*Command::FromJson(jsonBlock, warnings, origin, withKeybindings)); + } + else + { + _AddKeyBindingHelper(jsonBlock, warnings); + } + // todo: need to have a flag for fixups applied during load } return warnings; @@ -77,6 +96,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation }; // Serialize all standard Command objects in the current layer + // todo: change to _NewActionMap for (const auto& [_, cmd] : _ActionMap) { toJson(cmd); @@ -96,4 +116,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return actionList; } + + void ActionMap::_AddKeyBindingHelper(const Json::Value& json, std::vector& warnings) + { + // There should always be a "keys" field + // - If there is also an "id" field - we add the pair to our _KeyMap + // - If there is no "id" field - this is an explicit unbinding, still add it to the _KeyMap, + // when this key chord is queried for we will know it is an explicit unbinding + // todo: use the KeysKey and IDKey static strings from Command.cpp + const auto keysJson{ json[JsonKey("keys")] }; + if (keysJson.isArray() && keysJson.size() > 1) + { + warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); + } + else + { + Control::KeyChord keys{ nullptr }; + winrt::hstring idJson; + if (JsonUtils::GetValueForKey(json, "keys", keys)) + { + // even if the "id" field doesn't exist in the json, idJson will be an empty string which is fine + JsonUtils::GetValueForKey(json, "id", idJson); + + // any existing keybinding with the same keychord in this layer will get overwritten + _KeyMap2.insert_or_assign(keys, idJson); + + // if there is an id, make sure the command registers these keys + if (!idJson.empty()) + { + const auto& cmd{ _GetActionByID2(idJson) }; + if (cmd && *cmd) + { + cmd->RegisterKey(keys); + } + else + { + // check for the same ID among our parents + for (const auto& parent : _parents) + { + const auto& inheritedCmd{ parent->_GetActionByID2(idJson) }; + if (inheritedCmd && *inheritedCmd) + { + inheritedCmd->RegisterKey(keys); + } + } + } + } + } + } + return; + } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index c20be960a67..373eb01c934 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -318,6 +318,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // create an "invalid" ActionAndArgs result->_ActionAndArgs = make(); result->_nestedCommand = true; + // todo: new implementation ignores nested commands, we need to not ignore this though } JsonUtils::GetValueForKey(json, IconKey, result->_iconPath); @@ -328,6 +329,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto actionJson{ json[JsonKey(ActionKey)] }) { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); + + // if this is a user-defined command and they did not provide an id, generate one for them + if (result->_ID.empty() && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) + { + // There is currently a minor bug here that doesn't affect anything - + // we will reach this point for each command in an 'unpacked' nested/iterable command + // which means we will generate IDs for them. These don't get written in the json + // or stored anywhere though, but since they're also not used for anything we should + // probably just not generate them at all + result->GenerateID(); + } } else { diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index ff6dd5eec63..ea8785a1a26 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -35,6 +35,7 @@ void GlobalAppSettings::_FinalizeInheritance() for (const auto& parent : _parents) { _actionMap->AddLeastImportantParent(parent->_actionMap); + _keysMap->AddLeastImportantParent(parent->_keysMap); _keybindingsWarnings.insert(_keybindingsWarnings.end(), parent->_keybindingsWarnings.begin(), parent->_keybindingsWarnings.end()); for (const auto& [k, v] : parent->_themes) @@ -55,6 +56,8 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_defaultProfile = _defaultProfile; globals->_actionMap = _actionMap->Copy(); + // todo: complete this + //globals->_keysMap = _keysMap->Copy(); globals->_keybindingsWarnings = _keybindingsWarnings; #define GLOBAL_SETTINGS_COPY(type, name, jsonKey, ...) \ @@ -111,6 +114,11 @@ winrt::Microsoft::Terminal::Settings::Model::ActionMap GlobalAppSettings::Action return *_actionMap; } +winrt::Microsoft::Terminal::Settings::Model::KeysMap GlobalAppSettings::KeysMap() const noexcept +{ + return *_keysMap; +} + // Method Description: // - Create a new instance of this class from a serialized JsonObject. // Arguments: @@ -155,11 +163,20 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { - static constexpr std::array bindingsKeys{ LegacyKeybindingsKey, ActionsKey }; + // this order change is intentional, we want to do the keybindings map _after_ so that we have already + // stored the action IDs in the action map + // also, we want the keybindings map to overwrite any leftover keybindings that might have existed in the first pass, + // in case the user did a partial update from legacy to modern + static constexpr std::array bindingsKeys{ ActionsKey, LegacyKeybindingsKey }; for (const auto& jsonKey : bindingsKeys) { if (auto bindings{ json[JsonKey(jsonKey)] }) { + // what if we parse _keyMap first and then populate _actionMap->LayerJson and pass in the keymap + // alternative: do 'modern' actionmap->layerjson first, and then do keymap layer json, and then do actionmap->legacylayerjson, passing in the keymap + // just do 1 layer json call here, if there's no command field its a keys object from the new map + // if (command), if (keys) + // maybe don't even need keysmapkey?? just reuse legacykeybindingskeys auto warnings = _actionMap->LayerJson(bindings, origin, withKeybindings); // It's possible that the user provided keybindings have some warnings @@ -259,6 +276,8 @@ Json::Value GlobalAppSettings::ToJson() #undef GLOBAL_SETTINGS_TO_JSON json[JsonKey(ActionsKey)] = _actionMap->ToJson(); + // todo: complete this + //json[JsonKey(KeysMapKey)] = _keysMap->ToJson(); return json; } diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index c977094df53..86a77f50f96 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -20,6 +20,7 @@ Author(s): #include "MTSMSettings.h" #include "ActionMap.h" +#include "KeysMap.h" #include "Command.h" #include "ColorScheme.h" #include "Theme.h" @@ -47,6 +48,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::ColorScheme DuplicateColorScheme(const Model::ColorScheme& scheme); Model::ActionMap ActionMap() const noexcept; + Model::KeysMap KeysMap() const noexcept; static com_ptr FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None); void LayerJson(const Json::Value& json, const OriginTag origin); @@ -88,6 +90,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::guid _defaultProfile{}; bool _legacyReloadEnvironmentVariables{ true }; winrt::com_ptr _actionMap{ winrt::make_self() }; + winrt::com_ptr _keysMap{ winrt::make_self() }; std::vector _keybindingsWarnings; Windows::Foundation::Collections::IMap _colorSchemes{ winrt::single_threaded_map() }; diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.cpp b/src/cascadia/TerminalSettingsModel/KeysMap.cpp new file mode 100644 index 00000000000..ad1bd9c936a --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/KeysMap.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +#include "pch.h" +#include "AllShortcutActions.h" +#include "KeysMap.h" +#include "Command.h" +#include "AllShortcutActions.h" + +#include "KeysMap.g.cpp" + +using namespace winrt::Microsoft::Terminal::Settings::Model; +using namespace winrt::Microsoft::Terminal::Control; +using namespace winrt::Windows::Foundation::Collections; + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + void KeysMap::Thing() + { + return; + } + + void KeysMap::Blah() + { + return; + } +} diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.h b/src/cascadia/TerminalSettingsModel/KeysMap.h new file mode 100644 index 00000000000..443a1b93dba --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/KeysMap.h @@ -0,0 +1,51 @@ +/*++ +Copyright (c) Microsoft Corporation +Licensed under the MIT license. + +Module Name: +- KeysMap.h + +Abstract: +- A mapping of key chords to actions. Includes (de)serialization logic. + +Author(s): +- Carlos Zamora - September 2020 + +--*/ + +#pragma once + +#include "KeysMap.g.h" +#include "IInheritable.h" +#include "Command.h" + +namespace winrt::Microsoft::Terminal::Settings::Model::implementation +{ + struct KeyChordHash + { + inline std::size_t operator()(const Control::KeyChord& key) const + { + return static_cast(key.Hash()); + } + }; + + struct KeyChordEquality + { + inline bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const + { + return lhs.Equals(rhs); + } + }; + + struct KeysMap : KeysMapT, IInheritable + { + void Thing(); + void Blah(); + void Ayy(); + + std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); + + private: + std::unordered_map _KeyMap; + }; +} diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.idl b/src/cascadia/TerminalSettingsModel/KeysMap.idl new file mode 100644 index 00000000000..45724d7632e --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/KeysMap.idl @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +namespace Microsoft.Terminal.Settings.Model +{ + // This interface ensures that no changes are made to KeysMap + interface IKeysMapView + { + void Thing(); + }; + + [default_interface] runtimeclass KeysMap : IKeysMapView + { + void Blah(); + } +} diff --git a/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp new file mode 100644 index 00000000000..69c90342428 --- /dev/null +++ b/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp @@ -0,0 +1,48 @@ +// 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 "KeysMap.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 +{ + void KeysMap::Ayy() + { + return; + } + + // 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 KeysMap::LayerJson(const Json::Value& /*json*/, const OriginTag /*origin*/, const bool /*withKeybindings*/) + { + // 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; + + // todo: complete this + + return warnings; + } +} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 31e4844eb00..4dc705a4011 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -57,6 +57,9 @@ ActionMap.idl + + KeysMap.idl + ApplicationState.idl @@ -136,6 +139,12 @@ ActionMap.idl + + KeysMap.idl + + + KeysMap.idl + ApplicationState.idl @@ -216,6 +225,7 @@ + diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 89b0f24f473..1302b01ed52 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -103,6 +103,7 @@ + diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 06e26727ed0..49f30580c77 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -29,6 +29,7 @@ in here - put them in the lib's vcxproj instead! --> + From c134402507540288e7c5fe878cc21c8eb555a1b1 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 11:24:40 -0700 Subject: [PATCH 30/76] about to test stage 1 --- .../TerminalSettingsModel/ActionMap.cpp | 268 +++++++++++++++--- .../TerminalSettingsModel/ActionMap.h | 17 +- .../TerminalSettingsModel/ActionMap.idl | 1 + .../ActionMapSerialization.cpp | 44 ++- .../CascadiaSettingsSerialization.cpp | 1 + .../TerminalSettingsModel/Command.cpp | 39 +++ src/cascadia/TerminalSettingsModel/Command.h | 1 + .../GlobalAppSettings.cpp | 10 +- .../TerminalSettingsModel/GlobalAppSettings.h | 1 + 9 files changed, 337 insertions(+), 45 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 2bcd590952b..b2c9b1bb58c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -58,6 +58,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hasher.finalize(); } + bool ActionMap::FixUpsAppliedDuringLoad() const + { + return _fixUpsAppliedDuringLoad; + } + // Method Description: // - Retrieves the Command in the current layer, if it's valid // - We internally store invalid commands as full commands. @@ -104,7 +109,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional ActionMap::_GetActionByID2(const winrt::hstring actionID) const { - // todo: check this // Check current layer const auto actionMapPair{ _ActionMap2.find(actionID) }; if (actionMapPair != _ActionMap2.end()) @@ -114,13 +118,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); + // todo: stage 1 - not sure if we need this? _ActionMap2 doesn't contain invalid commands I'm p sure return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? nullptr : // explicitly unbound cmd; } - // todo: we should check parents here right - // We don't have an answer return std::nullopt; } @@ -145,6 +148,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - Retrieves a map of actions that can be bound to a key IMapView ActionMap::AvailableActions() { + // todo: stage 2 if (!_AvailableActionsCache) { // populate _AvailableActionsCache @@ -195,7 +199,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ActionMap::_PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const { - // todo: check this + // todo: stage 2 // Update AvailableActions and visitedActionIDs with our current layer for (const auto& [actionID, cmd] : _ActionMap2) { @@ -231,6 +235,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // an invalid state for the `ActionMap` IMapView ActionMap::NameMap() { + // todo: stage 1 (done, edit return) if (!_NameMapCache) { // populate _NameMapCache @@ -240,6 +245,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _NameMapCache = single_threaded_map(std::move(nameMap)); } + if (!_NameMapCache2) + { + // populate _NameMapCache + std::unordered_map nameMap2{}; + _PopulateNameMapWithSpecialCommands2(nameMap2); + _PopulateNameMapWithStandardCommands2(nameMap2); + + _NameMapCache2 = single_threaded_map(std::move(nameMap2)); + } return _NameMapCache.GetView(); } @@ -313,6 +327,76 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + // Method Description: + // - Populates the provided nameMap with all of our special commands and our parent's special commands. + // - Special commands include nested and iterable 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::_PopulateNameMapWithSpecialCommands2(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->_PopulateNameMapWithSpecialCommands2(nameMap); + } + + // 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); + } + } + + // Add IterableCommands to NameMap + for (const auto& cmd : _IterableCommands) + { + nameMap.insert_or_assign(cmd.Name(), cmd); + } + } + + // Method Description: + // - 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. + void ActionMap::_PopulateNameMapWithStandardCommands2(std::unordered_map& nameMap) const + { + std::unordered_set visitedActionIDs; + for (const auto& cmd : _GetCumulativeActions2()) + { + // only populate with valid commands + if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + { + // Only populate NameMap with actions that haven't been visited already. + const auto actionID{ cmd.ID() }; + if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + { + 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.emplace(actionID); + } + } + } + } + // Method Description: // - Provides an accumulated list of actions that are exposed. The accumulated list includes actions added in this layer, followed by actions added by our parents. std::vector ActionMap::_GetCumulativeActions() const noexcept @@ -346,40 +430,49 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // check this // First, add actions from our current layer - std::vector cumulativeActions2; - cumulativeActions2.reserve(_ActionMap2.size()); + std::vector cumulativeActions; + cumulativeActions.reserve(_ActionMap2.size()); - // masking actions have priority. Actions here are constructed from consolidating an inherited action with changes we've found when populating this layer. - std::transform(_ActionMap2.begin(), _ActionMap2.end(), std::back_inserter(cumulativeActions2), [](std::pair actionPair) { + std::transform(_ActionMap2.begin(), _ActionMap2.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { return actionPair.second; }); // Now, add the accumulated actions from our parents for (const auto& parent : _parents) { - const auto parentActions{ parent->_GetCumulativeActions() }; - cumulativeActions2.reserve(cumulativeActions2.size() + parentActions.size()); - cumulativeActions2.insert(cumulativeActions2.end(), parentActions.begin(), parentActions.end()); + const auto parentActions{ parent->_GetCumulativeActions2() }; + cumulativeActions.reserve(cumulativeActions.size() + parentActions.size()); + cumulativeActions.insert(cumulativeActions.end(), parentActions.begin(), parentActions.end()); } - return cumulativeActions2; + return cumulativeActions; } IMapView ActionMap::GlobalHotkeys() { + // todo: stage 1 (done, edit return) if (!_GlobalHotkeysCache) { _RefreshKeyBindingCaches(); } + if (!_GlobalHotkeysCache2) + { + _RefreshKeyBindingCaches2(); + } return _GlobalHotkeysCache.GetView(); } IMapView ActionMap::KeyBindings() { + // todo: stage 1 (done, edit return) if (!_KeyBindingMapCache) { _RefreshKeyBindingCaches(); } + if (!_KeyBindingMapCache2) + { + _RefreshKeyBindingCaches2(); + } return _KeyBindingMapCache.GetView(); } @@ -405,6 +498,28 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys)); } + void ActionMap::_RefreshKeyBindingCaches2() + { + std::unordered_map keyBindingsMap; + std::unordered_map globalHotkeys; + std::unordered_set unboundKeys; + + _PopulateKeyBindingMapWithStandardCommands2(keyBindingsMap, unboundKeys); + + for (const auto& [keys, cmd] : keyBindingsMap) + { + // Only populate GlobalHotkeys with actions whose + // ShortcutAction is GlobalSummon or QuakeMode + if (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode) + { + globalHotkeys.emplace(keys, cmd); + } + } + + _KeyBindingMapCache2 = single_threaded_map(std::move(keyBindingsMap)); + _GlobalHotkeysCache2 = single_threaded_map(std::move(globalHotkeys)); + } + // Method Description: // - Populates the provided keyBindingsMap with all of our actions and our parents actions // while omitting the key bindings that were already added before. @@ -453,6 +568,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + // Method Description: + // - Populates the provided keyBindingsMap with all of our actions and our parents actions + // while omitting the key bindings that were already added before. + // - This needs to be a bottom up approach to ensure that we only add each key chord once. + // Arguments: + // - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself. + // - unboundKeys: a set of keys that are explicitly unbound + void ActionMap::_PopulateKeyBindingMapWithStandardCommands2(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const + { + // Update KeyBindingsMap with our current layer + for (const auto& [keys, actionID] : _KeyMap2) + { + // Get the action our KeyMap maps to. + // If the actionID is empty, this keybinding is explicitly unbound + if (!actionID.empty()) + { + const auto cmd{ _GetActionByID2(actionID).value() }; + if (cmd) + { + // iterate over all of the action's bound keys + const auto cmdImpl{ get_self(cmd) }; + for (const auto& keys : cmdImpl->KeyMappings()) + { + // Only populate KeyBindingsMap with actions that... + // (1) haven't been visited already + // (2) aren't explicitly unbound + if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) + { + keyBindingsMap.emplace(keys, cmd); + } + } + } + } + else + { + // record any keys that are explicitly unbound, + // but don't add them to the list of key bindings + unboundKeys.emplace(keys); + } + } + + // Update keyBindingsMap and unboundKeys with our parents + for (const auto& parent : _parents) + { + parent->_PopulateKeyBindingMapWithStandardCommands2(keyBindingsMap, unboundKeys); + } + } + com_ptr ActionMap::Copy() const { auto actionMap{ make_self() }; @@ -626,7 +789,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ActionMap::_TryUpdateActionMap2(const Model::Command& cmd) { - // todo: check this + // todo: stage 1 (done) // Example: // { "command": "copy", "id": "User.MyAction" } --> add the action in for the first time // { "command": "paste", "id": "User.MyAction" } --> overwrite the "User.MyAction" command @@ -826,7 +989,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Assign the new action in the _KeyMap // However, there's a strange edge case here - since we're parsing a legacy or modern block, // the user might have { "command": null, "id": "someID", "keys": "ctrl+c" } - // i.e. they provided an ID for a null command + // i.e. they provided an ID for a null command (which they really shouldn't, there's no purpose) // in this case, we do _not_ want to use the id they provided, we want to use an empty id // (empty id in the _KeyMap indicates the the keychord was explicitly unbound) if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) @@ -853,6 +1016,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // We use the fact that the ..Internal call returns nullptr for explicitly unbound // key chords, and nullopt for keychord that are not bound - it allows us to distinguish // between unbound and lack of binding. + // todo: stage 1 (done, edit return) + //return _GetActionByKeyChordInternal2(keys) == nullptr; return _GetActionByKeyChordInternal(keys) == nullptr; } @@ -865,6 +1030,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullptr if the key chord doesn't exist Model::Command ActionMap::GetActionByKeyChord(const Control::KeyChord& keys) const { + // todo: stage 1 (done, edit return) + //return _GetActionByKeyChordInternal2(keys).value_or(nullptr); return _GetActionByKeyChordInternal(keys).value_or(nullptr); } @@ -911,9 +1078,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - 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::_GetActionByKeyChordInternal2(const Control::KeyChord& /*keys*/) const + std::optional ActionMap::_GetActionByKeyChordInternal2(const Control::KeyChord& keys) const { - // todo: complete this + if (const auto keyIDPair = _KeyMap2.find(keys); keyIDPair != _KeyMap2.end()) + { + if (const auto cmdID = keyIDPair->second; !cmdID.empty()) + { + return _GetActionByID2(cmdID); + } + else + { + // the keychord is in our map, but points to an empty string - explicitly unbound + return nullptr; + } + } + // we did not find the keychord in our map, its not bound and not explicity unbound either return std::nullopt; } @@ -965,6 +1144,35 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } + // Method Description: + // - Retrieves the key chord for the provided action + // Arguments: + // - cmdID: the ID of the command 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::GetKeyBindingForAction2(winrt::hstring cmdID) const + { + // todo: stage 1 (done, need to update the callers to use this, note for review: what if the user makes an action that does the same thing and edits the keys?) + // Check our internal state. + if (const auto& cmd{ _GetActionByID2(cmdID) }) + { + return cmd->Keys(); + } + + // Check our parents + for (const auto& parent : _parents) + { + if (const auto& keys{ parent->GetKeyBindingForAction2(cmdID) }) + { + return keys; + } + } + + // This key binding does not exist + return nullptr; + } + bool ActionMap::GenerateIDsForActions() { bool fixedUp{ false }; @@ -981,6 +1189,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation fixedUp = cmdImpl->GenerateID() || fixedUp; } } + _fixUpsAppliedDuringLoad = true; + // todo: stage 1 - probably don't need this as a return value anymore? return fixedUp; } @@ -993,6 +1203,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - true, if successful. False, otherwise. bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) { + // todo: stage 2 const auto& cmd{ GetActionByKeyChord(oldKeys) }; if (!cmd) { @@ -1024,6 +1235,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // create an "unbound" command // { "command": "unbound", "keys": } + // todo: stage 2 const auto cmd{ make_self() }; cmd->ActionAndArgs(make()); cmd->RegisterKey(keys); @@ -1040,35 +1252,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void ActionMap::RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action) { + // todo: stage 2 auto cmd{ make_self() }; cmd->RegisterKey(keys); cmd->ActionAndArgs(action); AddAction(*cmd); } - void ActionMap::_recursiveUpdateCommandKeybindingLabels() - { - const auto& commands{ _ExpandedCommandsCache }; - - for (const auto& command : commands) - { - if (command.HasNestedCommands()) - { - _recursiveUpdateCommandKeybindingLabels(); - } - 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{ GetKeyBindingForAction(command.ActionAndArgs().Action(), - command.ActionAndArgs().Args()) }; - command.RegisterKey(keyChord); - } - } - } - // This is a helper to aid in sorting commands by their `Name`s, alphabetically. static bool _compareSchemeNames(const ColorScheme& lhs, const ColorScheme& rhs) { diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 131acdee919..c68db6e0c94 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -46,6 +46,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const; Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action) const; Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action, const IActionArgs& actionArgs) const; + Control::KeyChord GetKeyBindingForAction2(winrt::hstring cmdID) const; // population void AddAction(const Model::Command& cmd); @@ -54,6 +55,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static com_ptr FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None); std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson() const; + Json::Value KeyBindingsToJson() const; + bool FixUpsAppliedDuringLoad() const; // modification bool GenerateIDsForActions(); @@ -74,11 +77,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::optional _GetActionByKeyChordInternal2(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); + void _RefreshKeyBindingCaches2(); void _PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; + void _PopulateKeyBindingMapWithStandardCommands2(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; + std::vector _GetCumulativeActions() const noexcept; std::vector _GetCumulativeActions2() const noexcept; @@ -86,8 +92,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd); void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd); - void _recursiveUpdateCommandKeybindingLabels(); - Windows::Foundation::Collections::IMap _AvailableActionsCache{ nullptr }; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; Windows::Foundation::Collections::IMap _GlobalHotkeysCache{ nullptr }; @@ -113,12 +117,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // than is necessary to be serialized. std::unordered_map _MaskingActions; + bool _fixUpsAppliedDuringLoad; + void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); void _TryUpdateActionMap2(const Model::Command& cmd); void _TryUpdateKeyChord2(const Model::Command& cmd); std::unordered_map _KeyMap2; std::unordered_map _ActionMap2; + void _PopulateNameMapWithSpecialCommands2(std::unordered_map& nameMap) const; + void _PopulateNameMapWithStandardCommands2(std::unordered_map& nameMap) const; + + Windows::Foundation::Collections::IMap _NameMapCache2{ nullptr }; + Windows::Foundation::Collections::IMap _GlobalHotkeysCache2{ nullptr }; + Windows::Foundation::Collections::IMap _KeyBindingMapCache2{ nullptr }; + friend class SettingsModelUnitTests::KeyBindingsTests; friend class SettingsModelUnitTests::DeserializationTests; friend class SettingsModelUnitTests::TerminalSettingsTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index b84305b9d0e..9236ace2fb9 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -14,6 +14,7 @@ namespace Microsoft.Terminal.Settings.Model Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); [method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); + Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction2(String cmdID); Windows.Foundation.Collections.IMapView AvailableActions { get; }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index fa2094bffe2..7d879b563bd 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -64,12 +64,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (jsonBlock.isMember(JsonKey("commands")) || jsonBlock.isMember(JsonKey("command"))) { AddAction(*Command::FromJson(jsonBlock, warnings, origin, withKeybindings)); + + // if we're in a 'command' block and there are keys, this is the legacy style + // let the parse know that fixups are needed + if (jsonBlock.isMember(JsonKey("keys"))) + { + _fixUpsAppliedDuringLoad = true; + } } else { _AddKeyBindingHelper(jsonBlock, warnings); } - // todo: need to have a flag for fixups applied during load } return warnings; @@ -77,6 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ActionMap::ToJson() const { + // todo: stage 1 (done, need to uncomment) Json::Value actionList{ Json::ValueType::arrayValue }; // Command serializes to an array of JSON objects. @@ -95,13 +102,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; + //auto toJson2 = [&actionList](const Model::Command& cmd) { + // const auto cmdImpl{ winrt::get_self(cmd) }; + // const auto& cmdJsonArray{ cmdImpl->ToJson2() }; + // for (const auto& cmdJson : cmdJsonArray) + // { + // actionList.append(cmdJson); + // } + //}; + // Serialize all standard Command objects in the current layer - // todo: change to _NewActionMap for (const auto& [_, cmd] : _ActionMap) { toJson(cmd); } + //for (const auto& [_, cmd] : _ActionMap2) + //{ + // toJson2(cmd); + //} + // Serialize all nested Command objects added in the current layer for (const auto& [_, cmd] : _NestedCommands) { @@ -117,6 +137,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return actionList; } + Json::Value ActionMap::KeyBindingsToJson() const + { + Json::Value keybindingsList{ Json::ValueType::arrayValue }; + + auto toJson = [&keybindingsList](const KeyChord kc, const winrt::hstring cmdID) { + Json::Value keyIDPair{ Json::ValueType::objectValue }; + JsonUtils::SetValueForKey(keyIDPair, "keys", kc); + JsonUtils::SetValueForKey(keyIDPair, "id", cmdID); + keybindingsList.append(keyIDPair); + }; + + // Serialize all standard Command objects in the current layer + for (const auto& [keys, cmdID] : _KeyMap2) + { + toJson(keys, cmdID); + } + + return keybindingsList; + } + void ActionMap::_AddKeyBindingHelper(const Json::Value& json, std::vector& warnings) { // There should always be a "keys" field diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 37112fe76b6..6ae07302fa4 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -461,6 +461,7 @@ bool SettingsLoader::FixupUserSettings() }; auto fixedUp = userSettings.fixupsAppliedDuringLoad; + fixedUp = userSettings.globals->FixUpsAppliedDuringLoad() || fixedUp; fixedUp = RemapColorSchemeForProfile(userSettings.baseLayerProfile) || fixedUp; for (const auto& profile : userSettings.profiles) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 373eb01c934..4cc2d997eda 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -500,6 +500,45 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cmdList; } + // Function Description: + // - Serialize the Command into an array of json actions + // Arguments: + // - + // Return Value: + // - an array of serialized actions + Json::Value Command::ToJson2() const + { + Json::Value cmdList{ Json::ValueType::arrayValue }; + + if (_nestedCommand || _IterateOn != ExpandCommandType::None) + { + // handle special commands + // For these, we can trust _originalJson to be correct. + // In fact, we _need_ to use it here because we don't actually deserialize `iterateOn` + // until we expand the command. + cmdList.append(_originalJson); + } + else + { + Json::Value cmdJson{ Json::ValueType::objectValue }; + JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); + JsonUtils::SetValueForKey(cmdJson, NameKey, _name); + if (!_ID.empty()) + { + JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); + } + + if (_ActionAndArgs) + { + cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); + } + + cmdList.append(cmdJson); + } + + return cmdList; + } + // Function Description: // - Helper to escape a string as a json string. This function will also // trim off the leading and trailing double-quotes, so the output string diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index f22e35348c7..52589b8c71d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -51,6 +51,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const Json::Value& json, const OriginTag origin); Json::Value ToJson() const; + Json::Value ToJson2() const; bool HasNestedCommands() const; bool IsNestedCommand() const noexcept; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index ea8785a1a26..d9b34a41ea2 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -276,11 +276,17 @@ Json::Value GlobalAppSettings::ToJson() #undef GLOBAL_SETTINGS_TO_JSON json[JsonKey(ActionsKey)] = _actionMap->ToJson(); - // todo: complete this - //json[JsonKey(KeysMapKey)] = _keysMap->ToJson(); + json[JsonKey(LegacyKeybindingsKey)] = _actionMap->KeyBindingsToJson(); + // todo: stage 3 - fix tests, they're surely broke with this + return json; } +bool GlobalAppSettings::FixUpsAppliedDuringLoad() +{ + return _actionMap->FixUpsAppliedDuringLoad(); +} + winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept { auto requestedTheme = Model::Theme::IsSystemInDarkTheme() ? diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 86a77f50f96..b4a51ea3e55 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -55,6 +55,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson(); + bool FixUpsAppliedDuringLoad(); const std::vector& KeybindingsWarnings() const; From 22ab9363efda5c6f6be0564cddf1042fc85a6631 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 14:33:01 -0700 Subject: [PATCH 31/76] works?? --- src/cascadia/TerminalApp/TabBase.cpp | 4 +- src/cascadia/TerminalApp/TerminalPage.cpp | 10 +++-- .../TerminalSettingsModel/ActionMap.cpp | 41 ++++++++++++------- .../ActionMapSerialization.cpp | 32 +++++++-------- 4 files changed, 53 insertions(+), 34 deletions(-) diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp index 04b7268ba06..e788b69672e 100644 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ b/src/cascadia/TerminalApp/TabBase.cpp @@ -192,7 +192,9 @@ namespace winrt::TerminalApp::implementation // - void TabBase::_UpdateSwitchToTabKeyChord() { - const auto keyChord = _actionMap ? _actionMap.GetKeyBindingForAction(ShortcutAction::SwitchToTab, SwitchToTabArgs{ _TabViewIndex }) : nullptr; + const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); + const auto keyChord{ _actionMap.GetKeyBindingForAction2(id) }; + //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/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index ff4b6743e69..d459a6ad047 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -825,7 +825,8 @@ namespace winrt::TerminalApp::implementation newTabFlyout.Items().Append(settingsItem); auto actionMap = _settings.ActionMap(); - const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) }; + //const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) }; + const auto settingsKeyChord{ actionMap.GetKeyBindingForAction2(L"Terminal.OpenSettingsUI") }; if (settingsKeyChord) { _SetAcceleratorForMenuItem(settingsItem, settingsKeyChord); @@ -847,7 +848,8 @@ namespace winrt::TerminalApp::implementation commandPaletteFlyout.Click({ this, &TerminalPage::_CommandPaletteButtonOnClick }); newTabFlyout.Items().Append(commandPaletteFlyout); - const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; + //const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; + const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction2(L"Terminal.ToggleCommandPalette") }; if (commandPaletteKeyChord) { _SetAcceleratorForMenuItem(commandPaletteFlyout, commandPaletteKeyChord); @@ -1022,7 +1024,9 @@ namespace winrt::TerminalApp::implementation // NewTab(ProfileIndex=N) action NewTerminalArgs newTerminalArgs{ profileIndex }; NewTabArgs newTabArgs{ newTerminalArgs }; - auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) }; + //auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) }; + const auto id = fmt::format(FMT_COMPILE(L"Terminal.OpenNewTabProfile{}"), profileIndex); + const auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction2(id) }; // make sure we find one to display if (profileKeyChord) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index b2c9b1bb58c..648323b93e7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -118,7 +118,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); - // todo: stage 1 - not sure if we need this? _ActionMap2 doesn't contain invalid commands I'm p sure + // todo: stage 3 - not sure if we need this? _ActionMap2 doesn't contain invalid commands I'm p sure return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? nullptr : // explicitly unbound cmd; @@ -235,7 +235,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // an invalid state for the `ActionMap` IMapView ActionMap::NameMap() { - // todo: stage 1 (done, edit return) + // todo: stage 1 (done) if (!_NameMapCache) { // populate _NameMapCache @@ -254,7 +254,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _NameMapCache2 = single_threaded_map(std::move(nameMap2)); } - return _NameMapCache.GetView(); + return _NameMapCache2.GetView(); } // Method Description: @@ -450,7 +450,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation IMapView ActionMap::GlobalHotkeys() { - // todo: stage 1 (done, edit return) + // todo: stage 1 (done) if (!_GlobalHotkeysCache) { _RefreshKeyBindingCaches(); @@ -459,12 +459,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { _RefreshKeyBindingCaches2(); } - return _GlobalHotkeysCache.GetView(); + return _GlobalHotkeysCache2.GetView(); } IMapView ActionMap::KeyBindings() { - // todo: stage 1 (done, edit return) + // todo: stage 1 (done) if (!_KeyBindingMapCache) { _RefreshKeyBindingCaches(); @@ -473,7 +473,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { _RefreshKeyBindingCaches2(); } - return _KeyBindingMapCache.GetView(); + return _KeyBindingMapCache2.GetView(); } void ActionMap::_RefreshKeyBindingCaches() @@ -622,6 +622,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // KeyChord --> ID actionMap->_KeyMap = _KeyMap; + actionMap->_KeyMap2 = _KeyMap2; // ID --> Command actionMap->_ActionMap.reserve(_ActionMap.size()); @@ -1016,9 +1017,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // We use the fact that the ..Internal call returns nullptr for explicitly unbound // key chords, and nullopt for keychord that are not bound - it allows us to distinguish // between unbound and lack of binding. - // todo: stage 1 (done, edit return) - //return _GetActionByKeyChordInternal2(keys) == nullptr; - return _GetActionByKeyChordInternal(keys) == nullptr; + // todo: stage 1 (done) + return _GetActionByKeyChordInternal2(keys) == nullptr; + //return _GetActionByKeyChordInternal(keys) == nullptr; } // Method Description: @@ -1030,9 +1031,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullptr if the key chord doesn't exist Model::Command ActionMap::GetActionByKeyChord(const Control::KeyChord& keys) const { - // todo: stage 1 (done, edit return) - //return _GetActionByKeyChordInternal2(keys).value_or(nullptr); - return _GetActionByKeyChordInternal(keys).value_or(nullptr); + // todo: stage 1 (done) + return _GetActionByKeyChordInternal2(keys).value_or(nullptr); + //return _GetActionByKeyChordInternal(keys).value_or(nullptr); } // Method Description: @@ -1092,6 +1093,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } } + + // the command was not bound in this layer, + // ask my parents + for (const auto& parent : _parents) + { + const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal2(keys) }; + if (inheritedCmd) + { + return *inheritedCmd; + } + } + // we did not find the keychord in our map, its not bound and not explicity unbound either return std::nullopt; } @@ -1190,7 +1203,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } _fixUpsAppliedDuringLoad = true; - // todo: stage 1 - probably don't need this as a return value anymore? + // todo: stage 3 - probably don't need this as a return value anymore? return fixedUp; } diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 7d879b563bd..21803c845be 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -83,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ActionMap::ToJson() const { - // todo: stage 1 (done, need to uncomment) + // todo: stage 1 (done) Json::Value actionList{ Json::ValueType::arrayValue }; // Command serializes to an array of JSON objects. @@ -102,26 +102,26 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; - //auto toJson2 = [&actionList](const Model::Command& cmd) { - // const auto cmdImpl{ winrt::get_self(cmd) }; - // const auto& cmdJsonArray{ cmdImpl->ToJson2() }; - // for (const auto& cmdJson : cmdJsonArray) - // { - // actionList.append(cmdJson); - // } - //}; + auto toJson2 = [&actionList](const Model::Command& cmd) { + const auto cmdImpl{ winrt::get_self(cmd) }; + const auto& cmdJsonArray{ cmdImpl->ToJson2() }; + for (const auto& cmdJson : cmdJsonArray) + { + actionList.append(cmdJson); + } + }; // Serialize all standard Command objects in the current layer - for (const auto& [_, cmd] : _ActionMap) - { - toJson(cmd); - } - - //for (const auto& [_, cmd] : _ActionMap2) + //for (const auto& [_, cmd] : _ActionMap) //{ - // toJson2(cmd); + // toJson(cmd); //} + for (const auto& [_, cmd] : _ActionMap2) + { + toJson2(cmd); + } + // Serialize all nested Command objects added in the current layer for (const auto& [_, cmd] : _NestedCommands) { From 0a3e17eebb3453268f7723c3bd57c6e0404d23ff Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 15:33:35 -0700 Subject: [PATCH 32/76] edge cases --- .../TerminalSettingsModel/ActionMap.cpp | 47 +++++++++++++++---- .../ActionMapSerialization.cpp | 2 +- 2 files changed, 39 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 648323b93e7..53eb69bdf45 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -584,11 +584,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // If the actionID is empty, this keybinding is explicitly unbound if (!actionID.empty()) { - const auto cmd{ _GetActionByID2(actionID).value() }; - if (cmd) + const auto cmd{ _GetActionByID2(actionID) }; + if (cmd.has_value()) { // iterate over all of the action's bound keys - const auto cmdImpl{ get_self(cmd) }; + const auto cmdImpl{ get_self(cmd.value()) }; for (const auto& keys : cmdImpl->KeyMappings()) { // Only populate KeyBindingsMap with actions that... @@ -596,7 +596,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // (2) aren't explicitly unbound if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) { - keyBindingsMap.emplace(keys, cmd); + keyBindingsMap.emplace(keys, cmd.value()); } } } @@ -1081,23 +1081,52 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullopt if it was not bound in this layer std::optional ActionMap::_GetActionByKeyChordInternal2(const Control::KeyChord& keys) const { + // todo: stage 3 - why does this function search through parents but _GetActionByID2 doesn't? even the original implementation was similar with some recursive and some non-recursive functions if (const auto keyIDPair = _KeyMap2.find(keys); keyIDPair != _KeyMap2.end()) { if (const auto cmdID = keyIDPair->second; !cmdID.empty()) { - return _GetActionByID2(cmdID); + if (const auto cmd = _GetActionByID2(cmdID); cmd.has_value()) + { + // standard case: both the keys and the ID are defined in this layer + return cmd; + } + else + { + for (const auto parent : _parents) + { + if (const auto inheritedCmd = parent->_GetActionByID2(cmdID); inheritedCmd.has_value()) + { + // edge case 1: the keys are bound to an ID in this layer, but the ID is defined in one of our parents + return inheritedCmd; + } + } + } } else { - // the keychord is in our map, but points to an empty string - explicitly unbound + // the keychord is defined in this layer, but points to an empty string - explicitly unbound return nullptr; } } - // the command was not bound in this layer, - // ask my parents + // search through our parents for (const auto& parent : _parents) { + if (const auto parentKeyIDPair = parent->_KeyMap2.find(keys); parentKeyIDPair != parent->_KeyMap2.end()) + { + if (const auto cmdID = parentKeyIDPair->second; !cmdID.empty()) + { + if (const auto cmd = _GetActionByID2(cmdID); cmd.has_value()) + { + // edge case 2: the keychord maps to an ID in one of our parents, but a command with that ID exists in this layer + // use the command from this layer + return cmd; + } + } + } + + // we've checked for the standard case and the 2 edge cases, now we can recurse const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal2(keys) }; if (inheritedCmd) { @@ -1105,7 +1134,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - // we did not find the keychord in our map, its not bound and not explicity unbound either + // we did not find the keychord anywhere, its not bound and not explicity unbound either return std::nullopt; } diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 21803c845be..6cc158892c7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -175,7 +175,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring idJson; if (JsonUtils::GetValueForKey(json, "keys", keys)) { - // even if the "id" field doesn't exist in the json, idJson will be an empty string which is fine + // if the "id" field doesn't exist in the json, idJson will be an empty string which is fine JsonUtils::GetValueForKey(json, "id", idJson); // any existing keybinding with the same keychord in this layer will get overwritten From e28d47888c992f05fa9b467946c6a8374d3538be Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 16:04:39 -0700 Subject: [PATCH 33/76] some todos for later --- .../ActionMapSerialization.cpp | 4 ++-- .../TerminalSettingsModel/Command.cpp | 19 +++++++++++-------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 6cc158892c7..5e1c4dd5878 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -65,8 +65,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { AddAction(*Command::FromJson(jsonBlock, warnings, origin, withKeybindings)); - // if we're in a 'command' block and there are keys, this is the legacy style - // let the parse know that fixups are needed + // this is a 'command' block and there are keys - meaning this is the legacy style + // let the loader know that fixups are needed if (jsonBlock.isMember(JsonKey("keys"))) { _fixUpsAppliedDuringLoad = true; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 4cc2d997eda..afd2e2f28d3 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -318,7 +318,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // create an "invalid" ActionAndArgs result->_ActionAndArgs = make(); result->_nestedCommand = true; - // todo: new implementation ignores nested commands, we need to not ignore this though } JsonUtils::GetValueForKey(json, IconKey, result->_iconPath); @@ -330,14 +329,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); - // if this is a user-defined command and they did not provide an id, generate one for them - if (result->_ID.empty() && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) + // if this is a user-defined, non-iterable, valid command and they did not provide an id, generate one for them + if (result->_ID.empty() && result->_IterateOn == ExpandCommandType::None && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) { - // There is currently a minor bug here that doesn't affect anything - - // we will reach this point for each command in an 'unpacked' nested/iterable command - // which means we will generate IDs for them. These don't get written in the json - // or stored anywhere though, but since they're also not used for anything we should - // probably just not generate them at all + // todo: stage 3 + // couple of issues - + // 1. we reach this point for 'unpacked' nested commands, which means we generate IDs for them + // these IDs aren't used anywhere or written to the json, which is intentional, but we should + // figure out a way to not generate them at all + // 2. if we generate an ID for a command here, we need to let the loader know that fixups are needed - + // ideally via action map's _fixUpsAppliedDuringLoad, because having one of those flags for each command sounds horrendous + // however to do this without false positives, we need to fix 1 first result->GenerateID(); } } @@ -424,6 +426,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation else { // Override commands with the same name + // todo: stage 3 - we may not need to do this anymore commands.Insert(result->Name(), *result); } } From f425746169c7c9069e424a13b72ef9c1e64e88bb Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 16:21:22 -0700 Subject: [PATCH 34/76] remove keysmap --- .../TerminalSettingsModel/ActionMap.h | 17 ++++++- .../GlobalAppSettings.cpp | 13 ----- .../TerminalSettingsModel/GlobalAppSettings.h | 3 -- .../TerminalSettingsModel/KeysMap.cpp | 27 ---------- src/cascadia/TerminalSettingsModel/KeysMap.h | 51 ------------------- .../TerminalSettingsModel/KeysMap.idl | 16 ------ .../KeysMapSerialization.cpp | 48 ----------------- ...crosoft.Terminal.Settings.ModelLib.vcxproj | 10 ---- ...Terminal.Settings.ModelLib.vcxproj.filters | 1 - .../Microsoft.Terminal.Settings.Model.vcxproj | 1 - 10 files changed, 16 insertions(+), 171 deletions(-) delete mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.cpp delete mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.h delete mode 100644 src/cascadia/TerminalSettingsModel/KeysMap.idl delete mode 100644 src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index c68db6e0c94..479aadcb4f5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -18,7 +18,6 @@ Author(s): #include "ActionMap.g.h" #include "IInheritable.h" #include "Command.h" -#include "KeysMap.h" // fwdecl unittest classes namespace SettingsModelUnitTests @@ -32,6 +31,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { using InternalActionID = size_t; + struct KeyChordHash + { + inline std::size_t operator()(const Control::KeyChord& key) const + { + return static_cast(key.Hash()); + } + }; + + struct KeyChordEquality + { + inline bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const + { + return lhs.Equals(rhs); + } + }; + struct ActionMap : ActionMapT, IInheritable { // views diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index d9b34a41ea2..8ea3f46e627 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -35,7 +35,6 @@ void GlobalAppSettings::_FinalizeInheritance() for (const auto& parent : _parents) { _actionMap->AddLeastImportantParent(parent->_actionMap); - _keysMap->AddLeastImportantParent(parent->_keysMap); _keybindingsWarnings.insert(_keybindingsWarnings.end(), parent->_keybindingsWarnings.begin(), parent->_keybindingsWarnings.end()); for (const auto& [k, v] : parent->_themes) @@ -56,8 +55,6 @@ winrt::com_ptr GlobalAppSettings::Copy() const globals->_defaultProfile = _defaultProfile; globals->_actionMap = _actionMap->Copy(); - // todo: complete this - //globals->_keysMap = _keysMap->Copy(); globals->_keybindingsWarnings = _keybindingsWarnings; #define GLOBAL_SETTINGS_COPY(type, name, jsonKey, ...) \ @@ -114,11 +111,6 @@ winrt::Microsoft::Terminal::Settings::Model::ActionMap GlobalAppSettings::Action return *_actionMap; } -winrt::Microsoft::Terminal::Settings::Model::KeysMap GlobalAppSettings::KeysMap() const noexcept -{ - return *_keysMap; -} - // Method Description: // - Create a new instance of this class from a serialized JsonObject. // Arguments: @@ -172,11 +164,6 @@ void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTa { if (auto bindings{ json[JsonKey(jsonKey)] }) { - // what if we parse _keyMap first and then populate _actionMap->LayerJson and pass in the keymap - // alternative: do 'modern' actionmap->layerjson first, and then do keymap layer json, and then do actionmap->legacylayerjson, passing in the keymap - // just do 1 layer json call here, if there's no command field its a keys object from the new map - // if (command), if (keys) - // maybe don't even need keysmapkey?? just reuse legacykeybindingskeys auto warnings = _actionMap->LayerJson(bindings, origin, withKeybindings); // It's possible that the user provided keybindings have some warnings diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index b4a51ea3e55..7b3e1ff9a82 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -20,7 +20,6 @@ Author(s): #include "MTSMSettings.h" #include "ActionMap.h" -#include "KeysMap.h" #include "Command.h" #include "ColorScheme.h" #include "Theme.h" @@ -48,7 +47,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Model::ColorScheme DuplicateColorScheme(const Model::ColorScheme& scheme); Model::ActionMap ActionMap() const noexcept; - Model::KeysMap KeysMap() const noexcept; static com_ptr FromJson(const Json::Value& json, const OriginTag origin = OriginTag::None); void LayerJson(const Json::Value& json, const OriginTag origin); @@ -91,7 +89,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::guid _defaultProfile{}; bool _legacyReloadEnvironmentVariables{ true }; winrt::com_ptr _actionMap{ winrt::make_self() }; - winrt::com_ptr _keysMap{ winrt::make_self() }; std::vector _keybindingsWarnings; Windows::Foundation::Collections::IMap _colorSchemes{ winrt::single_threaded_map() }; diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.cpp b/src/cascadia/TerminalSettingsModel/KeysMap.cpp deleted file mode 100644 index ad1bd9c936a..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeysMap.cpp +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -#include "pch.h" -#include "AllShortcutActions.h" -#include "KeysMap.h" -#include "Command.h" -#include "AllShortcutActions.h" - -#include "KeysMap.g.cpp" - -using namespace winrt::Microsoft::Terminal::Settings::Model; -using namespace winrt::Microsoft::Terminal::Control; -using namespace winrt::Windows::Foundation::Collections; - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - void KeysMap::Thing() - { - return; - } - - void KeysMap::Blah() - { - return; - } -} diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.h b/src/cascadia/TerminalSettingsModel/KeysMap.h deleted file mode 100644 index 443a1b93dba..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeysMap.h +++ /dev/null @@ -1,51 +0,0 @@ -/*++ -Copyright (c) Microsoft Corporation -Licensed under the MIT license. - -Module Name: -- KeysMap.h - -Abstract: -- A mapping of key chords to actions. Includes (de)serialization logic. - -Author(s): -- Carlos Zamora - September 2020 - ---*/ - -#pragma once - -#include "KeysMap.g.h" -#include "IInheritable.h" -#include "Command.h" - -namespace winrt::Microsoft::Terminal::Settings::Model::implementation -{ - struct KeyChordHash - { - inline std::size_t operator()(const Control::KeyChord& key) const - { - return static_cast(key.Hash()); - } - }; - - struct KeyChordEquality - { - inline bool operator()(const Control::KeyChord& lhs, const Control::KeyChord& rhs) const - { - return lhs.Equals(rhs); - } - }; - - struct KeysMap : KeysMapT, IInheritable - { - void Thing(); - void Blah(); - void Ayy(); - - std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); - - private: - std::unordered_map _KeyMap; - }; -} diff --git a/src/cascadia/TerminalSettingsModel/KeysMap.idl b/src/cascadia/TerminalSettingsModel/KeysMap.idl deleted file mode 100644 index 45724d7632e..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeysMap.idl +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Microsoft Corporation. -// Licensed under the MIT license. - -namespace Microsoft.Terminal.Settings.Model -{ - // This interface ensures that no changes are made to KeysMap - interface IKeysMapView - { - void Thing(); - }; - - [default_interface] runtimeclass KeysMap : IKeysMapView - { - void Blah(); - } -} diff --git a/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp deleted file mode 100644 index 69c90342428..00000000000 --- a/src/cascadia/TerminalSettingsModel/KeysMapSerialization.cpp +++ /dev/null @@ -1,48 +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 "KeysMap.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 -{ - void KeysMap::Ayy() - { - return; - } - - // 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 KeysMap::LayerJson(const Json::Value& /*json*/, const OriginTag /*origin*/, const bool /*withKeybindings*/) - { - // 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; - - // todo: complete this - - return warnings; - } -} diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj index 4dc705a4011..31e4844eb00 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj @@ -57,9 +57,6 @@ ActionMap.idl - - KeysMap.idl - ApplicationState.idl @@ -139,12 +136,6 @@ ActionMap.idl - - KeysMap.idl - - - KeysMap.idl - ApplicationState.idl @@ -225,7 +216,6 @@ - diff --git a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters index 1302b01ed52..89b0f24f473 100644 --- a/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters +++ b/src/cascadia/TerminalSettingsModel/Microsoft.Terminal.Settings.ModelLib.vcxproj.filters @@ -103,7 +103,6 @@ - diff --git a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj index 49f30580c77..06e26727ed0 100644 --- a/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj +++ b/src/cascadia/TerminalSettingsModel/dll/Microsoft.Terminal.Settings.Model.vcxproj @@ -29,7 +29,6 @@ in here - put them in the lib's vcxproj instead! --> - From d0938e2a246833ee1d0fd5c31e3a62b10c391ea8 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 24 Apr 2024 18:15:37 -0700 Subject: [PATCH 35/76] ugly way to make sure we fixup --- .../TerminalSettingsModel/ActionMapSerialization.cpp | 7 +++++++ src/cascadia/TerminalSettingsModel/Command.cpp | 10 +++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 5e1c4dd5878..fd416626339 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -71,6 +71,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { _fixUpsAppliedDuringLoad = true; } + // for non-nested non-iterable user commands, if there's no ID we generate one for them + // let the loader know that fixups are needed + // todo: stage 3 - there has to be a better way to check this? + if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey("id")) && jsonBlock.isMember(JsonKey("command")) && !jsonBlock.isMember(JsonKey("iterateOn"))) + { + _fixUpsAppliedDuringLoad = true; + } } else { diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index afd2e2f28d3..7a64793030a 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -333,13 +333,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (result->_ID.empty() && result->_IterateOn == ExpandCommandType::None && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) { // todo: stage 3 - // couple of issues - - // 1. we reach this point for 'unpacked' nested commands, which means we generate IDs for them - // these IDs aren't used anywhere or written to the json, which is intentional, but we should - // figure out a way to not generate them at all - // 2. if we generate an ID for a command here, we need to let the loader know that fixups are needed - - // ideally via action map's _fixUpsAppliedDuringLoad, because having one of those flags for each command sounds horrendous - // however to do this without false positives, we need to fix 1 first + // we reach this point for 'unpacked' nested commands, which means we generate IDs for them + // these IDs aren't used anywhere or written to the json, which is intentional, but we should + // figure out a way to not generate them at all result->GenerateID(); } } From 12a61c595e97f549f67b425ad90851514fac1b13 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 25 Apr 2024 19:16:27 -0700 Subject: [PATCH 36/76] shows up in sui and all keybindings work --- .../TerminalSettingsModel/ActionMap.cpp | 178 ++++++++++++++---- .../TerminalSettingsModel/ActionMap.h | 3 +- .../ActionMapSerialization.cpp | 4 +- 3 files changed, 148 insertions(+), 37 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 53eb69bdf45..4b219a8703b 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -148,7 +148,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - Retrieves a map of actions that can be bound to a key IMapView ActionMap::AvailableActions() { - // todo: stage 2 + // todo: stage 2 (done) + // todo: stage 3 - can we update RegisterShortcutAction to use new IDs instead of InternalActionID? + // then we'll be able to update _PopulateAvailableACtionsWithStandardCommands with new IDs too + // the problem is that we need IDs for every ShortcutAction, even if it is not in defaults/user settings if (!_AvailableActionsCache) { // populate _AvailableActionsCache @@ -163,7 +166,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _AvailableActionsCache = single_threaded_map(std::move(availableActions)); } - return _AvailableActionsCache.GetView(); + if (!_AvailableActionsCache2) + { + // populate _AvailableActionsCache + std::unordered_map availableActions2; + std::unordered_set visitedActionIDs2; + _PopulateAvailableActionsWithStandardCommands2(availableActions2, visitedActionIDs2); + +// now add any ShortcutActions that we might have missed +#define ON_ALL_ACTIONS(action) RegisterShortcutAction(ShortcutAction::action, availableActions2, visitedActionIDs2); + ALL_SHORTCUT_ACTIONS +#undef ON_ALL_ACTIONS + + _AvailableActionsCache2 = single_threaded_map(std::move(availableActions2)); + } + return _AvailableActionsCache2.GetView(); } void ActionMap::_PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const @@ -197,15 +214,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - void ActionMap::_PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const + void ActionMap::_PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const { - // todo: stage 2 // Update AvailableActions and visitedActionIDs with our current layer - for (const auto& [actionID, cmd] : _ActionMap2) + for (const auto& [_, cmd] : _ActionMap2) { + // todo: stage 3 - not sure if we need this? _ActionMap2 doesn't contain invalid commands anymore I'm p sure if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) { // Only populate AvailableActions with actions that haven't been visited already. + const auto actionID = Hash(cmd.ActionAndArgs()); if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { const auto& name{ cmd.Name() }; @@ -589,14 +607,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // iterate over all of the action's bound keys const auto cmdImpl{ get_self(cmd.value()) }; - for (const auto& keys : cmdImpl->KeyMappings()) + for (const auto& kc : cmdImpl->KeyMappings()) { // Only populate KeyBindingsMap with actions that... // (1) haven't been visited already // (2) aren't explicitly unbound - if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) + if (keyBindingsMap.find(kc) == keyBindingsMap.end() && unboundKeys.find(kc) == unboundKeys.end()) { - keyBindingsMap.emplace(keys, cmd.value()); + keyBindingsMap.emplace(kc, cmd.value()); } } } @@ -631,6 +649,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_ActionMap.emplace(actionID, *winrt::get_self(cmd)->Copy()); } + // ID --> Command + actionMap->_ActionMap2.reserve(_ActionMap2.size()); + for (const auto& [actionID, cmd] : _ActionMap2) + { + actionMap->_ActionMap2.emplace(actionID, *winrt::get_self(cmd)->Copy()); + } + // ID --> Command actionMap->_MaskingActions.reserve(_MaskingActions.size()); for (const auto& [actionID, cmd] : _MaskingActions) @@ -676,6 +701,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _NameMapCache = nullptr; _GlobalHotkeysCache = nullptr; _KeyBindingMapCache = nullptr; + _NameMapCache2 = nullptr; + _GlobalHotkeysCache2 = nullptr; + _KeyBindingMapCache2 = nullptr; // Handle nested commands const auto cmdImpl{ get_self(cmd) }; @@ -714,12 +742,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // _TryUpdateActionMap may update oldCmd and maskingCmd - Model::Command oldCmd{ nullptr }; - Model::Command maskingCmd{ nullptr }; - _TryUpdateActionMap(cmd, oldCmd, maskingCmd); + //Model::Command oldCmd{ nullptr }; + //Model::Command maskingCmd{ nullptr }; + //_TryUpdateActionMap(cmd, oldCmd, maskingCmd); - _TryUpdateName(cmd, oldCmd, maskingCmd); - _TryUpdateKeyChord(cmd, oldCmd, maskingCmd); + //_TryUpdateName(cmd, oldCmd, maskingCmd); + //_TryUpdateKeyChord(cmd, oldCmd, maskingCmd); _TryUpdateActionMap2(cmd); // I don't think we need a _TryUpdateName with the new implementation? @@ -791,15 +819,50 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ActionMap::_TryUpdateActionMap2(const Model::Command& cmd) { // todo: stage 1 (done) - // Example: - // { "command": "copy", "id": "User.MyAction" } --> add the action in for the first time - // { "command": "paste", "id": "User.MyAction" } --> overwrite the "User.MyAction" command // only add to the _ActionMap if there is an ID and the shortcut action is valid // (if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that) if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) { - // any existing command with the same id in this layer will get overwritten + // if a command with cmdID already exists, we need to port the keybindings over and then overwrite the previous command + // explanation by example: + // - command object with ID "X" already exists, and has "ctrl" in its keymappings + // - this means our _KeyMap has an entry for "ctrl" that points to "X" + // - we are now adding a new command object that also has ID "X", and has "shift" in its keymappings + // - _ActionMap needs to be updated so that "X" points to new command object + // - however _KeyMap will now contain "ctrl" -> "X" and also "shift" -> "X" + // - so we have to let this command object know that "ctrl" points to it as well + if (const auto idCmdPair = _ActionMap2.find(cmdID); idCmdPair != _ActionMap2.end()) + { + const auto newCmdImpl{ get_self(cmd) }; + const auto oldCmdImpl{ get_self(idCmdPair->second) }; + + // Command's keymapping implementation cares about order (the keychord at the back of the vector + // is considered its 'primary' keychord) - make sure we preserve this order as we register the old command's keys + + // make a copy of the new commands keymappings (we will insert them later to preserve order) + // todo: stage 3 - can we make this cleaner omg + std::vector newCmdKeymappingsCopy; + for (const auto kc : newCmdImpl->KeyMappings()) + { + newCmdKeymappingsCopy.emplace_back(kc); + } + + // copy the old commands key mappings into the new command + for (const auto kc : oldCmdImpl->KeyMappings()) + { + newCmdImpl->RegisterKey(kc); + } + + // transfer back the commands from the copy we made + // (this just makes sure that newCmd's keys are at the back of the vector, + // duplicates are automatically deleted by RegisterKey) + for (const auto kc : newCmdKeymappingsCopy) + { + newCmdImpl->RegisterKey(kc); + } + } + _ActionMap2.insert_or_assign(cmdID, cmd); } } @@ -976,7 +1039,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // // Remove the old one. (unbind "copy" in the example above) - // if oldKeyPair->second is empty, that means this keychord was unbound and is now being rebound + // if oldKeyPair->second is empty, that means this keychord was unbound earlier in this layer and is now being rebound // no collision logic needed - we will simply reassign it in the _KeyMap if (!oldKeyPair->second.empty()) { @@ -1245,25 +1308,50 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - true, if successful. False, otherwise. bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) { - // todo: stage 2 - const auto& cmd{ GetActionByKeyChord(oldKeys) }; + // todo: stage 2 (done) + const auto cmd{ GetActionByKeyChord(oldKeys) }; if (!cmd) { // oldKeys must be bound. Otherwise, we don't know what action to bind. return false; } - if (newKeys) + //if (newKeys) + //{ + // // Bind newKeys + // const auto newCmd{ make_self() }; + // newCmd->ActionAndArgs(cmd.ActionAndArgs()); + // newCmd->RegisterKey(newKeys); + // AddAction(*newCmd); + //} + + //// unbind oldKeys + //DeleteKeyBinding(oldKeys); + + // possible cases: + // - keybinding exists in our layer and the parent layer + // REPLACE oldKeys with newKeys + // - keybinding only exists in our layer + // REPLACE oldKeys with newKeys + // - keybinding only exists in parent layer + // ADD newKeys to our map + if (auto oldKeyPair = _KeyMap2.find(oldKeys); oldKeyPair != _KeyMap2.end()) { - // Bind newKeys - const auto newCmd{ make_self() }; - newCmd->ActionAndArgs(cmd.ActionAndArgs()); - newCmd->RegisterKey(newKeys); - AddAction(*newCmd); + // oldKeys is bound in our layer, replace it with newKeys + _KeyMap2.insert_or_assign(newKeys, cmd.ID()); + _KeyMap2.erase(oldKeyPair); } + else + { + // oldKeys is bound in some other layer, just set newKeys in this layer + _KeyMap2.insert_or_assign(newKeys, cmd.ID()); + } + + // make sure to update the Command with these changes + const auto cmdImpl{ get_self(cmd) }; + cmdImpl->EraseKey(oldKeys); + cmdImpl->RegisterKey(newKeys); - // unbind oldKeys - DeleteKeyBinding(oldKeys); return true; } @@ -1277,11 +1365,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // create an "unbound" command // { "command": "unbound", "keys": } - // todo: stage 2 - const auto cmd{ make_self() }; - cmd->ActionAndArgs(make()); - cmd->RegisterKey(keys); - AddAction(*cmd); + // todo: stage 2 (done) + //const auto cmd{ make_self() }; + //cmd->ActionAndArgs(make()); + //cmd->RegisterKey(keys); + //AddAction(*cmd); + + // possible cases: + // - keys exist in our layer and parent layer + // convert the mapping in our layer to unbound + // - keys only exist in our layer + // just delete from our map + // - keys only exist in parent layer + // make an unbound command in this layer + if (auto keyPair = _KeyMap2.find(keys); keyPair != _KeyMap2.end()) + { + // this keychord is bound in our layer, delete it + _KeyMap2.erase(keyPair); + } + + // either the keychord was never in this layer or we just deleted it above, + // if GetActionByKeyChord still returns a command that means the keychord is bound in another layer + if (GetActionByKeyChord(keys)) + { + // set to unbound in this layer + _KeyMap2.emplace(keys, L""); + } } // Method Description: @@ -1294,10 +1403,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void ActionMap::RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action) { - // todo: stage 2 + // todo: stage 2 (done) auto cmd{ make_self() }; cmd->RegisterKey(keys); cmd->ActionAndArgs(action); + cmd->GenerateID(); AddAction(*cmd); } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 479aadcb4f5..0b488137331 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -94,7 +94,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _RefreshKeyBindingCaches(); void _RefreshKeyBindingCaches2(); void _PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; - void _PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; + void _PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; @@ -143,6 +143,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _PopulateNameMapWithSpecialCommands2(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands2(std::unordered_map& nameMap) const; + Windows::Foundation::Collections::IMap _AvailableActionsCache2{ nullptr }; Windows::Foundation::Collections::IMap _NameMapCache2{ nullptr }; Windows::Foundation::Collections::IMap _GlobalHotkeysCache2{ nullptr }; Windows::Foundation::Collections::IMap _KeyBindingMapCache2{ nullptr }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index fd416626339..d168fcc287a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -155,7 +155,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation keybindingsList.append(keyIDPair); }; - // Serialize all standard Command objects in the current layer + // Serialize all standard keybinding objects in the current layer for (const auto& [keys, cmdID] : _KeyMap2) { toJson(keys, cmdID); @@ -182,7 +182,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring idJson; if (JsonUtils::GetValueForKey(json, "keys", keys)) { - // if the "id" field doesn't exist in the json, idJson will be an empty string which is fine + // if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine JsonUtils::GetValueForKey(json, "id", idJson); // any existing keybinding with the same keychord in this layer will get overwritten From f1633e0360105c8d1426803301a67e3d2b73d141 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 25 Apr 2024 21:25:17 -0700 Subject: [PATCH 37/76] overwritten IDs and overwritten keychords show up properly in the SUI --- .../TerminalSettingsModel/ActionMap.cpp | 63 ++++++++++++++----- .../ActionMapSerialization.cpp | 10 +++ 2 files changed, 57 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 4b219a8703b..79012a6cea2 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -107,6 +107,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return std::nullopt; } + // todo: stage 3 - does this need to return an optional? std::optional ActionMap::_GetActionByID2(const winrt::hstring actionID) const { // Check current layer @@ -595,39 +596,68 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - unboundKeys: a set of keys that are explicitly unbound void ActionMap::_PopulateKeyBindingMapWithStandardCommands2(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const { + // todo: stage 3 - can we just use _GetActionByKeyChordInternal here? // Update KeyBindingsMap with our current layer for (const auto& [keys, actionID] : _KeyMap2) { - // Get the action our KeyMap maps to. - // If the actionID is empty, this keybinding is explicitly unbound + // if the actionID is empty, this keychord is explicitly unbound if (!actionID.empty()) { - const auto cmd{ _GetActionByID2(actionID) }; - if (cmd.has_value()) + // make sure we haven't visited this key chord before + if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) { - // iterate over all of the action's bound keys - const auto cmdImpl{ get_self(cmd.value()) }; - for (const auto& kc : cmdImpl->KeyMappings()) + Model::Command foundCommand{ nullptr }; + const auto cmd{ _GetActionByID2(actionID) }; + if (cmd.has_value()) + { + // the keychord entry and the command with that ID exist in this layer + foundCommand = cmd.value(); + } + else { - // Only populate KeyBindingsMap with actions that... - // (1) haven't been visited already - // (2) aren't explicitly unbound - if (keyBindingsMap.find(kc) == keyBindingsMap.end() && unboundKeys.find(kc) == unboundKeys.end()) + // we have the keychord entry in this layer, but the command with that ID exists in a different layer + for (const auto parent : _parents) { - keyBindingsMap.emplace(kc, cmd.value()); + const auto inheritedCmd{ parent->_GetActionByID2(actionID) }; + if (inheritedCmd.has_value()) + { + foundCommand = inheritedCmd.value(); + break; + } } } + + if (foundCommand) + { + keyBindingsMap.emplace(keys, foundCommand); + } } } else { - // record any keys that are explicitly unbound, - // but don't add them to the list of key bindings + // actionID is empty, meaning this keychord is explicitly unbound - record it unboundKeys.emplace(keys); } } - // Update keyBindingsMap and unboundKeys with our parents + // similar to _GetActionByKeyChordInternal, we have to check the case where the keychord is in the parent layer, + // but the ID has been redefined in this layer + for (const auto& parent : _parents) + { + for (const auto& [keys, actionID] : parent->_KeyMap2) + { + if (!actionID.empty() && keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) + { + const auto cmd{ _GetActionByID2(actionID) }; + if (cmd.has_value()) + { + keyBindingsMap.emplace(keys, cmd.value()); + } + } + } + } + + // now we can recurse for (const auto& parent : _parents) { parent->_PopulateKeyBindingMapWithStandardCommands2(keyBindingsMap, unboundKeys); @@ -841,7 +871,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // is considered its 'primary' keychord) - make sure we preserve this order as we register the old command's keys // make a copy of the new commands keymappings (we will insert them later to preserve order) - // todo: stage 3 - can we make this cleaner omg + // todo: stage 3 - can we make this cleaner omg, actually ideally we just remove this and move away from Command + // having knowledge of its own keymappings std::vector newCmdKeymappingsCopy; for (const auto kc : newCmdImpl->KeyMappings()) { diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index d168fcc287a..0e35dcaf91a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -191,6 +191,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if there is an id, make sure the command registers these keys if (!idJson.empty()) { + // todo: stage 3 + // there is a problem here + // if the command with this id is only going to appear later during settings load + // then this will return null, meaning that the command created later on will not register this keybinding + // the keybinding will still work fine within the app, its just that the Command object itself won't know about this keymapping + // if we move away from Command needing to know its keymappings this is fine + // if we want to stick with commands knowing their keymappings, we will need to store these IDs of commands that we didn't + // register keybindings for and get back to them after parsing is complete const auto& cmd{ _GetActionByID2(idJson) }; if (cmd && *cmd) { @@ -199,6 +207,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation else { // check for the same ID among our parents + // with the current loader, I don't think we ever get here because we never have parents while parsing the json + // the parents only get added after all jsons have been parsed for (const auto& parent : _parents) { const auto& inheritedCmd{ parent->_GetActionByID2(idJson) }; From 3e7ab3861a29bc3e063ccabd25c37e2fea41f256 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 26 Apr 2024 11:26:10 -0700 Subject: [PATCH 38/76] sui works? --- .../TerminalSettingsModel/ActionMap.cpp | 27 +++---------------- 1 file changed, 4 insertions(+), 23 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 0c0af825aab..bae9655c366 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -151,7 +151,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // todo: stage 2 (done) // todo: stage 3 - can we update RegisterShortcutAction to use new IDs instead of InternalActionID? - // then we'll be able to update _PopulateAvailableACtionsWithStandardCommands with new IDs too + // then we'll be able to update _PopulateAvailableActionsWithStandardCommands with new IDs too // the problem is that we need IDs for every ShortcutAction, even if it is not in defaults/user settings if (!_AvailableActionsCache) { @@ -164,7 +164,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #define ON_ALL_ACTIONS(action) RegisterShortcutAction(ShortcutAction::action, availableActions, visitedActionIDs); ALL_SHORTCUT_ACTIONS #undef ON_ALL_ACTIONS - _AvailableActionsCache = single_threaded_map(std::move(availableActions)); } if (!_AvailableActionsCache2) @@ -178,7 +177,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #define ON_ALL_ACTIONS(action) RegisterShortcutAction(ShortcutAction::action, availableActions2, visitedActionIDs2); ALL_SHORTCUT_ACTIONS #undef ON_ALL_ACTIONS - _AvailableActionsCache2 = single_threaded_map(std::move(availableActions2)); } return _AvailableActionsCache2.GetView(); @@ -1280,25 +1278,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } - bool ActionMap::GenerateIDsForActions() - { - bool fixedUp{ false }; - for (auto actionPair : _ActionMap) - { - auto cmdImpl{ winrt::get_self(actionPair.second) }; - - // Note: this function should ONLY be called for the action map in the user's settings file - // this debug assert should verify that for debug builds - assert(cmdImpl->Origin() == OriginTag::User); - - if (cmdImpl->ID().empty()) - { - fixedUp = cmdImpl->GenerateID() || fixedUp; - } - } - return fixedUp; - } - // Method Description: // - Retrieves the key chord for the provided action // Arguments: @@ -1385,6 +1364,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // REPLACE oldKeys with newKeys // - keybinding only exists in parent layer // ADD newKeys to our map + // ADD oldKeys to our map, unbound if (auto oldKeyPair = _KeyMap2.find(oldKeys); oldKeyPair != _KeyMap2.end()) { // oldKeys is bound in our layer, replace it with newKeys @@ -1393,8 +1373,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - // oldKeys is bound in some other layer, just set newKeys in this layer + // oldKeys is bound in some other layer, set newKeys to cmd in this layer, and oldKeys to unbound in this layer _KeyMap2.insert_or_assign(newKeys, cmd.ID()); + _KeyMap2.insert_or_assign(oldKeys, L""); } // make sure to update the Command with these changes From ae16a5e0e1989e4baec6c463db5279e93bd8a705 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 26 Apr 2024 15:43:06 -0700 Subject: [PATCH 39/76] started stage 3 --- src/cascadia/TerminalApp/TabBase.cpp | 3 +- src/cascadia/TerminalApp/TerminalPage.cpp | 9 +- .../TerminalSettingsModel/ActionMap.cpp | 792 ++---------------- .../TerminalSettingsModel/ActionMap.h | 47 +- .../TerminalSettingsModel/ActionMap.idl | 4 +- .../ActionMapSerialization.cpp | 19 +- .../CascadiaSettingsSerialization.cpp | 4 - .../TerminalSettingsModel/Command.cpp | 1 + 8 files changed, 74 insertions(+), 805 deletions(-) diff --git a/src/cascadia/TerminalApp/TabBase.cpp b/src/cascadia/TerminalApp/TabBase.cpp index e788b69672e..2d5e8da4433 100644 --- a/src/cascadia/TerminalApp/TabBase.cpp +++ b/src/cascadia/TerminalApp/TabBase.cpp @@ -193,8 +193,7 @@ namespace winrt::TerminalApp::implementation void TabBase::_UpdateSwitchToTabKeyChord() { const auto id = fmt::format(FMT_COMPILE(L"Terminal.SwitchToTab{}"), _TabViewIndex); - const auto keyChord{ _actionMap.GetKeyBindingForAction2(id) }; - //const auto keyChord = _actionMap ? _actionMap.GetKeyBindingForAction(ShortcutAction::SwitchToTab, SwitchToTabArgs{ _TabViewIndex }) : nullptr; + const auto keyChord{ _actionMap.GetKeyBindingForAction(id) }; const auto keyChordText = keyChord ? KeyChordSerialization::ToString(keyChord) : L""; if (_keyChord == keyChordText) diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 139145ef790..27b12d0ae4c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -826,8 +826,7 @@ namespace winrt::TerminalApp::implementation newTabFlyout.Items().Append(settingsItem); auto actionMap = _settings.ActionMap(); - //const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::OpenSettings, OpenSettingsArgs{ SettingsTarget::SettingsUI }) }; - const auto settingsKeyChord{ actionMap.GetKeyBindingForAction2(L"Terminal.OpenSettingsUI") }; + const auto settingsKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.OpenSettingsUI") }; if (settingsKeyChord) { _SetAcceleratorForMenuItem(settingsItem, settingsKeyChord); @@ -849,8 +848,7 @@ namespace winrt::TerminalApp::implementation commandPaletteFlyout.Click({ this, &TerminalPage::_CommandPaletteButtonOnClick }); newTabFlyout.Items().Append(commandPaletteFlyout); - //const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; - const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction2(L"Terminal.ToggleCommandPalette") }; + const auto commandPaletteKeyChord{ actionMap.GetKeyBindingForAction(L"Terminal.ToggleCommandPalette") }; if (commandPaletteKeyChord) { _SetAcceleratorForMenuItem(commandPaletteFlyout, commandPaletteKeyChord); @@ -1025,9 +1023,8 @@ namespace winrt::TerminalApp::implementation // NewTab(ProfileIndex=N) action NewTerminalArgs newTerminalArgs{ profileIndex }; NewTabArgs newTabArgs{ newTerminalArgs }; - //auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(ShortcutAction::NewTab, newTabArgs) }; const auto id = fmt::format(FMT_COMPILE(L"Terminal.OpenNewTabProfile{}"), profileIndex); - const auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction2(id) }; + const auto profileKeyChord{ _settings.ActionMap().GetKeyBindingForAction(id) }; // make sure we find one to display if (profileKeyChord) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index bae9655c366..650da464ad1 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -75,20 +75,9 @@ 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(const InternalActionID actionID) const + // todo: stage 3 - does this need to return an optional? + std::optional ActionMap::_GetActionByID(const winrt::hstring actionID) const { - // Check the masking actions - const auto maskingPair{ _MaskingActions.find(actionID) }; - if (maskingPair != _MaskingActions.end()) - { - // ActionMap should never point to nullptr - FAIL_FAST_IF_NULL(maskingPair->second); - - // masking actions cannot contain nested or invalid commands, - // so we can just return it directly. - return maskingPair->second; - } - // Check current layer const auto actionMapPair{ _ActionMap.find(actionID) }; if (actionMapPair != _ActionMap.end()) @@ -98,28 +87,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); - return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? - nullptr : // explicitly unbound - cmd; - } - - // We don't have an answer - return std::nullopt; - } - - // todo: stage 3 - does this need to return an optional? - std::optional ActionMap::_GetActionByID2(const winrt::hstring actionID) const - { - // Check current layer - const auto actionMapPair{ _ActionMap2.find(actionID) }; - if (actionMapPair != _ActionMap2.end()) - { - auto& cmd{ actionMapPair->second }; - - // ActionMap should never point to nullptr - FAIL_FAST_IF_NULL(cmd); - - // todo: stage 3 - not sure if we need this? _ActionMap2 doesn't contain invalid commands I'm p sure + // todo: stage 3 - not sure if we need this? _ActionMap doesn't contain invalid commands I'm p sure return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? nullptr : // explicitly unbound cmd; @@ -149,7 +117,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - Retrieves a map of actions that can be bound to a key IMapView ActionMap::AvailableActions() { - // todo: stage 2 (done) // todo: stage 3 - can we update RegisterShortcutAction to use new IDs instead of InternalActionID? // then we'll be able to update _PopulateAvailableActionsWithStandardCommands with new IDs too // the problem is that we need IDs for every ShortcutAction, even if it is not in defaults/user settings @@ -166,57 +133,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #undef ON_ALL_ACTIONS _AvailableActionsCache = single_threaded_map(std::move(availableActions)); } - if (!_AvailableActionsCache2) - { - // populate _AvailableActionsCache - std::unordered_map availableActions2; - std::unordered_set visitedActionIDs2; - _PopulateAvailableActionsWithStandardCommands2(availableActions2, visitedActionIDs2); - -// now add any ShortcutActions that we might have missed -#define ON_ALL_ACTIONS(action) RegisterShortcutAction(ShortcutAction::action, availableActions2, visitedActionIDs2); - ALL_SHORTCUT_ACTIONS -#undef ON_ALL_ACTIONS - _AvailableActionsCache2 = single_threaded_map(std::move(availableActions2)); - } - return _AvailableActionsCache2.GetView(); + return _AvailableActionsCache.GetView(); } void ActionMap::_PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const { // Update AvailableActions and visitedActionIDs with our current layer - for (const auto& [actionID, cmd] : _ActionMap) - { - if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) - { - // Only populate AvailableActions with actions that haven't been visited already. - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) - { - const auto& name{ cmd.Name() }; - if (!name.empty()) - { - // Update AvailableActions. - const auto actionAndArgsImpl{ get_self(cmd.ActionAndArgs()) }; - availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy()); - } - - // Record that we already handled adding this action to the NameMap. - visitedActionIDs.insert(actionID); - } - } - } - - // Update NameMap and visitedActionIDs with our parents - for (const auto& parent : _parents) - { - parent->_PopulateAvailableActionsWithStandardCommands(availableActions, visitedActionIDs); - } - } - - void ActionMap::_PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const - { - // Update AvailableActions and visitedActionIDs with our current layer - for (const auto& [_, cmd] : _ActionMap2) + for (const auto& [_, cmd] : _ActionMap) { // todo: stage 3 - not sure if we need this? _ActionMap2 doesn't contain invalid commands anymore I'm p sure if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) @@ -242,7 +165,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Update NameMap and visitedActionIDs with our parents for (const auto& parent : _parents) { - parent->_PopulateAvailableActionsWithStandardCommands2(availableActions, visitedActionIDs); + parent->_PopulateAvailableActionsWithStandardCommands(availableActions, visitedActionIDs); } } @@ -252,26 +175,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // an invalid state for the `ActionMap` IMapView ActionMap::NameMap() { - // todo: stage 1 (done) if (!_NameMapCache) - { - // populate _NameMapCache - std::unordered_map nameMap{}; - _PopulateNameMapWithSpecialCommands(nameMap); - _PopulateNameMapWithStandardCommands(nameMap); - - _NameMapCache = single_threaded_map(std::move(nameMap)); - } - if (!_NameMapCache2) { // populate _NameMapCache std::unordered_map nameMap2{}; - _PopulateNameMapWithSpecialCommands2(nameMap2); - _PopulateNameMapWithStandardCommands2(nameMap2); + _PopulateNameMapWithSpecialCommands(nameMap2); + _PopulateNameMapWithStandardCommands(nameMap2); - _NameMapCache2 = single_threaded_map(std::move(nameMap2)); + _NameMapCache = single_threaded_map(std::move(nameMap2)); } - return _NameMapCache2.GetView(); + return _NameMapCache.GetView(); } // Method Description: @@ -319,79 +232,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - 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. void ActionMap::_PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const - { - std::unordered_set visitedActionIDs; - for (const auto& cmd : _GetCumulativeActions()) - { - // only populate with valid commands - if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) - { - // Only populate NameMap with actions that haven't been visited already. - const auto actionID{ Hash(cmd.ActionAndArgs()) }; - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) - { - 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.emplace(actionID); - } - } - } - } - - // Method Description: - // - Populates the provided nameMap with all of our special commands and our parent's special commands. - // - Special commands include nested and iterable 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::_PopulateNameMapWithSpecialCommands2(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->_PopulateNameMapWithSpecialCommands2(nameMap); - } - - // 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); - } - } - - // Add IterableCommands to NameMap - for (const auto& cmd : _IterableCommands) - { - nameMap.insert_or_assign(cmd.Name(), cmd); - } - } - - // Method Description: - // - 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. - void ActionMap::_PopulateNameMapWithStandardCommands2(std::unordered_map& nameMap) const { std::unordered_set visitedActionIDs; - for (const auto& cmd : _GetCumulativeActions2()) + for (const auto& cmd : _GetCumulativeActions()) { // only populate with valid commands if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) @@ -420,13 +263,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // First, add actions from our current layer std::vector cumulativeActions; - cumulativeActions.reserve(_MaskingActions.size() + _ActionMap.size()); + cumulativeActions.reserve(_ActionMap.size()); - // 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) { + std::transform(_ActionMap.begin(), _ActionMap.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { return actionPair.second; }); @@ -441,56 +280,22 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cumulativeActions; } - // Method Description: - // - Provides an accumulated list of actions that are exposed. The accumulated list includes actions added in this layer, followed by actions added by our parents. - std::vector ActionMap::_GetCumulativeActions2() const noexcept - { - // check this - // First, add actions from our current layer - std::vector cumulativeActions; - cumulativeActions.reserve(_ActionMap2.size()); - - std::transform(_ActionMap2.begin(), _ActionMap2.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { - return actionPair.second; - }); - - // Now, add the accumulated actions from our parents - for (const auto& parent : _parents) - { - const auto parentActions{ parent->_GetCumulativeActions2() }; - cumulativeActions.reserve(cumulativeActions.size() + parentActions.size()); - cumulativeActions.insert(cumulativeActions.end(), parentActions.begin(), parentActions.end()); - } - - return cumulativeActions; - } - IMapView ActionMap::GlobalHotkeys() { - // todo: stage 1 (done) if (!_GlobalHotkeysCache) { _RefreshKeyBindingCaches(); } - if (!_GlobalHotkeysCache2) - { - _RefreshKeyBindingCaches2(); - } - return _GlobalHotkeysCache2.GetView(); + return _GlobalHotkeysCache.GetView(); } IMapView ActionMap::KeyBindings() { - // todo: stage 1 (done) if (!_KeyBindingMapCache) { _RefreshKeyBindingCaches(); } - if (!_KeyBindingMapCache2) - { - _RefreshKeyBindingCaches2(); - } - return _KeyBindingMapCache2.GetView(); + return _KeyBindingMapCache.GetView(); } void ActionMap::_RefreshKeyBindingCaches() @@ -515,28 +320,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys)); } - void ActionMap::_RefreshKeyBindingCaches2() - { - std::unordered_map keyBindingsMap; - std::unordered_map globalHotkeys; - std::unordered_set unboundKeys; - - _PopulateKeyBindingMapWithStandardCommands2(keyBindingsMap, unboundKeys); - - for (const auto& [keys, cmd] : keyBindingsMap) - { - // Only populate GlobalHotkeys with actions whose - // ShortcutAction is GlobalSummon or QuakeMode - if (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode) - { - globalHotkeys.emplace(keys, cmd); - } - } - - _KeyBindingMapCache2 = single_threaded_map(std::move(keyBindingsMap)); - _GlobalHotkeysCache2 = single_threaded_map(std::move(globalHotkeys)); - } - // Method Description: // - Populates the provided keyBindingsMap with all of our actions and our parents actions // while omitting the key bindings that were already added before. @@ -545,58 +328,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself. // - unboundKeys: a set of keys that are explicitly unbound void ActionMap::_PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const - { - // Update KeyBindingsMap with our current layer - for (const auto& [keys, actionID] : _KeyMap) - { - // Get the action our KeyMap maps to. - // This _cannot_ be nullopt because KeyMap can only map to - // actions in this layer. - // This _can_ be nullptr because nullptr means it was - // explicitly unbound ( "command": "unbound", "keys": "ctrl+c" ). - const auto cmd{ _GetActionByID(actionID).value() }; - if (cmd) - { - // iterate over all of the action's bound keys - const auto cmdImpl{ get_self(cmd) }; - for (const auto& keys : cmdImpl->KeyMappings()) - { - // Only populate KeyBindingsMap with actions that... - // (1) haven't been visited already - // (2) aren't explicitly unbound - if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) - { - keyBindingsMap.emplace(keys, cmd); - } - } - } - else - { - // record any keys that are explicitly unbound, - // but don't add them to the list of key bindings - unboundKeys.emplace(keys); - } - } - - // Update keyBindingsMap and unboundKeys with our parents - for (const auto& parent : _parents) - { - parent->_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys); - } - } - - // Method Description: - // - Populates the provided keyBindingsMap with all of our actions and our parents actions - // while omitting the key bindings that were already added before. - // - This needs to be a bottom up approach to ensure that we only add each key chord once. - // Arguments: - // - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself. - // - unboundKeys: a set of keys that are explicitly unbound - void ActionMap::_PopulateKeyBindingMapWithStandardCommands2(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const { // todo: stage 3 - can we just use _GetActionByKeyChordInternal here? // Update KeyBindingsMap with our current layer - for (const auto& [keys, actionID] : _KeyMap2) + for (const auto& [keys, actionID] : _KeyMap) { // if the actionID is empty, this keychord is explicitly unbound if (!actionID.empty()) @@ -605,7 +340,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) { Model::Command foundCommand{ nullptr }; - const auto cmd{ _GetActionByID2(actionID) }; + const auto cmd{ _GetActionByID(actionID) }; if (cmd.has_value()) { // the keychord entry and the command with that ID exist in this layer @@ -616,7 +351,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // we have the keychord entry in this layer, but the command with that ID exists in a different layer for (const auto parent : _parents) { - const auto inheritedCmd{ parent->_GetActionByID2(actionID) }; + const auto inheritedCmd{ parent->_GetActionByID(actionID) }; if (inheritedCmd.has_value()) { foundCommand = inheritedCmd.value(); @@ -642,11 +377,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // but the ID has been redefined in this layer for (const auto& parent : _parents) { - for (const auto& [keys, actionID] : parent->_KeyMap2) + for (const auto& [keys, actionID] : parent->_KeyMap) { if (!actionID.empty() && keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) { - const auto cmd{ _GetActionByID2(actionID) }; + const auto cmd{ _GetActionByID(actionID) }; if (cmd.has_value()) { keyBindingsMap.emplace(keys, cmd.value()); @@ -658,7 +393,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // now we can recurse for (const auto& parent : _parents) { - parent->_PopulateKeyBindingMapWithStandardCommands2(keyBindingsMap, unboundKeys); + parent->_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys); } } @@ -668,7 +403,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // KeyChord --> ID actionMap->_KeyMap = _KeyMap; - actionMap->_KeyMap2 = _KeyMap2; // ID --> Command actionMap->_ActionMap.reserve(_ActionMap.size()); @@ -677,20 +411,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation actionMap->_ActionMap.emplace(actionID, *winrt::get_self(cmd)->Copy()); } - // ID --> Command - actionMap->_ActionMap2.reserve(_ActionMap2.size()); - for (const auto& [actionID, cmd] : _ActionMap2) - { - actionMap->_ActionMap2.emplace(actionID, *winrt::get_self(cmd)->Copy()); - } - - // ID --> Command - actionMap->_MaskingActions.reserve(_MaskingActions.size()); - for (const auto& [actionID, cmd] : _MaskingActions) - { - actionMap->_MaskingActions.emplace(actionID, *winrt::get_self(cmd)->Copy()); - } - // Name --> Command actionMap->_NestedCommands.reserve(_NestedCommands.size()); for (const auto& [name, cmd] : _NestedCommands) @@ -729,9 +449,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _NameMapCache = nullptr; _GlobalHotkeysCache = nullptr; _KeyBindingMapCache = nullptr; - _NameMapCache2 = nullptr; - _GlobalHotkeysCache2 = nullptr; - _KeyBindingMapCache2 = nullptr; // Handle nested commands const auto cmdImpl{ get_self(cmd) }; @@ -757,6 +474,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Add the new command to the KeyMap. // This map directs you to an entry in the ActionMap. + // todo: stage 3 - check this comment // 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. @@ -768,19 +486,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // 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 maskingCmd - - //Model::Command oldCmd{ nullptr }; - //Model::Command maskingCmd{ nullptr }; - //_TryUpdateActionMap(cmd, oldCmd, maskingCmd); - - //_TryUpdateName(cmd, oldCmd, maskingCmd); - //_TryUpdateKeyChord(cmd, oldCmd, maskingCmd); - - _TryUpdateActionMap2(cmd); - // I don't think we need a _TryUpdateName with the new implementation? - // we might still need it for legacy case... - _TryUpdateKeyChord2(cmd); + _TryUpdateActionMap(cmd); + _TryUpdateKeyChord(cmd); } // Method Description: @@ -791,260 +498,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - cmd: the action we're trying to register // - oldCmd: the action found in _ActionMap, if one already exists // - 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 - // { "command": "copy", "keys": "ctrl+shift+c" } --> update oldCmd - const auto actionID{ Hash(cmd.ActionAndArgs()) }; - const auto& actionPair{ _ActionMap.find(actionID) }; - if (actionPair == _ActionMap.end()) - { - // add this action in for the first time - _ActionMap.emplace(actionID, cmd); - } - else - { - // 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; - } - - // 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 _MaskingActions - // { "command": "copy", "keys": "ctrl+ins" } --> this should already be in _MaskingActions - - // Now check if this action was introduced in another layer. - const auto& maskingActionPair{ _MaskingActions.find(actionID) }; - if (maskingActionPair == _MaskingActions.end()) - { - // Check if we need to add this to our list of masking commands. - 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->_MaskingActions - // would have found it, so we inherit it for free! - const auto& inheritedCmd{ parent->_GetActionByID(actionID) }; - if (inheritedCmd && *inheritedCmd) - { - const auto& inheritedCmdImpl{ get_self(*inheritedCmd) }; - maskingCmd = *inheritedCmdImpl->Copy(); - _MaskingActions.emplace(actionID, maskingCmd); - } - } - } - else - { - // 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; - } - } - - void ActionMap::_TryUpdateActionMap2(const Model::Command& cmd) + void ActionMap::_TryUpdateActionMap(const Model::Command& cmd) { - // todo: stage 1 (done) - // only add to the _ActionMap if there is an ID and the shortcut action is valid // (if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that) if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) { - // if a command with cmdID already exists, we need to port the keybindings over and then overwrite the previous command - // explanation by example: - // - command object with ID "X" already exists, and has "ctrl" in its keymappings - // - this means our _KeyMap has an entry for "ctrl" that points to "X" - // - we are now adding a new command object that also has ID "X", and has "shift" in its keymappings - // - _ActionMap needs to be updated so that "X" points to new command object - // - however _KeyMap will now contain "ctrl" -> "X" and also "shift" -> "X" - // - so we have to let this command object know that "ctrl" points to it as well - if (const auto idCmdPair = _ActionMap2.find(cmdID); idCmdPair != _ActionMap2.end()) - { - const auto newCmdImpl{ get_self(cmd) }; - const auto oldCmdImpl{ get_self(idCmdPair->second) }; - - // Command's keymapping implementation cares about order (the keychord at the back of the vector - // is considered its 'primary' keychord) - make sure we preserve this order as we register the old command's keys - - // make a copy of the new commands keymappings (we will insert them later to preserve order) - // todo: stage 3 - can we make this cleaner omg, actually ideally we just remove this and move away from Command - // having knowledge of its own keymappings - std::vector newCmdKeymappingsCopy; - for (const auto kc : newCmdImpl->KeyMappings()) - { - newCmdKeymappingsCopy.emplace_back(kc); - } - - // copy the old commands key mappings into the new command - for (const auto kc : oldCmdImpl->KeyMappings()) - { - newCmdImpl->RegisterKey(kc); - } - - // transfer back the commands from the copy we made - // (this just makes sure that newCmd's keys are at the back of the vector, - // duplicates are automatically deleted by RegisterKey) - for (const auto kc : newCmdKeymappingsCopy) - { - newCmdImpl->RegisterKey(kc); - } - } - - _ActionMap2.insert_or_assign(cmdID, cmd); + _ActionMap.insert_or_assign(cmdID, cmd); } } - // Method Description: - // - Update our internal state with the name of the newly registered action - // Arguments: - // - cmd: the action we're trying to register - // - oldCmd: the action that already exists in our internal state. May be null. - // - 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 maskingCmd - // { "command": "copy" } --> no change to name, exit early - const auto cmdImpl{ get_self(cmd) }; - if (!cmdImpl->HasName()) - { - // the user is not trying to update the name. - return; - } - - // 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) - { - // This command has a name, check if it's new. - if (newName != oldCmd.Name()) - { - // The new name differs from the old name, - // update our name. - auto oldCmdImpl{ get_self(oldCmd) }; - oldCmdImpl->Name(newName); - } - } - - // 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 != maskingCmd.Name()) - { - // The new name differs from the old name, - // update our name. - auto maskingCmdImpl{ get_self(maskingCmd) }; - maskingCmdImpl->Name(newName); - } - } - - // Handle a collision with NestedCommands - _NestedCommands.erase(newName); - } - // Method Description: // - Update our internal state with the key chord of the newly registered action // Arguments: // - cmd: the action we're trying to register - // - oldCmd: the action that already exists in our internal state. May be null. - // - 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 maskingCmd - // { "name": "foo", "command": "copy" } --> no change to keys, exit early - const auto keys{ cmd.Keys() }; - if (!keys) - { - // 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 masking action! (this branch, sub-branch 2) - const auto conflictingActionID{ Hash(conflictingCmd.ActionAndArgs()) }; - 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 _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); - _MaskingActions.emplace(conflictingActionID, *conflictingCmdCopy); - } - else - { - // 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); - } - } - - // Assign the new action in the _KeyMap. - const auto actionID{ Hash(cmd.ActionAndArgs()) }; - _KeyMap.insert_or_assign(keys, actionID); - - // 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); - } - - // Additive operation: - // 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 (maskingCmd) - if (maskingCmd) - { - // Update inner Command with new key chord - auto maskingCmdImpl{ get_self(maskingCmd) }; - maskingCmdImpl->RegisterKey(keys); - } - } - - void ActionMap::_TryUpdateKeyChord2(const Model::Command& cmd) + void ActionMap::_TryUpdateKeyChord(const Model::Command& cmd) { // Example (this is a legacy case, where the keys are provided in the same block as the command): // { "command": "copy", "keys": "ctrl+c" } --> we are registering a new key chord @@ -1057,22 +525,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Handle collisions - const auto oldKeyPair{ _KeyMap2.find(keys) }; - if (oldKeyPair != _KeyMap2.end()) + 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) + // collision: the key chord was already in use in this layer - + // remove the old one // if oldKeyPair->second is empty, that means this keychord was unbound earlier in this layer and is now being rebound // no collision logic needed - we will simply reassign it in the _KeyMap if (!oldKeyPair->second.empty()) { - const auto actionPair{ _ActionMap2.find(oldKeyPair->second) }; + const auto actionPair{ _ActionMap.find(oldKeyPair->second) }; const auto conflictingCmd{ actionPair->second }; const auto conflictingCmdImpl{ get_self(conflictingCmd) }; conflictingCmdImpl->EraseKey(keys); @@ -1087,11 +550,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // (empty id in the _KeyMap indicates the the keychord was explicitly unbound) if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) { - _KeyMap2.insert_or_assign(keys, L""); + _KeyMap.insert_or_assign(keys, L""); } else { - _KeyMap2.insert_or_assign(keys, cmd.ID()); + _KeyMap.insert_or_assign(keys, cmd.ID()); } cmd.RegisterKey(keys); @@ -1109,9 +572,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // We use the fact that the ..Internal call returns nullptr for explicitly unbound // key chords, and nullopt for keychord that are not bound - it allows us to distinguish // between unbound and lack of binding. - // todo: stage 1 (done) - return _GetActionByKeyChordInternal2(keys) == nullptr; - //return _GetActionByKeyChordInternal(keys) == nullptr; + return _GetActionByKeyChordInternal(keys) == nullptr; } // Method Description: @@ -1123,9 +584,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullptr if the key chord doesn't exist Model::Command ActionMap::GetActionByKeyChord(const Control::KeyChord& keys) const { - // todo: stage 1 (done) - return _GetActionByKeyChordInternal2(keys).value_or(nullptr); - //return _GetActionByKeyChordInternal(keys).value_or(nullptr); + return _GetActionByKeyChordInternal(keys).value_or(nullptr); } // Method Description: @@ -1139,46 +598,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullopt if it was not bound in this layer std::optional ActionMap::_GetActionByKeyChordInternal(const Control::KeyChord& keys) const { - // Check the current layer - if (const auto actionIDPair = _KeyMap.find(keys); 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, - // ask my parents - for (const auto& parent : _parents) - { - const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; - if (inheritedCmd) - { - return *inheritedCmd; - } - } - - // This action is not explicitly bound - return std::nullopt; - } - - // Method Description: - // - 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: - // - 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::_GetActionByKeyChordInternal2(const Control::KeyChord& keys) const - { - // todo: stage 3 - why does this function search through parents but _GetActionByID2 doesn't? even the original implementation was similar with some recursive and some non-recursive functions - if (const auto keyIDPair = _KeyMap2.find(keys); keyIDPair != _KeyMap2.end()) + // todo: stage 3 - why does this function search through parents but _GetActionByID doesn't? even the original implementation was similar with some recursive and some non-recursive functions + if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end()) { if (const auto cmdID = keyIDPair->second; !cmdID.empty()) { - if (const auto cmd = _GetActionByID2(cmdID); cmd.has_value()) + if (const auto cmd = _GetActionByID(cmdID); cmd.has_value()) { // standard case: both the keys and the ID are defined in this layer return cmd; @@ -1187,7 +612,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { for (const auto parent : _parents) { - if (const auto inheritedCmd = parent->_GetActionByID2(cmdID); inheritedCmd.has_value()) + if (const auto inheritedCmd = parent->_GetActionByID(cmdID); inheritedCmd.has_value()) { // edge case 1: the keys are bound to an ID in this layer, but the ID is defined in one of our parents return inheritedCmd; @@ -1205,11 +630,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // search through our parents for (const auto& parent : _parents) { - if (const auto parentKeyIDPair = parent->_KeyMap2.find(keys); parentKeyIDPair != parent->_KeyMap2.end()) + if (const auto parentKeyIDPair = parent->_KeyMap.find(keys); parentKeyIDPair != parent->_KeyMap.end()) { if (const auto cmdID = parentKeyIDPair->second; !cmdID.empty()) { - if (const auto cmd = _GetActionByID2(cmdID); cmd.has_value()) + if (const auto cmd = _GetActionByID(cmdID); cmd.has_value()) { // edge case 2: the keychord maps to an ID in one of our parents, but a command with that ID exists in this layer // use the command from this layer @@ -1219,7 +644,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // we've checked for the standard case and the 2 edge cases, now we can recurse - const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal2(keys) }; + const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; if (inheritedCmd) { return *inheritedCmd; @@ -1230,54 +655,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation 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(const ShortcutAction& 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(const ShortcutAction& myAction, const IActionArgs& myArgs) const - { - if (myAction == ShortcutAction::Invalid) - { - return nullptr; - } - - // Check our internal state. - const auto actionAndArgs = winrt::make(myAction, myArgs); - const auto hash{ Hash(actionAndArgs) }; - if (const auto& cmd{ _GetActionByID(hash) }) - { - 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; - } - // Method Description: // - Retrieves the key chord for the provided action // Arguments: @@ -1285,11 +662,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Return Value: // - the key chord that executes the given action // - nullptr if the action is not bound to a key chord - Control::KeyChord ActionMap::GetKeyBindingForAction2(winrt::hstring cmdID) const + Control::KeyChord ActionMap::GetKeyBindingForAction(winrt::hstring cmdID) const { - // todo: stage 1 (done, need to update the callers to use this, note for review: what if the user makes an action that does the same thing and edits the keys?) + // todo: stage 3 what if the user makes an action that does the same thing and edits the keys? // Check our internal state. - if (const auto& cmd{ _GetActionByID2(cmdID) }) + if (const auto& cmd{ _GetActionByID(cmdID) }) { return cmd->Keys(); } @@ -1297,7 +674,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Check our parents for (const auto& parent : _parents) { - if (const auto& keys{ parent->GetKeyBindingForAction2(cmdID) }) + if (const auto& keys{ parent->GetKeyBindingForAction(cmdID) }) { return keys; } @@ -1307,27 +684,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return nullptr; } - bool ActionMap::GenerateIDsForActions() - { - bool fixedUp{ false }; - for (auto actionPair : _ActionMap) - { - auto cmdImpl{ winrt::get_self(actionPair.second) }; - - // Note: this function should ONLY be called for the action map in the user's settings file - // this debug assert should verify that for debug builds - assert(cmdImpl->Origin() == OriginTag::User); - - if (cmdImpl->ID().empty()) - { - fixedUp = cmdImpl->GenerateID() || fixedUp; - } - } - _fixUpsAppliedDuringLoad = true; - // todo: stage 3 - probably don't need this as a return value anymore? - return fixedUp; - } - // Method Description: // - Rebinds a key binding to a new key chord // Arguments: @@ -1337,7 +693,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - true, if successful. False, otherwise. bool ActionMap::RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys) { - // todo: stage 2 (done) const auto cmd{ GetActionByKeyChord(oldKeys) }; if (!cmd) { @@ -1345,40 +700,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return false; } - //if (newKeys) - //{ - // // Bind newKeys - // const auto newCmd{ make_self() }; - // newCmd->ActionAndArgs(cmd.ActionAndArgs()); - // newCmd->RegisterKey(newKeys); - // AddAction(*newCmd); - //} - - //// unbind oldKeys - //DeleteKeyBinding(oldKeys); - - // possible cases: - // - keybinding exists in our layer and the parent layer - // REPLACE oldKeys with newKeys - // - keybinding only exists in our layer - // REPLACE oldKeys with newKeys - // - keybinding only exists in parent layer - // ADD newKeys to our map - // ADD oldKeys to our map, unbound - if (auto oldKeyPair = _KeyMap2.find(oldKeys); oldKeyPair != _KeyMap2.end()) + if (auto oldKeyPair = _KeyMap.find(oldKeys); oldKeyPair != _KeyMap.end()) { // oldKeys is bound in our layer, replace it with newKeys - _KeyMap2.insert_or_assign(newKeys, cmd.ID()); - _KeyMap2.erase(oldKeyPair); + _KeyMap.insert_or_assign(newKeys, cmd.ID()); + _KeyMap.erase(oldKeyPair); } else { // oldKeys is bound in some other layer, set newKeys to cmd in this layer, and oldKeys to unbound in this layer - _KeyMap2.insert_or_assign(newKeys, cmd.ID()); - _KeyMap2.insert_or_assign(oldKeys, L""); + _KeyMap.insert_or_assign(newKeys, cmd.ID()); + _KeyMap.insert_or_assign(oldKeys, L""); } // make sure to update the Command with these changes + // todo: stage 3 - remove command's knowledge of keys const auto cmdImpl{ get_self(cmd) }; cmdImpl->EraseKey(oldKeys); cmdImpl->RegisterKey(newKeys); @@ -1394,25 +730,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void ActionMap::DeleteKeyBinding(const KeyChord& keys) { - // create an "unbound" command - // { "command": "unbound", "keys": } - // todo: stage 2 (done) - //const auto cmd{ make_self() }; - //cmd->ActionAndArgs(make()); - //cmd->RegisterKey(keys); - //AddAction(*cmd); - - // possible cases: - // - keys exist in our layer and parent layer - // convert the mapping in our layer to unbound - // - keys only exist in our layer - // just delete from our map - // - keys only exist in parent layer - // make an unbound command in this layer - if (auto keyPair = _KeyMap2.find(keys); keyPair != _KeyMap2.end()) + if (auto keyPair = _KeyMap.find(keys); keyPair != _KeyMap.end()) { // this keychord is bound in our layer, delete it - _KeyMap2.erase(keyPair); + _KeyMap.erase(keyPair); } // either the keychord was never in this layer or we just deleted it above, @@ -1420,7 +741,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (GetActionByKeyChord(keys)) { // set to unbound in this layer - _KeyMap2.emplace(keys, L""); + _KeyMap.emplace(keys, L""); } } @@ -1434,7 +755,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - void ActionMap::RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action) { - // todo: stage 2 (done) auto cmd{ make_self() }; cmd->RegisterKey(keys); cmd->ActionAndArgs(action); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 0b488137331..51283a5e3f3 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -59,9 +59,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // queries Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const; bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const; - Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action) const; - Control::KeyChord GetKeyBindingForAction(const ShortcutAction& action, const IActionArgs& actionArgs) const; - Control::KeyChord GetKeyBindingForAction2(winrt::hstring cmdID) const; + Control::KeyChord GetKeyBindingForAction(winrt::hstring cmdID) const; // population void AddAction(const Model::Command& cmd); @@ -74,7 +72,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool FixUpsAppliedDuringLoad() const; // modification - bool GenerateIDsForActions(); bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); void DeleteKeyBinding(const Control::KeyChord& keys); void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action); @@ -86,26 +83,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IVector FilterToSendInput(winrt::hstring currentCommandline); private: - std::optional _GetActionByID(const InternalActionID actionID) const; - std::optional _GetActionByID2(const winrt::hstring actionID) const; + std::optional _GetActionByID(const winrt::hstring actionID) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; - std::optional _GetActionByKeyChordInternal2(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); - void _RefreshKeyBindingCaches2(); void _PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; - void _PopulateAvailableActionsWithStandardCommands2(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; - void _PopulateKeyBindingMapWithStandardCommands2(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; std::vector _GetCumulativeActions() const noexcept; - std::vector _GetCumulativeActions2() const noexcept; - void _TryUpdateActionMap(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd); - void _TryUpdateName(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd); - void _TryUpdateKeyChord(const Model::Command& cmd, const Model::Command& oldCmd, const Model::Command& consolidatedCmd); + void _TryUpdateActionMap(const Model::Command& cmd); + void _TryUpdateKeyChord(const Model::Command& cmd); Windows::Foundation::Collections::IMap _AvailableActionsCache{ nullptr }; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; @@ -116,37 +106,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_map _NestedCommands; std::vector _IterableCommands; - std::unordered_map _KeyMap; - std::unordered_map _ActionMap; - - // Masking Actions: - // These are actions that were introduced in an ancestor, - // but were edited (or unbound) in the current layer. - // _ActionMap shows a Command with keys that were added in this layer, - // 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 _MaskingActions; bool _fixUpsAppliedDuringLoad; void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); - void _TryUpdateActionMap2(const Model::Command& cmd); - void _TryUpdateKeyChord2(const Model::Command& cmd); - std::unordered_map _KeyMap2; - std::unordered_map _ActionMap2; - - void _PopulateNameMapWithSpecialCommands2(std::unordered_map& nameMap) const; - void _PopulateNameMapWithStandardCommands2(std::unordered_map& nameMap) const; - - Windows::Foundation::Collections::IMap _AvailableActionsCache2{ nullptr }; - Windows::Foundation::Collections::IMap _NameMapCache2{ nullptr }; - Windows::Foundation::Collections::IMap _GlobalHotkeysCache2{ nullptr }; - Windows::Foundation::Collections::IMap _KeyBindingMapCache2{ nullptr }; + std::unordered_map _KeyMap; + std::unordered_map _ActionMap; friend class SettingsModelUnitTests::KeyBindingsTests; friend class SettingsModelUnitTests::DeserializationTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.idl b/src/cascadia/TerminalSettingsModel/ActionMap.idl index 9236ace2fb9..105f00715f5 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.idl +++ b/src/cascadia/TerminalSettingsModel/ActionMap.idl @@ -12,9 +12,7 @@ namespace Microsoft.Terminal.Settings.Model Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys); - Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action); - [method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs); - Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction2(String cmdID); + Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(String cmdID); Windows.Foundation.Collections.IMapView AvailableActions { get; }; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 0e35dcaf91a..1cf1ecd88cc 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -90,7 +90,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value ActionMap::ToJson() const { - // todo: stage 1 (done) Json::Value actionList{ Json::ValueType::arrayValue }; // Command serializes to an array of JSON objects. @@ -118,13 +117,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } }; - // Serialize all standard Command objects in the current layer - //for (const auto& [_, cmd] : _ActionMap) - //{ - // toJson(cmd); - //} - - for (const auto& [_, cmd] : _ActionMap2) + for (const auto& [_, cmd] : _ActionMap) { toJson2(cmd); } @@ -156,7 +149,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation }; // Serialize all standard keybinding objects in the current layer - for (const auto& [keys, cmdID] : _KeyMap2) + for (const auto& [keys, cmdID] : _KeyMap) { toJson(keys, cmdID); } @@ -186,12 +179,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation JsonUtils::GetValueForKey(json, "id", idJson); // any existing keybinding with the same keychord in this layer will get overwritten - _KeyMap2.insert_or_assign(keys, idJson); + _KeyMap.insert_or_assign(keys, idJson); // if there is an id, make sure the command registers these keys if (!idJson.empty()) { - // todo: stage 3 + // todo: stage 3 remove this! // there is a problem here // if the command with this id is only going to appear later during settings load // then this will return null, meaning that the command created later on will not register this keybinding @@ -199,7 +192,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if we move away from Command needing to know its keymappings this is fine // if we want to stick with commands knowing their keymappings, we will need to store these IDs of commands that we didn't // register keybindings for and get back to them after parsing is complete - const auto& cmd{ _GetActionByID2(idJson) }; + const auto& cmd{ _GetActionByID(idJson) }; if (cmd && *cmd) { cmd->RegisterKey(keys); @@ -211,7 +204,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // the parents only get added after all jsons have been parsed for (const auto& parent : _parents) { - const auto& inheritedCmd{ parent->_GetActionByID2(idJson) }; + const auto& inheritedCmd{ parent->_GetActionByID(idJson) }; if (inheritedCmd && *inheritedCmd) { inheritedCmd->RegisterKey(keys); diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 6ae07302fa4..009154eb199 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -505,10 +505,6 @@ bool SettingsLoader::FixupUserSettings() fixedUp = true; } - // we need to generate an ID for a command in the user settings if it doesn't already have one - auto actionMap{ winrt::get_self(userSettings.globals->ActionMap()) }; - fixedUp = actionMap->GenerateIDsForActions() || fixedUp; - return fixedUp; } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7a64793030a..ef4a422a20e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -145,6 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector Command::KeyMappings() const noexcept { + // todo: stage 3 - remove knowledge of keymappings return _keyMappings; } From dc874c3b3f03bbbe014e4eae2171107390f12177 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Sat, 27 Apr 2024 15:36:17 -0700 Subject: [PATCH 40/76] rename to special/standard --- .../ActionMapSerialization.cpp | 14 +++++++------- src/cascadia/TerminalSettingsModel/Command.cpp | 4 ++-- src/cascadia/TerminalSettingsModel/Command.h | 4 ++-- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 1cf1ecd88cc..d52d20381fa 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -99,18 +99,18 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" } // { "command": "copy", "keys": "ctrl+shift+c" } // { "command": "copy", "keys": "ctrl+ins" } - auto toJson = [&actionList](const Model::Command& cmd) { + auto toJsonSpecial = [&actionList](const Model::Command& cmd) { const auto cmdImpl{ winrt::get_self(cmd) }; - const auto& cmdJsonArray{ cmdImpl->ToJson() }; + const auto& cmdJsonArray{ cmdImpl->ToJsonSpecial() }; for (const auto& cmdJson : cmdJsonArray) { actionList.append(cmdJson); } }; - auto toJson2 = [&actionList](const Model::Command& cmd) { + auto toJsonStandard = [&actionList](const Model::Command& cmd) { const auto cmdImpl{ winrt::get_self(cmd) }; - const auto& cmdJsonArray{ cmdImpl->ToJson2() }; + const auto& cmdJsonArray{ cmdImpl->ToJsonStandard() }; for (const auto& cmdJson : cmdJsonArray) { actionList.append(cmdJson); @@ -119,19 +119,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation for (const auto& [_, cmd] : _ActionMap) { - toJson2(cmd); + toJsonStandard(cmd); } // Serialize all nested Command objects added in the current layer for (const auto& [_, cmd] : _NestedCommands) { - toJson(cmd); + toJsonSpecial(cmd); } // Serialize all iterable Command objects added in the current layer for (const auto& cmd : _IterableCommands) { - toJson(cmd); + toJsonSpecial(cmd); } return actionList; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index ef4a422a20e..7fb7662dc5f 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -439,7 +439,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - // Return Value: // - an array of serialized actions - Json::Value Command::ToJson() const + Json::Value Command::ToJsonSpecial() const { Json::Value cmdList{ Json::ValueType::arrayValue }; @@ -506,7 +506,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - // Return Value: // - an array of serialized actions - Json::Value Command::ToJson2() const + Json::Value Command::ToJsonStandard() const { Json::Value cmdList{ Json::ValueType::arrayValue }; diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 52589b8c71d..b7a164bf7d6 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -50,8 +50,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::vector LayerJson(Windows::Foundation::Collections::IMap& commands, const Json::Value& json, const OriginTag origin); - Json::Value ToJson() const; - Json::Value ToJson2() const; + Json::Value ToJsonSpecial() const; + Json::Value ToJsonStandard() const; bool HasNestedCommands() const; bool IsNestedCommand() const noexcept; From 936afd6b0190a55497e6410e009e45dab721cccd Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Sat, 27 Apr 2024 16:16:25 -0700 Subject: [PATCH 41/76] _getactionbyid no longer returns optional --- .../TerminalSettingsModel/ActionMap.cpp | 44 +++++++------------ .../TerminalSettingsModel/ActionMap.h | 2 +- .../ActionMapSerialization.cpp | 19 ++++---- .../TerminalSettingsModel/Command.cpp | 1 - 4 files changed, 26 insertions(+), 40 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 650da464ad1..d352741854f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -64,19 +64,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Retrieves the Command in the current layer, 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. + // - Retrieves the Command in the current layer // Arguments: // - actionID: the internal ID associated with a Command // Return Value: - // - 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. + // - The command if it exists in this layer, otherwise nullptr // todo: stage 3 - does this need to return an optional? - std::optional ActionMap::_GetActionByID(const winrt::hstring actionID) const + Model::Command ActionMap::_GetActionByID(const winrt::hstring actionID) const { // Check current layer const auto actionMapPair{ _ActionMap.find(actionID) }; @@ -87,14 +81,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // ActionMap should never point to nullptr FAIL_FAST_IF_NULL(cmd); - // todo: stage 3 - not sure if we need this? _ActionMap doesn't contain invalid commands I'm p sure - return !cmd.HasNestedCommands() && cmd.ActionAndArgs().Action() == ShortcutAction::Invalid ? - nullptr : // explicitly unbound - cmd; + return cmd; } // We don't have an answer - return std::nullopt; + return nullptr; } static void RegisterShortcutAction(ShortcutAction shortcutAction, std::unordered_map& list, std::unordered_set& visited) @@ -310,7 +301,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // Only populate GlobalHotkeys with actions whose // ShortcutAction is GlobalSummon or QuakeMode - if (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode) + if (cmd && (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode)) { globalHotkeys.emplace(keys, cmd); } @@ -341,10 +332,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Model::Command foundCommand{ nullptr }; const auto cmd{ _GetActionByID(actionID) }; - if (cmd.has_value()) + if (cmd) { // the keychord entry and the command with that ID exist in this layer - foundCommand = cmd.value(); + foundCommand = cmd; } else { @@ -352,9 +343,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation for (const auto parent : _parents) { const auto inheritedCmd{ parent->_GetActionByID(actionID) }; - if (inheritedCmd.has_value()) + if (inheritedCmd) { - foundCommand = inheritedCmd.value(); + foundCommand = inheritedCmd; break; } } @@ -382,9 +373,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (!actionID.empty() && keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) { const auto cmd{ _GetActionByID(actionID) }; - if (cmd.has_value()) + if (cmd) { - keyBindingsMap.emplace(keys, cmd.value()); + keyBindingsMap.emplace(keys, cmd); } } } @@ -603,7 +594,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto cmdID = keyIDPair->second; !cmdID.empty()) { - if (const auto cmd = _GetActionByID(cmdID); cmd.has_value()) + if (const auto cmd = _GetActionByID(cmdID)) { // standard case: both the keys and the ID are defined in this layer return cmd; @@ -612,7 +603,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { for (const auto parent : _parents) { - if (const auto inheritedCmd = parent->_GetActionByID(cmdID); inheritedCmd.has_value()) + if (const auto inheritedCmd = parent->_GetActionByID(cmdID)) { // edge case 1: the keys are bound to an ID in this layer, but the ID is defined in one of our parents return inheritedCmd; @@ -634,7 +625,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto cmdID = parentKeyIDPair->second; !cmdID.empty()) { - if (const auto cmd = _GetActionByID(cmdID); cmd.has_value()) + if (const auto cmd = _GetActionByID(cmdID)) { // edge case 2: the keychord maps to an ID in one of our parents, but a command with that ID exists in this layer // use the command from this layer @@ -666,9 +657,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // todo: stage 3 what if the user makes an action that does the same thing and edits the keys? // Check our internal state. - if (const auto& cmd{ _GetActionByID(cmdID) }) + if (const auto cmd{ _GetActionByID(cmdID) }) { - return cmd->Keys(); + return cmd.Keys(); } // Check our parents @@ -714,7 +705,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // make sure to update the Command with these changes - // todo: stage 3 - remove command's knowledge of keys const auto cmdImpl{ get_self(cmd) }; cmdImpl->EraseKey(oldKeys); cmdImpl->RegisterKey(newKeys); diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 51283a5e3f3..ecaee3253d1 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -83,7 +83,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IVector FilterToSendInput(winrt::hstring currentCommandline); private: - std::optional _GetActionByID(const winrt::hstring actionID) const; + Model::Command _GetActionByID(const winrt::hstring actionID) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index d52d20381fa..8c4366ce441 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -184,18 +184,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if there is an id, make sure the command registers these keys if (!idJson.empty()) { - // todo: stage 3 remove this! - // there is a problem here + // there is a problem here (make a GH issue and mark the todo here) // if the command with this id is only going to appear later during settings load // then this will return null, meaning that the command created later on will not register this keybinding // the keybinding will still work fine within the app, its just that the Command object itself won't know about this keymapping - // if we move away from Command needing to know its keymappings this is fine - // if we want to stick with commands knowing their keymappings, we will need to store these IDs of commands that we didn't - // register keybindings for and get back to them after parsing is complete - const auto& cmd{ _GetActionByID(idJson) }; - if (cmd && *cmd) + // we are going to move away from Command needing to know its keymappings in a followup + const auto cmd{ _GetActionByID(idJson) }; + if (cmd) { - cmd->RegisterKey(keys); + cmd.RegisterKey(keys); } else { @@ -204,10 +201,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // the parents only get added after all jsons have been parsed for (const auto& parent : _parents) { - const auto& inheritedCmd{ parent->_GetActionByID(idJson) }; - if (inheritedCmd && *inheritedCmd) + const auto inheritedCmd{ parent->_GetActionByID(idJson) }; + if (inheritedCmd) { - inheritedCmd->RegisterKey(keys); + inheritedCmd.RegisterKey(keys); } } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7fb7662dc5f..5a1d4ad2313 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -145,7 +145,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector Command::KeyMappings() const noexcept { - // todo: stage 3 - remove knowledge of keymappings return _keyMappings; } From b3e9c267f5ed66b4dc49506c3ac4b3c3a676433a Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Sat, 27 Apr 2024 16:22:14 -0700 Subject: [PATCH 42/76] remove check for invalid --- .../TerminalSettingsModel/ActionMap.cpp | 51 ++++++++----------- 1 file changed, 21 insertions(+), 30 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index d352741854f..a2a2eb79f0d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -69,7 +69,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - actionID: the internal ID associated with a Command // Return Value: // - The command if it exists in this layer, otherwise nullptr - // todo: stage 3 - does this need to return an optional? Model::Command ActionMap::_GetActionByID(const winrt::hstring actionID) const { // Check current layer @@ -132,24 +131,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Update AvailableActions and visitedActionIDs with our current layer for (const auto& [_, cmd] : _ActionMap) { - // todo: stage 3 - not sure if we need this? _ActionMap2 doesn't contain invalid commands anymore I'm p sure - if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + // Only populate AvailableActions with actions that haven't been visited already. + const auto actionID = Hash(cmd.ActionAndArgs()); + if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { - // Only populate AvailableActions with actions that haven't been visited already. - const auto actionID = Hash(cmd.ActionAndArgs()); - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + const auto& name{ cmd.Name() }; + if (!name.empty()) { - const auto& name{ cmd.Name() }; - if (!name.empty()) - { - // Update AvailableActions. - const auto actionAndArgsImpl{ get_self(cmd.ActionAndArgs()) }; - availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy()); - } - - // Record that we already handled adding this action to the NameMap. - visitedActionIDs.insert(actionID); + // Update AvailableActions. + const auto actionAndArgsImpl{ get_self(cmd.ActionAndArgs()) }; + availableActions.insert_or_assign(name, *actionAndArgsImpl->Copy()); } + + // Record that we already handled adding this action to the NameMap. + visitedActionIDs.insert(actionID); } } @@ -227,23 +222,19 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_set visitedActionIDs; for (const auto& cmd : _GetCumulativeActions()) { - // only populate with valid commands - if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + // Only populate NameMap with actions that haven't been visited already. + const auto actionID{ cmd.ID() }; + if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) { - // Only populate NameMap with actions that haven't been visited already. - const auto actionID{ cmd.ID() }; - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + const auto& name{ cmd.Name() }; + if (!name.empty()) { - 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.emplace(actionID); + // Update NameMap. + nameMap.insert_or_assign(name, cmd); } + + // Record that we already handled adding this action to the NameMap. + visitedActionIDs.emplace(actionID); } } } From 5a1b8228332aff2fde6f394385d85d1f4379fa10 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 29 Apr 2024 21:33:33 -0700 Subject: [PATCH 43/76] reimplement populating all known keybindings --- .../TerminalSettingsModel/ActionMap.cpp | 279 +++++++----------- .../TerminalSettingsModel/ActionMap.h | 8 +- .../ActionMapSerialization.cpp | 32 +- 3 files changed, 130 insertions(+), 189 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index a2a2eb79f0d..9ed03f98051 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -83,6 +83,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cmd; } + for (const auto parent : _parents) + { + if (const auto inheritedCmd = parent->_GetActionByID(actionID)) + { + return inheritedCmd; + } + } + // We don't have an answer return nullptr; } @@ -107,9 +115,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - Retrieves a map of actions that can be bound to a key IMapView ActionMap::AvailableActions() { - // todo: stage 3 - can we update RegisterShortcutAction to use new IDs instead of InternalActionID? - // then we'll be able to update _PopulateAvailableActionsWithStandardCommands with new IDs too - // the problem is that we need IDs for every ShortcutAction, even if it is not in defaults/user settings if (!_AvailableActionsCache) { // populate _AvailableActionsCache @@ -164,11 +169,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (!_NameMapCache) { // populate _NameMapCache - std::unordered_map nameMap2{}; - _PopulateNameMapWithSpecialCommands(nameMap2); - _PopulateNameMapWithStandardCommands(nameMap2); + std::unordered_map nameMap{}; + _PopulateNameMapWithSpecialCommands(nameMap); + _PopulateNameMapWithStandardCommands(nameMap); - _NameMapCache = single_threaded_map(std::move(nameMap2)); + _NameMapCache = single_threaded_map(std::move(nameMap)); } return _NameMapCache.GetView(); } @@ -262,6 +267,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cumulativeActions; } + // Method Description: + // - Recursively populated keyBindingsMap with ours and our parents' key -> id pairs + void ActionMap::_PopulateCumulativeKeyMap(std::unordered_map& keyBindingsMap) + { + for (const auto& [keys, cmdID] : _KeyMap) + { + if (keyBindingsMap.find(keys) == keyBindingsMap.end()) + { + keyBindingsMap.emplace(keys, cmdID); + } + } + + for (const auto parent : _parents) + { + parent->_PopulateCumulativeKeyMap(keyBindingsMap); + } + } + + // Method Description: + // - Recursively populated actionMap with ours and our parents' id -> command pairs + void ActionMap::_PopulateCumulativeActionMap(std::unordered_map& actionMap) + { + for (const auto& [cmdID, cmd] : _ActionMap) + { + if (actionMap.find(cmdID) == actionMap.end()) + { + actionMap.emplace(cmdID, cmd); + } + } + + for (const auto parent : _parents) + { + parent->_PopulateCumulativeActionMap(actionMap); + } + } + IMapView ActionMap::GlobalHotkeys() { if (!_GlobalHotkeysCache) @@ -273,110 +314,42 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation IMapView ActionMap::KeyBindings() { - if (!_KeyBindingMapCache) + if (!_ResolvedKeyActionMapCache) { _RefreshKeyBindingCaches(); } - return _KeyBindingMapCache.GetView(); + return _ResolvedKeyActionMapCache.GetView(); } void ActionMap::_RefreshKeyBindingCaches() { - std::unordered_map keyBindingsMap; std::unordered_map globalHotkeys; - std::unordered_set unboundKeys; + std::unordered_map accumulatedKeybindingsMap; + std::unordered_map accumulatedActionsMap; + std::unordered_map resolvedKeyActionMap; - _PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys); + _PopulateCumulativeKeyMap(accumulatedKeybindingsMap); + _PopulateCumulativeActionMap(accumulatedActionsMap); - for (const auto& [keys, cmd] : keyBindingsMap) + for (const auto [keys, cmdID] : accumulatedKeybindingsMap) { - // Only populate GlobalHotkeys with actions whose - // ShortcutAction is GlobalSummon or QuakeMode - if (cmd && (cmd.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || cmd.ActionAndArgs().Action() == ShortcutAction::QuakeMode)) + if (const auto idCmdPair = accumulatedActionsMap.find(cmdID); idCmdPair != accumulatedActionsMap.end()) { - globalHotkeys.emplace(keys, cmd); - } - } - - _KeyBindingMapCache = single_threaded_map(std::move(keyBindingsMap)); - _GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys)); - } + resolvedKeyActionMap.emplace(keys, idCmdPair->second); - // Method Description: - // - Populates the provided keyBindingsMap with all of our actions and our parents actions - // while omitting the key bindings that were already added before. - // - This needs to be a bottom up approach to ensure that we only add each key chord once. - // Arguments: - // - keyBindingsMap: the keyBindingsMap we're populating. This maps the key chord of a command to the command itself. - // - unboundKeys: a set of keys that are explicitly unbound - void ActionMap::_PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const - { - // todo: stage 3 - can we just use _GetActionByKeyChordInternal here? - // Update KeyBindingsMap with our current layer - for (const auto& [keys, actionID] : _KeyMap) - { - // if the actionID is empty, this keychord is explicitly unbound - if (!actionID.empty()) - { - // make sure we haven't visited this key chord before - if (keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) + // Only populate GlobalHotkeys with actions whose + // ShortcutAction is GlobalSummon or QuakeMode + if (idCmdPair->second.ActionAndArgs().Action() == ShortcutAction::GlobalSummon || idCmdPair->second.ActionAndArgs().Action() == ShortcutAction::QuakeMode) { - Model::Command foundCommand{ nullptr }; - const auto cmd{ _GetActionByID(actionID) }; - if (cmd) - { - // the keychord entry and the command with that ID exist in this layer - foundCommand = cmd; - } - else - { - // we have the keychord entry in this layer, but the command with that ID exists in a different layer - for (const auto parent : _parents) - { - const auto inheritedCmd{ parent->_GetActionByID(actionID) }; - if (inheritedCmd) - { - foundCommand = inheritedCmd; - break; - } - } - } - - if (foundCommand) - { - keyBindingsMap.emplace(keys, foundCommand); - } + globalHotkeys.emplace(keys, idCmdPair->second); } } - else - { - // actionID is empty, meaning this keychord is explicitly unbound - record it - unboundKeys.emplace(keys); - } } - // similar to _GetActionByKeyChordInternal, we have to check the case where the keychord is in the parent layer, - // but the ID has been redefined in this layer - for (const auto& parent : _parents) - { - for (const auto& [keys, actionID] : parent->_KeyMap) - { - if (!actionID.empty() && keyBindingsMap.find(keys) == keyBindingsMap.end() && unboundKeys.find(keys) == unboundKeys.end()) - { - const auto cmd{ _GetActionByID(actionID) }; - if (cmd) - { - keyBindingsMap.emplace(keys, cmd); - } - } - } - } - - // now we can recurse - for (const auto& parent : _parents) - { - parent->_PopulateKeyBindingMapWithStandardCommands(keyBindingsMap, unboundKeys); - } + _CumulativeKeyMapCache = single_threaded_map(std::move(accumulatedKeybindingsMap)); + _CumulativeActionMapCache = single_threaded_map(std::move(accumulatedActionsMap)); + _ResolvedKeyActionMapCache = single_threaded_map(std::move(resolvedKeyActionMap)); + _GlobalHotkeysCache = single_threaded_map(std::move(globalHotkeys)); } com_ptr ActionMap::Copy() const @@ -430,7 +403,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // invalidate caches _NameMapCache = nullptr; _GlobalHotkeysCache = nullptr; - _KeyBindingMapCache = nullptr; + _CumulativeKeyMapCache = nullptr; + _CumulativeActionMapCache = nullptr; + _ResolvedKeyActionMapCache = nullptr; // Handle nested commands const auto cmdImpl{ get_self(cmd) }; @@ -453,20 +428,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // General Case: - // Add the new command to the KeyMap. - // This map directs you to an entry in the ActionMap. - - // todo: stage 3 - check this comment - // 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. - // 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. + // Add the new command to the _ActionMap + // Add the new keybinding to the _KeyMap _TryUpdateActionMap(cmd); _TryUpdateKeyChord(cmd); @@ -507,21 +470,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Handle collisions - const auto oldKeyPair{ _KeyMap.find(keys) }; - if (oldKeyPair != _KeyMap.end()) + if (const auto foundCommand = _GetActionByKeyChordInternal(keys); foundCommand && *foundCommand) { - // collision: the key chord was already in use in this layer - - // remove the old one + // collision: the key chord is bound to some command, make sure that command erases + // this key chord as we are about to overwrite it - // if oldKeyPair->second is empty, that means this keychord was unbound earlier in this layer and is now being rebound - // no collision logic needed - we will simply reassign it in the _KeyMap - if (!oldKeyPair->second.empty()) - { - const auto actionPair{ _ActionMap.find(oldKeyPair->second) }; - const auto conflictingCmd{ actionPair->second }; - const auto conflictingCmdImpl{ get_self(conflictingCmd) }; - conflictingCmdImpl->EraseKey(keys); - } + const auto foundCommandImpl{ get_self(*foundCommand) }; + foundCommandImpl->EraseKey(keys); } // Assign the new action in the _KeyMap @@ -570,70 +525,69 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Retrieves the assigned command with the given key chord. + // - Retrieves the assigned command ID 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: - // - 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(const Control::KeyChord& keys) const + // - the command ID with the given key chord + // - an empty string if the key chord is explicitly unbound + // - nullopt if it is not bound + std::optional ActionMap::_GetActionIDByKeyChordInternal(const Control::KeyChord& keys) const { - // todo: stage 3 - why does this function search through parents but _GetActionByID doesn't? even the original implementation was similar with some recursive and some non-recursive functions if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end()) { if (const auto cmdID = keyIDPair->second; !cmdID.empty()) { - if (const auto cmd = _GetActionByID(cmdID)) - { - // standard case: both the keys and the ID are defined in this layer - return cmd; - } - else - { - for (const auto parent : _parents) - { - if (const auto inheritedCmd = parent->_GetActionByID(cmdID)) - { - // edge case 1: the keys are bound to an ID in this layer, but the ID is defined in one of our parents - return inheritedCmd; - } - } - } + return cmdID; } else { // the keychord is defined in this layer, but points to an empty string - explicitly unbound - return nullptr; + return L""; } } // search through our parents for (const auto& parent : _parents) { - if (const auto parentKeyIDPair = parent->_KeyMap.find(keys); parentKeyIDPair != parent->_KeyMap.end()) + if (const auto foundCmdID = parent->_GetActionIDByKeyChordInternal(keys)) + { + return foundCmdID; + } + } + + // we did not find the keychord anywhere, its not bound and not explicity unbound either + return std::nullopt; + } + + // Method Description: + // - 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: + // - the command with the given key chord + // - nullptr if the key chord is explicitly unbound + // - nullopt if it is not bound + std::optional ActionMap::_GetActionByKeyChordInternal(const Control::KeyChord& keys) const + { + if (const auto actionIDOptional = _GetActionIDByKeyChordInternal(keys)) + { + if (!(*actionIDOptional).empty()) { - if (const auto cmdID = parentKeyIDPair->second; !cmdID.empty()) + // there is an ID associated with these keys, find the command + if (const auto foundCmd = _GetActionByID(*actionIDOptional)) { - if (const auto cmd = _GetActionByID(cmdID)) - { - // edge case 2: the keychord maps to an ID in one of our parents, but a command with that ID exists in this layer - // use the command from this layer - return cmd; - } + return foundCmd; } } - - // we've checked for the standard case and the 2 edge cases, now we can recurse - const auto& inheritedCmd{ parent->_GetActionByKeyChordInternal(keys) }; - if (inheritedCmd) { - return *inheritedCmd; + // the ID is an empty string, these keys are explicitly unbound + return nullptr; } } - // we did not find the keychord anywhere, its not bound and not explicity unbound either return std::nullopt; } @@ -653,15 +607,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cmd.Keys(); } - // Check our parents - for (const auto& parent : _parents) - { - if (const auto& keys{ parent->GetKeyBindingForAction(cmdID) }) - { - return keys; - } - } - // This key binding does not exist return nullptr; } diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index ecaee3253d1..41ed1a2615c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -84,15 +84,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: Model::Command _GetActionByID(const winrt::hstring actionID) const; + std::optional _GetActionIDByKeyChordInternal(const Control::KeyChord& keys) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); void _PopulateAvailableActionsWithStandardCommands(std::unordered_map& availableActions, std::unordered_set& visitedActionIDs) const; void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; - void _PopulateKeyBindingMapWithStandardCommands(std::unordered_map& keyBindingsMap, std::unordered_set& unboundKeys) const; std::vector _GetCumulativeActions() const noexcept; + void _PopulateCumulativeKeyMap(std::unordered_map& keyBindingsMap); + void _PopulateCumulativeActionMap(std::unordered_map& actionMap); void _TryUpdateActionMap(const Model::Command& cmd); void _TryUpdateKeyChord(const Model::Command& cmd); @@ -100,7 +102,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Windows::Foundation::Collections::IMap _AvailableActionsCache{ nullptr }; Windows::Foundation::Collections::IMap _NameMapCache{ nullptr }; Windows::Foundation::Collections::IMap _GlobalHotkeysCache{ nullptr }; - Windows::Foundation::Collections::IMap _KeyBindingMapCache{ nullptr }; Windows::Foundation::Collections::IVector _ExpandedCommandsCache{ nullptr }; @@ -112,6 +113,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); std::unordered_map _KeyMap; std::unordered_map _ActionMap; + Windows::Foundation::Collections::IMap _CumulativeKeyMapCache{ nullptr }; + Windows::Foundation::Collections::IMap _CumulativeActionMapCache{ nullptr }; + Windows::Foundation::Collections::IMap _ResolvedKeyActionMapCache{ nullptr }; friend class SettingsModelUnitTests::KeyBindingsTests; friend class SettingsModelUnitTests::DeserializationTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 8c4366ce441..55a1fde4094 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -73,7 +73,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // for non-nested non-iterable user commands, if there's no ID we generate one for them // let the loader know that fixups are needed - // todo: stage 3 - there has to be a better way to check this? if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey("id")) && jsonBlock.isMember(JsonKey("command")) && !jsonBlock.isMember(JsonKey("iterateOn"))) { _fixUpsAppliedDuringLoad = true; @@ -175,39 +174,32 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::hstring idJson; if (JsonUtils::GetValueForKey(json, "keys", keys)) { + // if these keys are already bound to some command, + // we need to update that command to erase these keys as we are about to overwrite them + if (const auto foundCommand = _GetActionByKeyChordInternal(keys); foundCommand && *foundCommand) + { + const auto foundCommandImpl{ get_self(*foundCommand) }; + foundCommandImpl->EraseKey(keys); + } + // if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine JsonUtils::GetValueForKey(json, "id", idJson); // any existing keybinding with the same keychord in this layer will get overwritten _KeyMap.insert_or_assign(keys, idJson); - // if there is an id, make sure the command registers these keys + // make sure the command registers these keys if (!idJson.empty()) { - // there is a problem here (make a GH issue and mark the todo here) + // there is a problem here (stage 3 - make a GH issue and mark the todo here) // if the command with this id is only going to appear later during settings load // then this will return null, meaning that the command created later on will not register this keybinding // the keybinding will still work fine within the app, its just that the Command object itself won't know about this keymapping - // we are going to move away from Command needing to know its keymappings in a followup - const auto cmd{ _GetActionByID(idJson) }; - if (cmd) + // we are going to move away from Command needing to know its keymappings in a followup, so this shouldn't matter for very long + if (const auto cmd = _GetActionByID(idJson)) { cmd.RegisterKey(keys); } - else - { - // check for the same ID among our parents - // with the current loader, I don't think we ever get here because we never have parents while parsing the json - // the parents only get added after all jsons have been parsed - for (const auto& parent : _parents) - { - const auto inheritedCmd{ parent->_GetActionByID(idJson) }; - if (inheritedCmd) - { - inheritedCmd.RegisterKey(keys); - } - } - } } } } From c51558ff4ce20fefb0742dd36ddaeadf9cda39c4 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 10:20:07 -0700 Subject: [PATCH 44/76] unmark these --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 1 - src/cascadia/TerminalSettingsModel/Command.cpp | 5 ----- 2 files changed, 6 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 9ed03f98051..d5387e5693c 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -600,7 +600,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullptr if the action is not bound to a key chord Control::KeyChord ActionMap::GetKeyBindingForAction(winrt::hstring cmdID) const { - // todo: stage 3 what if the user makes an action that does the same thing and edits the keys? // Check our internal state. if (const auto cmd{ _GetActionByID(cmdID) }) { diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 5a1d4ad2313..99946d5f413 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -332,10 +332,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if this is a user-defined, non-iterable, valid command and they did not provide an id, generate one for them if (result->_ID.empty() && result->_IterateOn == ExpandCommandType::None && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) { - // todo: stage 3 - // we reach this point for 'unpacked' nested commands, which means we generate IDs for them - // these IDs aren't used anywhere or written to the json, which is intentional, but we should - // figure out a way to not generate them at all result->GenerateID(); } } @@ -422,7 +418,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation else { // Override commands with the same name - // todo: stage 3 - we may not need to do this anymore commands.Insert(result->Name(), *result); } } From 754bf04ab34531d1a2004bc603c16e2cff4ace90 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 10:27:25 -0700 Subject: [PATCH 45/76] mark gh todo --- src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp | 2 +- src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 55a1fde4094..215695a5c39 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -191,7 +191,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // make sure the command registers these keys if (!idJson.empty()) { - // there is a problem here (stage 3 - make a GH issue and mark the todo here) + // TODO GH#17160 // if the command with this id is only going to appear later during settings load // then this will return null, meaning that the command created later on will not register this keybinding // the keybinding will still work fine within the app, its just that the Command object itself won't know about this keymapping diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 8ea3f46e627..70e1e3a758d 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -264,7 +264,6 @@ Json::Value GlobalAppSettings::ToJson() json[JsonKey(ActionsKey)] = _actionMap->ToJson(); json[JsonKey(LegacyKeybindingsKey)] = _actionMap->KeyBindingsToJson(); - // todo: stage 3 - fix tests, they're surely broke with this return json; } From 2f1d8d2dca71af0cc50a2d48f3a352717a971412 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 10:34:58 -0700 Subject: [PATCH 46/76] update defaults --- .../TerminalSettingsModel/defaults.json | 215 ++++++++++++------ 1 file changed, 148 insertions(+), 67 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 2c79c63b7c8..ca60a8501a0 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -422,28 +422,28 @@ ], "actions": [ - // Application-level Keys - { "command": "closeWindow", "keys": "alt+f4", "id": "Terminal.CloseWindow" }, - { "command": "toggleFullscreen", "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" }, - { "command": "toggleFullscreen", "keys": "f11", "id": "Terminal.ToggleFullscreen" }, + // Application-level Commands + { "command": "closeWindow", "id": "Terminal.CloseWindow" }, + { "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" }, + { "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" }, { "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" }, { "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" }, - { "command": "openNewTabDropdown", "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" }, - { "command": { "action": "openSettings", "target": "settingsUI" }, "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, - { "command": { "action": "openSettings", "target": "settingsFile" }, "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, - { "command": { "action": "openSettings", "target": "defaultsFile" }, "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, - { "command": "find", "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, + { "command": "openNewTabDropdown", "id": "Terminal.OpenNewTabDropdown" }, + { "command": { "action": "openSettings", "target": "settingsUI" }, "id": "Terminal.OpenSettingsUI" }, + { "command": { "action": "openSettings", "target": "settingsFile" }, "id": "Terminal.OpenSettingsFile" }, + { "command": { "action": "openSettings", "target": "defaultsFile" }, "id": "Terminal.OpenDefaultSettingsFile" }, + { "command": "find", "id": "Terminal.FindText" }, { "command": { "action": "findMatch", "direction": "next" }, "id": "Terminal.FindNextMatch" }, { "command": { "action": "findMatch", "direction": "prev" }, "id": "Terminal.FindPrevMatch" }, { "command": "toggleShaderEffects", "id": "Terminal.ToggleShaderEffects" }, { "command": "openTabColorPicker", "id": "Terminal.OpenTabColorPicker" }, { "command": "renameTab", "id": "Terminal.RenameTab" }, { "command": "openTabRenamer", "id": "Terminal.OpenTabRenamer" }, - { "command": "commandPalette", "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, + { "command": "commandPalette", "id": "Terminal.ToggleCommandPalette" }, { "command": "identifyWindow", "id": "Terminal.IdentifyWindow" }, { "command": "openWindowRenamer", "id": "Terminal.OpenWindowRenamer" }, - { "command": "quakeMode", "keys":"win+sc(41)", "id": "Terminal.QuakeMode" }, - { "command": "openSystemMenu", "keys": "alt+space", "id": "Terminal.OpenSystemMenu" }, + { "command": "quakeMode", "id": "Terminal.QuakeMode" }, + { "command": "openSystemMenu", "id": "Terminal.OpenSystemMenu" }, { "command": "quit", "id": "Terminal.Quit" }, { "command": "restoreLastClosed", "id": "Terminal.RestoreLastClosed" }, { "command": "openAbout", "id": "Terminal.OpenAboutDialog" }, @@ -455,49 +455,49 @@ { "command": "closeTabsAfter", "id": "Terminal.CloseTabsAfter" }, { "command": { "action" : "moveTab", "direction": "forward" }, "id": "Terminal.MoveTabForward" }, { "command": { "action" : "moveTab", "direction": "backward" }, "id": "Terminal.MoveTabBackward" }, - { "command": "newTab", "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" }, - { "command": "newWindow", "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" }, - { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" }, - { "command": { "action": "newTab", "index": 1 }, "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" }, - { "command": { "action": "newTab", "index": 2 }, "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" }, - { "command": { "action": "newTab", "index": 3 }, "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" }, - { "command": { "action": "newTab", "index": 4 }, "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" }, - { "command": { "action": "newTab", "index": 5 }, "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" }, - { "command": { "action": "newTab", "index": 6 }, "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" }, - { "command": { "action": "newTab", "index": 7 }, "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" }, - { "command": { "action": "newTab", "index": 8 }, "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" }, - { "command": "duplicateTab", "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" }, - { "command": "nextTab", "keys": "ctrl+tab", "id": "Terminal.NextTab" }, - { "command": "prevTab", "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" }, - { "command": { "action": "switchToTab", "index": 0 }, "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" }, - { "command": { "action": "switchToTab", "index": 1 }, "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" }, - { "command": { "action": "switchToTab", "index": 2 }, "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" }, - { "command": { "action": "switchToTab", "index": 3 }, "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" }, - { "command": { "action": "switchToTab", "index": 4 }, "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" }, - { "command": { "action": "switchToTab", "index": 5 }, "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" }, - { "command": { "action": "switchToTab", "index": 6 }, "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" }, - { "command": { "action": "switchToTab", "index": 7 }, "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" }, - { "command": { "action": "switchToTab", "index": 4294967295 }, "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" }, + { "command": "newTab", "id": "Terminal.OpenNewTab" }, + { "command": "newWindow", "id": "Terminal.OpenNewWindow" }, + { "command": { "action": "newTab", "index": 0 }, "id": "Terminal.OpenNewTabProfile0" }, + { "command": { "action": "newTab", "index": 1 }, "id": "Terminal.OpenNewTabProfile1" }, + { "command": { "action": "newTab", "index": 2 }, "id": "Terminal.OpenNewTabProfile2" }, + { "command": { "action": "newTab", "index": 3 }, "id": "Terminal.OpenNewTabProfile3" }, + { "command": { "action": "newTab", "index": 4 }, "id": "Terminal.OpenNewTabProfile4" }, + { "command": { "action": "newTab", "index": 5 }, "id": "Terminal.OpenNewTabProfile5" }, + { "command": { "action": "newTab", "index": 6 }, "id": "Terminal.OpenNewTabProfile6" }, + { "command": { "action": "newTab", "index": 7 }, "id": "Terminal.OpenNewTabProfile7" }, + { "command": { "action": "newTab", "index": 8 }, "id": "Terminal.OpenNewTabProfile8" }, + { "command": "duplicateTab", "id": "Terminal.DuplicateTab" }, + { "command": "nextTab", "id": "Terminal.NextTab" }, + { "command": "prevTab", "id": "Terminal.PrevTab" }, + { "command": { "action": "switchToTab", "index": 0 }, "id": "Terminal.SwitchToTab0" }, + { "command": { "action": "switchToTab", "index": 1 }, "id": "Terminal.SwitchToTab1" }, + { "command": { "action": "switchToTab", "index": 2 }, "id": "Terminal.SwitchToTab2" }, + { "command": { "action": "switchToTab", "index": 3 }, "id": "Terminal.SwitchToTab3" }, + { "command": { "action": "switchToTab", "index": 4 }, "id": "Terminal.SwitchToTab4" }, + { "command": { "action": "switchToTab", "index": 5 }, "id": "Terminal.SwitchToTab5" }, + { "command": { "action": "switchToTab", "index": 6 }, "id": "Terminal.SwitchToTab6" }, + { "command": { "action": "switchToTab", "index": 7 }, "id": "Terminal.SwitchToTab7" }, + { "command": { "action": "switchToTab", "index": 4294967295 }, "id": "Terminal.SwitchToLastTab" }, { "command": { "action": "moveTab", "window": "new" }, "id": "Terminal.MoveTabToNewWindow" }, // Pane Management { "command": "closeOtherPanes", "id": "Terminal.CloseOtherPanes" }, - { "command": "closePane", "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, + { "command": "closePane", "id": "Terminal.ClosePane" }, { "command": { "action": "splitPane", "split": "up" }, "id": "Terminal.SplitPaneUp" }, { "command": { "action": "splitPane", "split": "down" }, "id": "Terminal.SplitPaneDown" }, { "command": { "action": "splitPane", "split": "left" }, "id": "Terminal.SplitPaneLeft" }, { "command": { "action": "splitPane", "split": "right" }, "id": "Terminal.SplitPaneRight" }, - { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "keys": "alt+shift+-", "id": "Terminal.SplitPaneDuplicateDown" }, - { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "keys": "alt+shift+plus", "id": "Terminal.SplitPaneDuplicateRight" }, - { "command": { "action": "resizePane", "direction": "down" }, "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, - { "command": { "action": "resizePane", "direction": "left" }, "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, - { "command": { "action": "resizePane", "direction": "right" }, "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, - { "command": { "action": "resizePane", "direction": "up" }, "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" }, - { "command": { "action": "moveFocus", "direction": "down" }, "keys": "alt+down", "id": "Terminal.MoveFocusDown" }, - { "command": { "action": "moveFocus", "direction": "left" }, "keys": "alt+left", "id": "Terminal.MoveFocusLeft" }, - { "command": { "action": "moveFocus", "direction": "right" }, "keys": "alt+right", "id": "Terminal.MoveFocusRight" }, - { "command": { "action": "moveFocus", "direction": "up" }, "keys": "alt+up", "id": "Terminal.MoveFocusUp" }, - { "command": { "action": "moveFocus", "direction": "previous" }, "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "id": "Terminal.SplitPaneDuplicateDown" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "id": "Terminal.SplitPaneDuplicateRight" }, + { "command": { "action": "resizePane", "direction": "down" }, "id": "Terminal.ResizePaneDown" }, + { "command": { "action": "resizePane", "direction": "left" }, "id": "Terminal.ResizePaneLeft" }, + { "command": { "action": "resizePane", "direction": "right" }, "id": "Terminal.ResizePaneRight" }, + { "command": { "action": "resizePane", "direction": "up" }, "id": "Terminal.ResizePaneUp" }, + { "command": { "action": "moveFocus", "direction": "down" }, "id": "Terminal.MoveFocusDown" }, + { "command": { "action": "moveFocus", "direction": "left" }, "id": "Terminal.MoveFocusLeft" }, + { "command": { "action": "moveFocus", "direction": "right" }, "id": "Terminal.MoveFocusRight" }, + { "command": { "action": "moveFocus", "direction": "up" }, "id": "Terminal.MoveFocusUp" }, + { "command": { "action": "moveFocus", "direction": "previous" }, "id": "Terminal.MoveFocusPrevious" }, { "command": { "action": "moveFocus", "direction": "previousInOrder" }, "id": "Terminal.MoveFocusPreviousInOrder" }, { "command": { "action": "moveFocus", "direction": "nextInOrder" }, "id": "Terminal.MoveFocusNextInOrder" }, { "command": { "action": "moveFocus", "direction": "first" }, "id": "Terminal.MoveFocusFirst" }, @@ -530,38 +530,38 @@ { "command": "restartConnection", "id": "Terminal.RestartConnection" }, // Clipboard Integration - { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" }, - { "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" }, - { "command": { "action": "copy", "singleLine": false }, "keys": "enter", "id": "Terminal.CopySelectedText" }, - { "command": "paste", "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" }, - { "command": "paste", "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" }, - { "command": "selectAll", "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" }, - { "command": "markMode", "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" }, + { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, + { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, + { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, + { "command": "paste", "id": "Terminal.PasteFromClipboard" }, + { "command": "paste", "id": "Terminal.PasteFromClipboard" }, + { "command": "selectAll", "id": "Terminal.SelectAll" }, + { "command": "markMode", "id": "Terminal.ToggleMarkMode" }, { "command": "toggleBlockSelection", "id": "Terminal.ToggleBlockSelection" }, { "command": "switchSelectionEndpoint", "id": "Terminal.SwitchSelectionEndpoint" }, { "command": "expandSelectionToWord", "id": "Terminal.ExpandSelectionToWord" }, - { "command": "showContextMenu", "keys": "menu", "id": "Terminal.ShowContextMenu" }, + { "command": "showContextMenu", "id": "Terminal.ShowContextMenu" }, // Web Search { "command": { "action": "searchWeb" }, "name": { "key": "SearchWebCommandKey" }, "id": "Terminal.SearchWeb" }, // Scrollback - { "command": "scrollDown", "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" }, - { "command": "scrollDownPage", "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" }, - { "command": "scrollUp", "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" }, - { "command": "scrollUpPage", "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, - { "command": "scrollToTop", "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, - { "command": "scrollToBottom", "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, + { "command": "scrollDown", "id": "Terminal.ScrollDown" }, + { "command": "scrollDownPage", "id": "Terminal.ScrollDownPage" }, + { "command": "scrollUp", "id": "Terminal.ScrollUp" }, + { "command": "scrollUpPage", "id": "Terminal.ScrollUpPage" }, + { "command": "scrollToTop", "id": "Terminal.ScrollToTop" }, + { "command": "scrollToBottom", "id": "Terminal.ScrollToBottom" }, { "command": { "action": "clearBuffer", "clear": "all" }, "id": "Terminal.ClearBuffer" }, { "command": "exportBuffer", "id": "Terminal.ExportBuffer" }, // Visual Adjustments - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, - { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" }, - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" }, - { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" }, - { "command": "resetFontSize", "keys": "ctrl+0", "id": "Terminal.ResetFontSize" }, - { "command": "resetFontSize", "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" }, + { "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Terminal.IncreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Terminal.DecreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Terminal.IncreaseFontSize" }, + { "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Terminal.DecreaseFontSize" }, + { "command": "resetFontSize", "id": "Terminal.ResetFontSize" }, + { "command": "resetFontSize", "id": "Terminal.ResetFontSize" }, // Other commands { @@ -626,5 +626,86 @@ { "command": { "action": "adjustOpacity", "opacity": 100, "relative": false } } ] } + ], + "keybindings": [ + // Application-level Keys + { "keys": "alt+f4", "id": "Terminal.CloseWindow" }, + { "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" }, + { "keys": "f11", "id": "Terminal.ToggleFullscreen" }, + { "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" }, + { "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, + { "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, + { "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, + { "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, + { "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, + { "keys":"win+sc(41)", "id": "Terminal.QuakeMode" }, + { "keys": "alt+space", "id": "Terminal.OpenSystemMenu" }, + + // Tab Management + // "command": "closeTab" is unbound by default. + // The closeTab command closes a tab without confirmation, even if it has multiple panes. + { "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" }, + { "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" }, + { "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" }, + { "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" }, + { "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" }, + { "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" }, + { "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" }, + { "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" }, + { "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" }, + { "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" }, + { "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" }, + { "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" }, + { "keys": "ctrl+tab", "id": "Terminal.NextTab" }, + { "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" }, + { "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" }, + { "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" }, + { "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" }, + { "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" }, + { "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" }, + { "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" }, + { "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" }, + { "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" }, + { "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" }, + + // Pane Management + { "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, + { "keys": "alt+shift+-", "id": "Terminal.SplitPaneDuplicateDown" }, + { "keys": "alt+shift+plus", "id": "Terminal.SplitPaneDuplicateRight" }, + { "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, + { "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, + { "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, + { "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" }, + { "keys": "alt+down", "id": "Terminal.MoveFocusDown" }, + { "keys": "alt+left", "id": "Terminal.MoveFocusLeft" }, + { "keys": "alt+right", "id": "Terminal.MoveFocusRight" }, + { "keys": "alt+up", "id": "Terminal.MoveFocusUp" }, + { "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" }, + + // Clipboard Integration + { "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" }, + { "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" }, + { "keys": "enter", "id": "Terminal.CopySelectedText" }, + { "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" }, + { "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" }, + { "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" }, + { "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" }, + { "keys": "menu", "id": "Terminal.ShowContextMenu" }, + + // Scrollback + { "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" }, + { "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" }, + { "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" }, + { "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, + { "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, + { "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, + + // Visual Adjustments + { "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, + { "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" }, + { "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" }, + { "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" }, + { "keys": "ctrl+0", "id": "Terminal.ResetFontSize" }, + { "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" }, ] } From 2b4aeb2b11c83b07aa312250b803598e85b3e938 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 11:04:01 -0700 Subject: [PATCH 47/76] don't check for special in standard --- .../TerminalSettingsModel/ActionMap.cpp | 4 ++- .../TerminalSettingsModel/Command.cpp | 31 ++++++------------- 2 files changed, 13 insertions(+), 22 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index d5387e5693c..48e1a61b84d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -64,7 +64,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Retrieves the Command in the current layer + // - Retrieves the Command referred to be the given ID + // - Will recurse through parents if we don't find it in this layer // Arguments: // - actionID: the internal ID associated with a Command // Return Value: @@ -126,6 +127,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation #define ON_ALL_ACTIONS(action) RegisterShortcutAction(ShortcutAction::action, availableActions, visitedActionIDs); ALL_SHORTCUT_ACTIONS #undef ON_ALL_ACTIONS + _AvailableActionsCache = single_threaded_map(std::move(availableActions)); } return _AvailableActionsCache.GetView(); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 99946d5f413..0c565498d9d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -504,32 +504,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Json::Value cmdList{ Json::ValueType::arrayValue }; - if (_nestedCommand || _IterateOn != ExpandCommandType::None) + Json::Value cmdJson{ Json::ValueType::objectValue }; + JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); + JsonUtils::SetValueForKey(cmdJson, NameKey, _name); + if (!_ID.empty()) { - // handle special commands - // For these, we can trust _originalJson to be correct. - // In fact, we _need_ to use it here because we don't actually deserialize `iterateOn` - // until we expand the command. - cmdList.append(_originalJson); + JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); } - else - { - Json::Value cmdJson{ Json::ValueType::objectValue }; - JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); - JsonUtils::SetValueForKey(cmdJson, NameKey, _name); - if (!_ID.empty()) - { - JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); - } - - if (_ActionAndArgs) - { - cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); - } - cmdList.append(cmdJson); + if (_ActionAndArgs) + { + cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); } + cmdList.append(cmdJson); + return cmdList; } From e62dfa21770dae6c2286bd2bd3e831b104cbad7c Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 11:09:15 -0700 Subject: [PATCH 48/76] some comments --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 6 ++++-- src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 48e1a61b84d..93199cc65be 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -270,7 +270,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Recursively populated keyBindingsMap with ours and our parents' key -> id pairs + // - Recursively populate keyBindingsMap with ours and our parents' key -> id pairs + // - This is a bottom-up approach, ensuring that the keybindings of the parents are overridden by the children void ActionMap::_PopulateCumulativeKeyMap(std::unordered_map& keyBindingsMap) { for (const auto& [keys, cmdID] : _KeyMap) @@ -288,7 +289,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Method Description: - // - Recursively populated actionMap with ours and our parents' id -> command pairs + // - Recursively populate actionMap with ours and our parents' id -> command pairs + // - This is a bottom-up approach, ensuring that the actions of the parents are overridden by the children void ActionMap::_PopulateCumulativeActionMap(std::unordered_map& actionMap) { for (const auto& [cmdID, cmd] : _ActionMap) diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 70e1e3a758d..0d3a7d75c7b 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -155,7 +155,7 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { - // this order change is intentional, we want to do the keybindings map _after_ so that we have already + // we want to do the keybindings map after the actions map so that we have already // stored the action IDs in the action map // also, we want the keybindings map to overwrite any leftover keybindings that might have existed in the first pass, // in case the user did a partial update from legacy to modern From db00b90306948de50c51d57b2418924b4b2d5a74 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 11:14:21 -0700 Subject: [PATCH 49/76] spelling things --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 8 ++++---- src/cascadia/TerminalSettingsModel/ActionMap.h | 2 +- .../TerminalSettingsModel/ActionMapSerialization.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 93199cc65be..3d954b89184 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -488,7 +488,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // the user might have { "command": null, "id": "someID", "keys": "ctrl+c" } // i.e. they provided an ID for a null command (which they really shouldn't, there's no purpose) // in this case, we do _not_ want to use the id they provided, we want to use an empty id - // (empty id in the _KeyMap indicates the the keychord was explicitly unbound) + // (empty id in the _KeyMap indicates the keychord was explicitly unbound) if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) { _KeyMap.insert_or_assign(keys, L""); @@ -537,7 +537,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - the command ID with the given key chord // - an empty string if the key chord is explicitly unbound // - nullopt if it is not bound - std::optional ActionMap::_GetActionIDByKeyChordInternal(const Control::KeyChord& keys) const + std::optional ActionMap::_GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const { if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end()) { @@ -555,7 +555,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // search through our parents for (const auto& parent : _parents) { - if (const auto foundCmdID = parent->_GetActionIDByKeyChordInternal(keys)) + if (const auto foundCmdID = parent->_GetActionIdByKeyChordInternal(keys)) { return foundCmdID; } @@ -576,7 +576,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - nullopt if it is not bound std::optional ActionMap::_GetActionByKeyChordInternal(const Control::KeyChord& keys) const { - if (const auto actionIDOptional = _GetActionIDByKeyChordInternal(keys)) + if (const auto actionIDOptional = _GetActionIdByKeyChordInternal(keys)) { if (!(*actionIDOptional).empty()) { diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 41ed1a2615c..3ffb25d4667 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -84,7 +84,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation private: Model::Command _GetActionByID(const winrt::hstring actionID) const; - std::optional _GetActionIDByKeyChordInternal(const Control::KeyChord& keys) const; + std::optional _GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; void _RefreshKeyBindingCaches(); diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 215695a5c39..d6a78bb9fbb 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -194,8 +194,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // TODO GH#17160 // if the command with this id is only going to appear later during settings load // then this will return null, meaning that the command created later on will not register this keybinding - // the keybinding will still work fine within the app, its just that the Command object itself won't know about this keymapping - // we are going to move away from Command needing to know its keymappings in a followup, so this shouldn't matter for very long + // the keybinding will still work fine within the app, its just that the Command object itself won't know about this key mapping + // we are going to move away from Command needing to know its key mappings in a followup, so this shouldn't matter for very long if (const auto cmd = _GetActionByID(idJson)) { cmd.RegisterKey(keys); From 3d92f27de766b91e12d9d7166a5d95d6308a5d5d Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 11:18:12 -0700 Subject: [PATCH 50/76] format --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 3d954b89184..3f94d177990 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -495,7 +495,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } else { - _KeyMap.insert_or_assign(keys, cmd.ID()); + _KeyMap.insert_or_assign(keys, cmd.ID()); } cmd.RegisterKey(keys); From 428821b40c028a868dccda6a762cc4244245f83e Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 13:04:14 -0700 Subject: [PATCH 51/76] remove _idwasgenerated --- src/cascadia/TerminalSettingsModel/Command.cpp | 3 +-- src/cascadia/TerminalSettingsModel/Command.h | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index d0c93cc8f66..0c565498d9d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -129,7 +129,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) { _ID = generatedID; - _IDWasGenerated = true; return true; } } @@ -452,7 +451,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation Json::Value cmdJson{ Json::ValueType::objectValue }; JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); JsonUtils::SetValueForKey(cmdJson, NameKey, _name); - if (!_ID.empty() && !_IDWasGenerated) + if (!_ID.empty()) { JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); } diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index fc7e1360771..b7a164bf7d6 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -89,7 +89,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector _keyMappings; std::optional _name; std::wstring _ID; - bool _IDWasGenerated{ false }; std::optional _iconPath; bool _nestedCommand{ false }; From 6437b9f5082fcb74d16b4ba2a336d64a8dc5a4c7 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 14:36:09 -0700 Subject: [PATCH 52/76] fix user defaults file --- src/cascadia/TerminalSettingsModel/defaults.json | 11 ++++------- src/cascadia/TerminalSettingsModel/userDefaults.json | 11 +++++++---- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 1968d8948b5..8a742e98117 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -487,8 +487,8 @@ { "command": { "action": "splitPane", "split": "down" }, "id": "Terminal.SplitPaneDown" }, { "command": { "action": "splitPane", "split": "left" }, "id": "Terminal.SplitPaneLeft" }, { "command": { "action": "splitPane", "split": "right" }, "id": "Terminal.SplitPaneRight" }, - { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "id": "Terminal.SplitPaneDuplicateDown" }, - { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "id": "Terminal.SplitPaneDuplicateRight" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "id": "Terminal.DuplicatePaneDown" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "id": "Terminal.DuplicatePaneRight" }, { "command": { "action": "resizePane", "direction": "down" }, "id": "Terminal.ResizePaneDown" }, { "command": { "action": "resizePane", "direction": "left" }, "id": "Terminal.ResizePaneLeft" }, { "command": { "action": "resizePane", "direction": "right" }, "id": "Terminal.ResizePaneRight" }, @@ -531,9 +531,6 @@ // Clipboard Integration { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, - { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, - { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, - { "command": "paste", "id": "Terminal.PasteFromClipboard" }, { "command": "paste", "id": "Terminal.PasteFromClipboard" }, { "command": "selectAll", "id": "Terminal.SelectAll" }, { "command": "markMode", "id": "Terminal.ToggleMarkMode" }, @@ -670,8 +667,8 @@ // Pane Management { "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, - { "keys": "alt+shift+-", "id": "Terminal.SplitPaneDuplicateDown" }, - { "keys": "alt+shift+plus", "id": "Terminal.SplitPaneDuplicateRight" }, + { "keys": "alt+shift+-", "id": "Terminal.DuplicatePaneDown" }, + { "keys": "alt+shift+plus", "id": "Terminal.DuplicatePaneRight" }, { "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, { "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, { "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, diff --git a/src/cascadia/TerminalSettingsModel/userDefaults.json b/src/cascadia/TerminalSettingsModel/userDefaults.json index 59fa13a6df0..22dd9d14cd3 100644 --- a/src/cascadia/TerminalSettingsModel/userDefaults.json +++ b/src/cascadia/TerminalSettingsModel/userDefaults.json @@ -21,11 +21,14 @@ } ] }, + "keybindings": + [ + { "id": "Terminal.CopySelectedText", "keys": "ctrl+c" }, + { "id": "Terminal.PasteFromClipboard", "keys": "ctrl+v" }, + { "id": "Terminal.DuplicatePaneAuto", "keys": "alt+shift+d" } + ], "actions": [ - { "command": {"action": "copy", "singleLine": false }, "keys": "ctrl+c" }, - { "command": "paste", "keys": "ctrl+v" }, - { "command": "find", "keys": "ctrl+shift+f" }, - { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "keys": "alt+shift+d" } + { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "id": "Terminal.DuplicatePaneAuto" } ] } From 4c744e6ab358031168afa7338533f8001ca71b75 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 14:56:42 -0700 Subject: [PATCH 53/76] misc --- .../TerminalSettingsModel/ActionMap.h | 11 ++++++++ .../ActionMapSerialization.cpp | 26 ++++++++++++------- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 3ffb25d4667..d7e80a90b2f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -111,10 +111,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation bool _fixUpsAppliedDuringLoad; void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); + + // _KeyMap is the map of key chords -> action IDs defined in this layer + // _ActionMap is the map of action IDs -> commands defined in this layer + // These maps are the ones that we deserialize into when parsing the user json and vice-versa std::unordered_map _KeyMap; std::unordered_map _ActionMap; + + // _CumulativeKeyMapCache is the map of key chords -> action IDs defined in all layers, with child layers overriding parent layers Windows::Foundation::Collections::IMap _CumulativeKeyMapCache{ nullptr }; + // _CumulativeActionMapCache is the map of action IDs -> commands defined in all layers, with child layers overriding parent layers Windows::Foundation::Collections::IMap _CumulativeActionMapCache{ nullptr }; + + // _ResolvedKeyActionMapCache is the map of key chords -> commands defined in all layers, with child layers overriding parent layers + // This is effectively a combination of _CumulativeKeyMapCache and _CumulativeActionMapCache and its purpose is so that + // we can give the SUI a view of the key chords and the commands they map to Windows::Foundation::Collections::IMap _ResolvedKeyActionMapCache{ nullptr }; friend class SettingsModelUnitTests::KeyBindingsTests; diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index d6a78bb9fbb..1336258bdb8 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -65,21 +65,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { AddAction(*Command::FromJson(jsonBlock, warnings, origin, withKeybindings)); - // this is a 'command' block and there are keys - meaning this is the legacy style - // let the loader know that fixups are needed - if (jsonBlock.isMember(JsonKey("keys"))) + // for non-nested non-iterable commands, + // check if this is a legacy-style command block so we can inform the loader that fixups are needed + if (jsonBlock.isMember(JsonKey("command")) && !jsonBlock.isMember(JsonKey("iterateOn"))) { - _fixUpsAppliedDuringLoad = true; - } - // for non-nested non-iterable user commands, if there's no ID we generate one for them - // let the loader know that fixups are needed - if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey("id")) && jsonBlock.isMember(JsonKey("command")) && !jsonBlock.isMember(JsonKey("iterateOn"))) - { - _fixUpsAppliedDuringLoad = true; + if (jsonBlock.isMember(JsonKey("keys"))) + { + // there are keys in this command block - its the legacy style + _fixUpsAppliedDuringLoad = true; + } + + if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey("id"))) + { + // there's no ID in this command block - we will generate one for the user + // inform the loader that the ID needs to be written into the json + _fixUpsAppliedDuringLoad = true; + } } } else { + // this is not a command block, so it is a keybinding block _AddKeyBindingHelper(jsonBlock, warnings); } } From ca4015f5f98f223a7606afad68613814d04e75b4 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 15:19:33 -0700 Subject: [PATCH 54/76] only one tojson --- .../ActionMapSerialization.cpp | 42 +++-------- .../TerminalSettingsModel/Command.cpp | 73 ++----------------- src/cascadia/TerminalSettingsModel/Command.h | 3 +- 3 files changed, 19 insertions(+), 99 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index 1336258bdb8..f811fc108b1 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -27,15 +27,14 @@ 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. + // - Deserialize an ActionMap from the array `json` + // - The json array either contains an array of serialized `Command` objects, + // or an array of keybindings + // - The actions are added to _ActionMap and the keybindings are added to _KeyMap // Arguments: - // - json: an array of Json::Value's to deserialize into our ActionMap. + // - json: an array of Json::Value's to deserialize into our _ActionMap and _KeyMap // Return value: // - a list of warnings encountered while deserializing the json - // todo: update this description std::vector ActionMap::LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { // It's possible that the user provided keybindings have some warnings in @@ -97,46 +96,27 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Json::Value actionList{ Json::ValueType::arrayValue }; - // Command serializes to an array of JSON objects. - // This is because a Command may have multiple key chords associated with it. - // The name and icon are only serialized in the first object. - // Example: - // { "name": "Custom Copy", "command": "copy", "keys": "ctrl+c" } - // { "command": "copy", "keys": "ctrl+shift+c" } - // { "command": "copy", "keys": "ctrl+ins" } - auto toJsonSpecial = [&actionList](const Model::Command& cmd) { + auto toJson = [&actionList](const Model::Command& cmd) { const auto cmdImpl{ winrt::get_self(cmd) }; - const auto& cmdJsonArray{ cmdImpl->ToJsonSpecial() }; - for (const auto& cmdJson : cmdJsonArray) - { - actionList.append(cmdJson); - } - }; - - auto toJsonStandard = [&actionList](const Model::Command& cmd) { - const auto cmdImpl{ winrt::get_self(cmd) }; - const auto& cmdJsonArray{ cmdImpl->ToJsonStandard() }; - for (const auto& cmdJson : cmdJsonArray) - { - actionList.append(cmdJson); - } + const auto& cmdJson{ cmdImpl->ToJson() }; + actionList.append(cmdJson); }; for (const auto& [_, cmd] : _ActionMap) { - toJsonStandard(cmd); + toJson(cmd); } // Serialize all nested Command objects added in the current layer for (const auto& [_, cmd] : _NestedCommands) { - toJsonSpecial(cmd); + toJson(cmd); } // Serialize all iterable Command objects added in the current layer for (const auto& cmd : _IterableCommands) { - toJsonSpecial(cmd); + toJson(cmd); } return actionList; diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 0c565498d9d..fb70849af99 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -428,14 +428,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // Function Description: - // - Serialize the Command into an array of json actions + // - Serialize the Command into a json value // Arguments: // - // Return Value: - // - an array of serialized actions - Json::Value Command::ToJsonSpecial() const + // - a serialized command + Json::Value Command::ToJson() const { - Json::Value cmdList{ Json::ValueType::arrayValue }; + Json::Value cmdJson{ Json::ValueType::objectValue }; if (_nestedCommand || _IterateOn != ExpandCommandType::None) { @@ -443,12 +443,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // For these, we can trust _originalJson to be correct. // In fact, we _need_ to use it here because we don't actually deserialize `iterateOn` // until we expand the command. - cmdList.append(_originalJson); + cmdJson = _originalJson; } - else if (_keyMappings.empty()) + else { - // only write out one command - Json::Value cmdJson{ Json::ValueType::objectValue }; JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); JsonUtils::SetValueForKey(cmdJson, NameKey, _name); if (!_ID.empty()) @@ -460,66 +458,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); } - - cmdList.append(cmdJson); - } - else - { - // we'll write out one command per key mapping - for (auto keys{ _keyMappings.begin() }; keys != _keyMappings.end(); ++keys) - { - Json::Value cmdJson{ Json::ValueType::objectValue }; - - if (keys == _keyMappings.begin()) - { - // First iteration also writes icon and name - JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); - JsonUtils::SetValueForKey(cmdJson, NameKey, _name); - if (!_ID.empty()) - { - JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); - } - } - - if (_ActionAndArgs) - { - cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); - } - - JsonUtils::SetValueForKey(cmdJson, KeysKey, *keys); - cmdList.append(cmdJson); - } } - return cmdList; - } - - // Function Description: - // - Serialize the Command into an array of json actions - // Arguments: - // - - // Return Value: - // - an array of serialized actions - Json::Value Command::ToJsonStandard() const - { - Json::Value cmdList{ Json::ValueType::arrayValue }; - - Json::Value cmdJson{ Json::ValueType::objectValue }; - JsonUtils::SetValueForKey(cmdJson, IconKey, _iconPath); - JsonUtils::SetValueForKey(cmdJson, NameKey, _name); - if (!_ID.empty()) - { - JsonUtils::SetValueForKey(cmdJson, IDKey, _ID); - } - - if (_ActionAndArgs) - { - cmdJson[JsonKey(ActionKey)] = ActionAndArgs::ToJson(_ActionAndArgs); - } - - cmdList.append(cmdJson); - - return cmdList; + return cmdJson; } // Function Description: diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index b7a164bf7d6..f22e35348c7 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -50,8 +50,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static std::vector LayerJson(Windows::Foundation::Collections::IMap& commands, const Json::Value& json, const OriginTag origin); - Json::Value ToJsonSpecial() const; - Json::Value ToJsonStandard() const; + Json::Value ToJson() const; bool HasNestedCommands() const; bool IsNestedCommand() const noexcept; From 45cfcd6ecaf410bd085683697436adb8351115ad Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 15:24:33 -0700 Subject: [PATCH 55/76] just add duplicate pane auto to defaults --- src/cascadia/TerminalSettingsModel/defaults.json | 1 + src/cascadia/TerminalSettingsModel/userDefaults.json | 4 ---- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 8a742e98117..f0c637b4f7e 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -489,6 +489,7 @@ { "command": { "action": "splitPane", "split": "right" }, "id": "Terminal.SplitPaneRight" }, { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "down" }, "id": "Terminal.DuplicatePaneDown" }, { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "right" }, "id": "Terminal.DuplicatePaneRight" }, + { "command": { "action": "splitPane", "splitMode": "duplicate", "split": "auto" }, "id": "Terminal.DuplicatePaneAuto" }, { "command": { "action": "resizePane", "direction": "down" }, "id": "Terminal.ResizePaneDown" }, { "command": { "action": "resizePane", "direction": "left" }, "id": "Terminal.ResizePaneLeft" }, { "command": { "action": "resizePane", "direction": "right" }, "id": "Terminal.ResizePaneRight" }, diff --git a/src/cascadia/TerminalSettingsModel/userDefaults.json b/src/cascadia/TerminalSettingsModel/userDefaults.json index 22dd9d14cd3..b6b4582f2a9 100644 --- a/src/cascadia/TerminalSettingsModel/userDefaults.json +++ b/src/cascadia/TerminalSettingsModel/userDefaults.json @@ -26,9 +26,5 @@ { "id": "Terminal.CopySelectedText", "keys": "ctrl+c" }, { "id": "Terminal.PasteFromClipboard", "keys": "ctrl+v" }, { "id": "Terminal.DuplicatePaneAuto", "keys": "alt+shift+d" } - ], - "actions": - [ - { "command": { "action": "splitPane", "split": "auto", "splitMode": "duplicate" }, "id": "Terminal.DuplicatePaneAuto" } ] } From 3c6015d97bf5f354ff1814f260fabc478cb0a96a Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 16:45:59 -0700 Subject: [PATCH 56/76] remove _getcumulativeactions --- .../TerminalSettingsModel/ActionMap.cpp | 46 ++++--------------- .../TerminalSettingsModel/ActionMap.h | 1 - .../GlobalAppSettings.cpp | 6 +-- 3 files changed, 11 insertions(+), 42 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 3f94d177990..77c4e070672 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -170,6 +170,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (!_NameMapCache) { + if (!_CumulativeActionMapCache) + { + _RefreshKeyBindingCaches(); + } // populate _NameMapCache std::unordered_map nameMap{}; _PopulateNameMapWithSpecialCommands(nameMap); @@ -226,49 +230,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 { - std::unordered_set visitedActionIDs; - for (const auto& cmd : _GetCumulativeActions()) + for (const auto& [_, cmd] : _CumulativeActionMapCache) { - // Only populate NameMap with actions that haven't been visited already. - const auto actionID{ cmd.ID() }; - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + const auto& name{ cmd.Name() }; + if (!name.empty()) { - 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.emplace(actionID); + // Update NameMap. + nameMap.insert_or_assign(name, cmd); } } } - // Method Description: - // - Provides an accumulated list of actions that are exposed. The accumulated list includes actions added in this layer, followed by actions added by our parents. - std::vector ActionMap::_GetCumulativeActions() const noexcept - { - // First, add actions from our current layer - std::vector cumulativeActions; - cumulativeActions.reserve(_ActionMap.size()); - - std::transform(_ActionMap.begin(), _ActionMap.end(), std::back_inserter(cumulativeActions), [](std::pair actionPair) { - return actionPair.second; - }); - - // Now, add the accumulated actions from our parents - for (const auto& parent : _parents) - { - const auto parentActions{ parent->_GetCumulativeActions() }; - cumulativeActions.reserve(cumulativeActions.size() + parentActions.size()); - cumulativeActions.insert(cumulativeActions.end(), parentActions.begin(), parentActions.end()); - } - - return cumulativeActions; - } - // Method Description: // - Recursively populate keyBindingsMap with ours and our parents' key -> id pairs // - This is a bottom-up approach, ensuring that the keybindings of the parents are overridden by the children diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index d7e80a90b2f..a32a6f16d08 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -92,7 +92,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void _PopulateNameMapWithSpecialCommands(std::unordered_map& nameMap) const; void _PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const; - std::vector _GetCumulativeActions() const noexcept; void _PopulateCumulativeKeyMap(std::unordered_map& keyBindingsMap); void _PopulateCumulativeActionMap(std::unordered_map& actionMap); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 0d3a7d75c7b..c878a49dc00 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -155,10 +155,8 @@ void GlobalAppSettings::LayerJson(const Json::Value& json, const OriginTag origi void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings) { - // we want to do the keybindings map after the actions map so that we have already - // stored the action IDs in the action map - // also, we want the keybindings map to overwrite any leftover keybindings that might have existed in the first pass, - // in case the user did a partial update from legacy to modern + // we want to do the keybindings map after the actions map so that we overwrite any leftover keybindings + // that might have existed in the first pass, in case the user did a partial update from legacy to modern static constexpr std::array bindingsKeys{ ActionsKey, LegacyKeybindingsKey }; for (const auto& jsonKey : bindingsKeys) { From c2c75c80ed15e4adfede1d0bb910e12d9b53b2fa Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 17:33:53 -0700 Subject: [PATCH 57/76] bandaid temporary fix for name --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 77c4e070672..bd05327e4d7 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -226,8 +226,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - 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. + // - nameMap: the nameMap we're populating, this maps the name (hstring) of a command to the command itself void ActionMap::_PopulateNameMapWithStandardCommands(std::unordered_map& nameMap) const { for (const auto& [_, cmd] : _CumulativeActionMapCache) @@ -235,8 +234,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation const auto& name{ cmd.Name() }; if (!name.empty()) { - // Update NameMap. - nameMap.insert_or_assign(name, cmd); + // there might be a collision here, where there could be 2 different commands with the same name + // in this case, prioritize the user's action + if (nameMap.find(name) != nameMap.end() && cmd.Origin() != OriginTag::User) + { + // a command with this name already exists in the map and this command is not a user-defined one, ignore it + continue; + } + else + { + nameMap.insert_or_assign(name, cmd); + } } } } From 3e601f5b6683b6e775b14cc9d51a7a90377ae575 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 17:36:37 -0700 Subject: [PATCH 58/76] better if --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index bd05327e4d7..20d61c33d5f 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -236,13 +236,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // there might be a collision here, where there could be 2 different commands with the same name // in this case, prioritize the user's action - if (nameMap.find(name) != nameMap.end() && cmd.Origin() != OriginTag::User) - { - // a command with this name already exists in the map and this command is not a user-defined one, ignore it - continue; - } - else + if (nameMap.find(name) == nameMap.end() || cmd.Origin() == OriginTag::User) { + // either a command with this name does not exist, or this is a user-defined command with a name + // in either case, update the name map with the command (if this is a user-defined command with + // the same name as an existing command, the existing one will get overwritten) nameMap.insert_or_assign(name, cmd); } } From cdb907d94d26784bb3fc3d0a302cd8799b889b8b Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Tue, 30 Apr 2024 17:43:25 -0700 Subject: [PATCH 59/76] mark todo --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 20d61c33d5f..48ea8c7d97d 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -236,6 +236,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // there might be a collision here, where there could be 2 different commands with the same name // in this case, prioritize the user's action + // TODO GH #17160: we should no longer use Command.Name to identify commands anywhere if (nameMap.find(name) == nameMap.end() || cmd.Origin() == OriginTag::User) { // either a command with this name does not exist, or this is a user-defined command with a name From 2b16acd4cfe5f00cb12a90d9d38aef0cc803b276 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 1 May 2024 14:48:37 -0700 Subject: [PATCH 60/76] check for name, fix some tests --- .../TerminalSettingsModel/ActionMap.cpp | 29 ++++++++++++++++ .../TerminalSettingsModel/Command.cpp | 10 ++++-- src/cascadia/TerminalSettingsModel/Command.h | 4 ++- .../TerminalSettingsModel/defaults.json | 1 - .../DeserializationTests.cpp | 16 +++++---- .../KeyBindingsTests.cpp | 33 ++++++++----------- 6 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 48ea8c7d97d..a53277ff3dd 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -432,6 +432,35 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // (if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that) if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) { + // in the legacy scenario, a user might have several of the same action but only one of them has defined an icon or a name + // eg. { "command": "paste", "name": "myPaste", "keys":"ctrl+a" } + // { "command": "paste", "keys": "ctrl+b" } + // once they port over to the new implementation, we will reduce it to just one Command object with a generated ID + // but several key binding entries, like so + // { "command": "newTab", "id": "User.paste" } -> in the actions map + // { "keys": "ctrl+a", "id": "User.paste" } -> in the keybindings map + // { "keys": "ctrl+b", "id": "User.paste" } -> in the keybindings map + // however, we have to make sure that we preserve the icon/name that might have been there in one of the command objects + // to do that, we check if this command we're adding had an ID that was generated + // if so, we check if there already exists a command with that generated ID, and if there is we port over any name/icon there might be + // (this may cause us to overwrite in scenarios where the user has an existing command that has the same generated ID but + // performs a different action or has different args, but that falls under "play stupid games") + const auto cmdImpl{ get_self(cmd) }; + if (cmdImpl->IdWasGenerated()) + { + if (const auto foundCmd{ _GetActionByID(cmdID) }) + { + const auto foundCmdImpl{ get_self(foundCmd) }; + if (foundCmdImpl->HasName() && !cmdImpl->HasName()) + { + cmdImpl->Name(foundCmdImpl->Name()); + } + if (!foundCmdImpl->IconPath().empty() && cmdImpl->IconPath().empty()) + { + cmdImpl->IconPath(foundCmdImpl->IconPath()); + } + } + } _ActionMap.insert_or_assign(cmdID, cmd); } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index fb70849af99..65ee04a028e 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -121,7 +121,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hstring{ _ID }; } - bool Command::GenerateID() + void Command::GenerateID() { if (_ActionAndArgs) { @@ -129,10 +129,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) { _ID = generatedID; - return true; + _IdWasGenerated = true; } } - return false; + } + + bool Command::IdWasGenerated() + { + return _IdWasGenerated; } void Command::Name(const hstring& value) diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index f22e35348c7..ed85159e153 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -62,7 +62,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void Name(const hstring& name); hstring ID() const noexcept; - bool GenerateID(); + void GenerateID(); + bool IdWasGenerated(); Control::KeyChord Keys() const noexcept; hstring KeyChordText() const noexcept; @@ -88,6 +89,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector _keyMappings; std::optional _name; std::wstring _ID; + bool _IdWasGenerated{ false }; std::optional _iconPath; bool _nestedCommand{ false }; diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index f0c637b4f7e..6424311c518 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -425,7 +425,6 @@ // Application-level Commands { "command": "closeWindow", "id": "Terminal.CloseWindow" }, { "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" }, - { "command": "toggleFullscreen", "id": "Terminal.ToggleFullscreen" }, { "command": "toggleFocusMode", "id": "Terminal.ToggleFocusMode" }, { "command": "toggleAlwaysOnTop", "id": "Terminal.ToggleAlwaysOnTop" }, { "command": "openNewTabDropdown", "id": "Terminal.OpenNewTabDropdown" }, diff --git a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp index 50462a72346..b0c72992729 100644 --- a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp @@ -1235,11 +1235,11 @@ namespace SettingsModelUnitTests const auto settings = createSettings(badSettings); // KeyMap: ctrl+a/b are mapped to "invalid" - // ActionMap: "splitPane" and "invalid" are the only deserialized actions + // ActionMap: "splitPane" is the only deserialized action // NameMap: "splitPane" has no key binding, but it is still added to the name map const auto actionMap = winrt::get_self(settings->GlobalSettings().ActionMap()); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); - VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size()); + VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); VERIFY_ARE_EQUAL(1u, actionMap->NameMap().Size()); VERIFY_ARE_EQUAL(5u, settings->Warnings().Size()); @@ -1981,7 +1981,8 @@ namespace SettingsModelUnitTests }, { "name": "bar", - "command": "closePane" + "command": "closePane", + "id": "Test.ClosePane" }, ], })" }; @@ -2005,7 +2006,7 @@ namespace SettingsModelUnitTests } { // Verify ActionMap::GetKeyBindingForAction API - const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(ShortcutAction::ClosePane) }; + const auto& actualKeyChord{ settings->ActionMap().GetKeyBindingForAction(L"Test.ClosePane") }; VERIFY_IS_NULL(actualKeyChord); } } @@ -2049,7 +2050,8 @@ namespace SettingsModelUnitTests "actions": [ { "command": { "action": "addMark" }, - "name": "Test Action" + "name": "Test Action", + "id": "Test.FragmentAction" }, ] })" }; @@ -2074,6 +2076,7 @@ namespace SettingsModelUnitTests { "command": { "action": "addMark" }, "keys": "ctrl+f", + "id": "Test.FragmentAction", "name": "Test Action" }, ] @@ -2195,7 +2198,8 @@ namespace SettingsModelUnitTests "actions": [ { "command": { "action": "addMark" }, - "name": "Test Action" + "name": "Test Action", + "id": "Test.FragmentAction" }, ] })" }; diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index afe22c8930c..3ee569e1620 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -157,8 +157,8 @@ namespace SettingsModelUnitTests void KeyBindingsTests::HashDeduplication() { const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); } @@ -166,8 +166,8 @@ namespace SettingsModelUnitTests { Log::Comment(L"These are two actions with different content args. They should have different hashes for their terminal args."); const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "keys": ["ctrl+c"] } ])"), OriginTag::None); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "keys": ["ctrl+shift+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "id": "Test.NewTabNoArgs", "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "id": "Test.NewTab0", "keys": ["ctrl+shift+c"] } ])"), OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size()); KeyChord ctrlC{ VirtualKeyModifiers::Control, static_cast('C'), 0 }; @@ -712,10 +712,10 @@ namespace SettingsModelUnitTests 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 std::string bindings3String{ R"([ { "command": "commandPalette", "keys": "ctrl+shift+p" } ])" }; + const std::string bindings0String{ R"([ { "command": "closeWindow", "id": "Test.CloseWindow", "keys": "ctrl+a" } ])" }; + const std::string bindings1String{ R"([ { "command": { "action": "copy", "singleLine": true }, "id": "Test.Copy", "keys": "ctrl+b" } ])" }; + const std::string bindings2String{ R"([ { "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTab", "keys": "ctrl+c" } ])" }; + const std::string bindings3String{ R"([ { "command": "commandPalette", "id": "Test.CmdPal", "keys": "ctrl+shift+p" } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); const auto bindings1Json = VerifyParseSucceeded(bindings1String); @@ -742,7 +742,7 @@ namespace SettingsModelUnitTests Log::Comment(L"simple command: no args"); actionMap->LayerJson(bindings0Json, OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_KeyMap.size()); - const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CloseWindow) }; + const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.CloseWindow") }; VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast('A'), 0 }, kbd); } { @@ -750,10 +750,7 @@ namespace SettingsModelUnitTests actionMap->LayerJson(bindings1Json, OriginTag::None); VERIFY_ARE_EQUAL(2u, actionMap->_KeyMap.size()); - auto args{ winrt::make_self() }; - args->SingleLine(true); - - const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::CopyText, *args) }; + const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.Copy") }; VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast('B'), 0 }, kbd); } { @@ -761,11 +758,7 @@ namespace SettingsModelUnitTests actionMap->LayerJson(bindings2Json, OriginTag::None); 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) }; + const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.NewTab") }; VerifyKeyChordEquality({ VirtualKeyModifiers::Control, static_cast('C'), 0 }, kbd); } { @@ -773,7 +766,7 @@ namespace SettingsModelUnitTests actionMap->LayerJson(bindings3Json, OriginTag::None); VERIFY_ARE_EQUAL(4u, actionMap->_KeyMap.size()); - const auto& kbd{ actionMap->GetKeyBindingForAction(ShortcutAction::ToggleCommandPalette) }; + const auto& kbd{ actionMap->GetKeyBindingForAction(L"Test.CmdPal") }; VerifyKeyChordEquality({ VirtualKeyModifiers::Control | VirtualKeyModifiers::Shift, static_cast('P'), 0 }, kbd); } } @@ -807,7 +800,7 @@ namespace SettingsModelUnitTests void KeyBindingsTests::KeybindingsWithoutVkey() { - const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "keys":"shift+sc(255)"}])!"); + const auto json = VerifyParseSucceeded(R"!([{"command": "quakeMode", "id": "Test.NoVKey", "keys":"shift+sc(255)"}])!"); const auto actionMap = winrt::make_self(); actionMap->LayerJson(json, OriginTag::None); From 193e5733bf1493929f85469ffe5919b8d32fcb53 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 1 May 2024 17:36:31 -0700 Subject: [PATCH 61/76] fix remaining tests --- .../KeyBindingsTests.cpp | 64 ++++++++--------- .../SerializationTests.cpp | 69 +++++++++++-------- 2 files changed, 71 insertions(+), 62 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index 3ee569e1620..a11970b3253 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -271,32 +271,32 @@ namespace SettingsModelUnitTests auto actionMap = winrt::make_self(); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings0Json, OriginTag::None); + actionMap->LayerJson(bindings0Json, OriginTag::User); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings1Json, OriginTag::None); + actionMap->LayerJson(bindings1Json, OriginTag::User); VERIFY_IS_TRUE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); - actionMap->LayerJson(bindings2Json, OriginTag::None); + actionMap->LayerJson(bindings2Json, OriginTag::User); VERIFY_IS_FALSE(actionMap->IsKeyChordExplicitlyUnbound(keyChord)); } void KeyBindingsTests::TestArbitraryArgs() { const std::string bindings0String{ R"([ - { "command": "copy", "keys": ["ctrl+c"] }, - { "command": { "action": "copy", "singleLine": false }, "keys": ["ctrl+shift+c"] }, - { "command": { "action": "copy", "singleLine": true }, "keys": ["alt+shift+c"] }, + { "command": "copy", "id": "Test.CopyNoArgs", "keys": ["ctrl+c"] }, + { "command": { "action": "copy", "singleLine": false }, "id": "Test.CopyMultiline", "keys": ["ctrl+shift+c"] }, + { "command": { "action": "copy", "singleLine": true }, "id": "Test.CopySingleline", "keys": ["alt+shift+c"] }, - { "command": "newTab", "keys": ["ctrl+t"] }, - { "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+t"] }, - { "command": { "action": "newTab", "index": 11 }, "keys": ["ctrl+shift+y"] }, + { "command": "newTab", "id": "Test.NewTabNoArgs", "keys": ["ctrl+t"] }, + { "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTab0", "keys": ["ctrl+shift+t"] }, + { "command": { "action": "newTab", "index": 11 }, "id": "Test.NewTab11", "keys": ["ctrl+shift+y"] }, - { "command": { "action": "copy", "madeUpBool": true }, "keys": ["ctrl+b"] }, - { "command": { "action": "copy" }, "keys": ["ctrl+shift+b"] }, + { "command": { "action": "copy", "madeUpBool": true }, "id": "Test.CopyFakeArgs", "keys": ["ctrl+b"] }, + { "command": { "action": "copy" }, "id": "Test.CopyNullArgs", "keys": ["ctrl+shift+b"] }, - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": ["ctrl+f"] }, - { "command": { "action": "adjustFontSize", "delta": -1 }, "keys": ["ctrl+g"] } + { "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Test.EnlargeFont", "keys": ["ctrl+f"] }, + { "command": { "action": "adjustFontSize", "delta": -1 }, "id": "Test.ReduceFont", "keys": ["ctrl+g"] } ])" }; @@ -428,10 +428,10 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestSplitPaneArgs() { const std::string bindings0String{ R"([ - { "keys": ["ctrl+d"], "command": { "action": "splitPane", "split": "vertical" } }, - { "keys": ["ctrl+e"], "command": { "action": "splitPane", "split": "horizontal" } }, - { "keys": ["ctrl+g"], "command": { "action": "splitPane" } }, - { "keys": ["ctrl+h"], "command": { "action": "splitPane", "split": "auto" } } + { "keys": ["ctrl+d"], "id": "Test.SplitPaneVertical", "command": { "action": "splitPane", "split": "vertical" } }, + { "keys": ["ctrl+e"], "id": "Test.SplitPaneHorizontal", "command": { "action": "splitPane", "split": "horizontal" } }, + { "keys": ["ctrl+g"], "id": "Test.SplitPane", "command": { "action": "splitPane" } }, + { "keys": ["ctrl+h"], "id": "Test.SplitPaneAuto", "command": { "action": "splitPane", "split": "auto" } } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -478,9 +478,9 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestSetTabColorArgs() { const std::string bindings0String{ R"([ - { "keys": ["ctrl+c"], "command": { "action": "setTabColor", "color": null } }, - { "keys": ["ctrl+d"], "command": { "action": "setTabColor", "color": "#123456" } }, - { "keys": ["ctrl+f"], "command": "setTabColor" }, + { "keys": ["ctrl+c"], "id": "Test.SetTabColorNull", "command": { "action": "setTabColor", "color": null } }, + { "keys": ["ctrl+d"], "id": "Test.SetTabColor", "command": { "action": "setTabColor", "color": "#123456" } }, + { "keys": ["ctrl+f"], "id": "Test.SetTabColorNoArgs", "command": "setTabColor" }, ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -521,7 +521,7 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestStringOverload() { const std::string bindings0String{ R"([ - { "command": "copy", "keys": "ctrl+c" } + { "command": "copy", "id": "Test.Copy", "keys": "ctrl+c" } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -543,12 +543,12 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestScrollArgs() { const std::string bindings0String{ R"([ - { "keys": ["up"], "command": "scrollUp" }, - { "keys": ["down"], "command": "scrollDown" }, - { "keys": ["ctrl+up"], "command": { "action": "scrollUp" } }, - { "keys": ["ctrl+down"], "command": { "action": "scrollDown" } }, - { "keys": ["ctrl+shift+up"], "command": { "action": "scrollUp", "rowsToScroll": 10 } }, - { "keys": ["ctrl+shift+down"], "command": { "action": "scrollDown", "rowsToScroll": 10 } } + { "keys": ["up"], "id": "Test.ScrollUp0", "command": "scrollUp" }, + { "keys": ["down"], "id": "Test.ScrollDown0", "command": "scrollDown" }, + { "keys": ["ctrl+up"], "id": "Test.ScrollUp1", "command": { "action": "scrollUp" } }, + { "keys": ["ctrl+down"], "id": "Test.ScrollDown1", "command": { "action": "scrollDown" } }, + { "keys": ["ctrl+shift+up"], "id": "Test.ScrollUp2", "command": { "action": "scrollUp", "rowsToScroll": 10 } }, + { "keys": ["ctrl+shift+down"], "id": "Test.ScrollDown2", "command": { "action": "scrollDown", "rowsToScroll": 10 } } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -620,8 +620,8 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestMoveTabArgs() { const std::string bindings0String{ R"([ - { "keys": ["up"], "command": { "action": "moveTab", "direction": "forward" } }, - { "keys": ["down"], "command": { "action": "moveTab", "direction": "backward" } } + { "keys": ["up"], "id": "Test.MoveTabUp", "command": { "action": "moveTab", "direction": "forward" } }, + { "keys": ["down"], "id": "Test.MoveTabDown", "command": { "action": "moveTab", "direction": "backward" } } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); @@ -665,9 +665,9 @@ namespace SettingsModelUnitTests void KeyBindingsTests::TestToggleCommandPaletteArgs() { const std::string bindings0String{ R"([ - { "keys": ["up"], "command": "commandPalette" }, - { "keys": ["ctrl+up"], "command": { "action": "commandPalette", "launchMode" : "action" } }, - { "keys": ["ctrl+shift+up"], "command": { "action": "commandPalette", "launchMode" : "commandLine" } } + { "keys": ["up"], "id": "Test.CmdPal", "command": "commandPalette" }, + { "keys": ["ctrl+up"], "id": "Test.CmdPalActionMode", "command": { "action": "commandPalette", "launchMode" : "action" } }, + { "keys": ["ctrl+shift+up"], "id": "Test.CmdPalLineMode", "command": { "action": "commandPalette", "launchMode" : "commandLine" } } ])" }; const auto bindings0Json = VerifyParseSucceeded(bindings0String); diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 20357656638..0e1cab1ddc9 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -120,13 +120,15 @@ namespace SettingsModelUnitTests "experimental.input.forceVT": false, - "actions": [] + "actions": [], + "keybindings": [] })" }; static constexpr std::string_view smallGlobalsString{ R"( { "defaultProfile": "{61c54bbd-c2c6-5271-96e7-009a87ff44bf}", - "actions": [] + "actions": [], + "keybindings": [] })" }; RoundtripTest(globalsString); @@ -275,47 +277,50 @@ namespace SettingsModelUnitTests { // simple command static constexpr std::string_view actionsString1{ R"([ - { "command": "paste" } + { "command": "paste", "id": "Test.Paste" } ])" }; // complex command static constexpr std::string_view actionsString2A{ R"([ - { "command": { "action": "setTabColor" } } + { "command": { "action": "setTabColor" }, "id": "Test.SetTabColor" } ])" }; static constexpr std::string_view actionsString2B{ R"([ - { "command": { "action": "setTabColor", "color": "#112233" } } + { "command": { "action": "setTabColor", "color": "#112233" }, "id": "Test.SetTabColor112233" } ])" }; static constexpr std::string_view actionsString2C{ R"([ - { "command": { "action": "copy" } }, - { "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" } } + { "command": { "action": "copy" }, "id": "Test.Copy" }, + { "command": { "action": "copy", "singleLine": true, "copyFormatting": "html" }, "id": "Test.CopyWithArgs" } ])" }; // simple command with key chords - static constexpr std::string_view actionsString3{ R"([ - { "command": "toggleAlwaysOnTop", "keys": "ctrl+a" }, - { "command": "toggleAlwaysOnTop", "keys": "ctrl+b" } - ])" }; + static constexpr std::string_view actionsString3{ R"({ "actions": [ + { "command": "toggleAlwaysOnTop", "id": "Test.ToggleAlwaysOnTop" } ], + "keybindings": [ + { "keys": "ctrl+a", "id": "Test.ToggleAlwaysOnTop" }, + { "keys": "ctrl+b", "id": "Test.ToggleAlwaysOnTop" } ]})" }; // complex command with key chords - static constexpr std::string_view actionsString4A{ R"([ - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+c" }, - { "command": { "action": "adjustFontSize", "delta": 1 }, "keys": "ctrl+d" } - ])" }; + static constexpr std::string_view actionsString4A{ R"({ "actions":[ + { "command": { "action": "adjustFontSize", "delta": 1 }, "id": "Test.EnlargeFont" } ], + "keybindings": [ + { "keys": "ctrl+c", "id": "Test.EnlargeFont" }, + { "keys": "ctrl+d", "id": "Test.EnlargeFont" } ]})" }; // command with name and icon and multiple key chords - static constexpr std::string_view actionsString5{ R"([ - { "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "keys": "ctrl+e" }, - { "command": "scrollToTop", "keys": "ctrl+f" } - ])" }; + static constexpr std::string_view actionsString5{ R"({ "actions":[ + { "icon": "image.png", "name": "Scroll To Top Name", "command": "scrollToTop", "id": "Test.ScrollToTop" } ], + "keybindings": [ + { "id": "Test.ScrollToTop", "keys": "ctrl+f" }, + { "id": "Test.ScrollToTop", "keys": "ctrl+e" } ]})" }; // complex command with new terminal args static constexpr std::string_view actionsString6{ R"([ - { "command": { "action": "newTab", "index": 0 }, "keys": "ctrl+g" }, + { "command": { "action": "newTab", "index": 0 }, "id": "Test.NewTerminal" }, ])" }; // complex command with meaningful null arg static constexpr std::string_view actionsString7{ R"([ - { "command": { "action": "renameWindow", "name": null }, "keys": "ctrl+h" } + { "command": { "action": "renameWindow", "name": null }, "id": "Test.MeaningfulNull" } ])" }; // nested command @@ -397,9 +402,9 @@ namespace SettingsModelUnitTests ])"" }; // unbound command - static constexpr std::string_view actionsString10{ R"([ - { "command": "unbound", "keys": "ctrl+c" } - ])" }; + static constexpr std::string_view actionsString10{ R"({ "actions": [], + "keybindings": [ + { "id": null, "keys": "ctrl+c" } ]})" }; Log::Comment(L"simple command"); RoundtripTest(actionsString1); @@ -409,14 +414,16 @@ namespace SettingsModelUnitTests RoundtripTest(actionsString2B); RoundtripTest(actionsString2C); + // ActionMap has effectively 2 "to json" calls we need to make, one for the actions and one for the keybindings + // So we cannot use RoundtripTest for actions + keychords, just use RoundTripTest Log::Comment(L"simple command with key chords"); - RoundtripTest(actionsString3); + RoundtripTest(actionsString3); Log::Comment(L"complex commands with key chords"); - RoundtripTest(actionsString4A); + RoundtripTest(actionsString4A); Log::Comment(L"command with name and icon and multiple key chords"); - RoundtripTest(actionsString5); + RoundtripTest(actionsString5); Log::Comment(L"complex command with new terminal args"); RoundtripTest(actionsString6); @@ -434,7 +441,7 @@ namespace SettingsModelUnitTests RoundtripTest(actionsString9D); Log::Comment(L"unbound command"); - RoundtripTest(actionsString10); + RoundtripTest(actionsString10); } void SerializationTests::CascadiaSettings() @@ -503,7 +510,10 @@ namespace SettingsModelUnitTests } ], "actions": [ - { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id": "User.sendInput.E02B3DF9", "keys": "ctrl+k" } + { "command": { "action": "sendInput", "input": "VT Griese Mode" }, "id": "Test.SendInput" } + ], + "keybindings": [ + { "id": "Test.SendInput", "keys": "ctrl+k" } ], "theme": "system", "themes": [] @@ -995,7 +1005,6 @@ namespace SettingsModelUnitTests { "name": "foo", "command": "closePane", - "keys": "ctrl+shift+w", "id": "thisIsMyClosePane" }, { From 0480d651cb43c91a74fbaa8e5ff5e4bdfa020519 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 1 May 2024 17:54:46 -0700 Subject: [PATCH 62/76] this is better --- src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index a11970b3253..0e38d164f0b 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -157,8 +157,8 @@ namespace SettingsModelUnitTests void KeyBindingsTests::HashDeduplication() { const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "id": "Test.SplitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "id": "Test.SplitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); } From 02a1e37aaea7c37c184495fdd81a85894dfadb1e Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Wed, 1 May 2024 18:05:06 -0700 Subject: [PATCH 63/76] correct GH todo --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index a53277ff3dd..ec05d5c1671 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -236,7 +236,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // there might be a collision here, where there could be 2 different commands with the same name // in this case, prioritize the user's action - // TODO GH #17160: we should no longer use Command.Name to identify commands anywhere + // TODO GH #17166: we should no longer use Command.Name to identify commands anywhere if (nameMap.find(name) == nameMap.end() || cmd.Origin() == OriginTag::User) { // either a command with this name does not exist, or this is a user-defined command with a name From 80fc299c1011156d74276ca3c193068b2d0b1d95 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 2 May 2024 18:11:46 -0700 Subject: [PATCH 64/76] some new tests --- .../KeyBindingsTests.cpp | 10 +- .../SerializationTests.cpp | 121 ++++++++++++++++++ 2 files changed, 126 insertions(+), 5 deletions(-) diff --git a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp index 0e38d164f0b..771fa87ed8e 100644 --- a/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/KeyBindingsTests.cpp @@ -157,17 +157,17 @@ namespace SettingsModelUnitTests void KeyBindingsTests::HashDeduplication() { const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "id": "Test.SplitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "id": "Test.SplitPane", "keys": ["ctrl+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": "splitPane", "keys": ["ctrl+c"] } ])"), OriginTag::User); VERIFY_ARE_EQUAL(1u, actionMap->_ActionMap.size()); } void KeyBindingsTests::HashContentArgs() { - Log::Comment(L"These are two actions with different content args. They should have different hashes for their terminal args."); + Log::Comment(L"These are two actions with different content args. They should have different generated IDs for their terminal args."); const auto actionMap = winrt::make_self(); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "id": "Test.NewTabNoArgs", "keys": ["ctrl+c"] } ])"), OriginTag::None); - actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "id": "Test.NewTab0", "keys": ["ctrl+shift+c"] } ])"), OriginTag::None); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", } , "keys": ["ctrl+c"] } ])"), OriginTag::User); + actionMap->LayerJson(VerifyParseSucceeded(R"([ { "command": { "action": "newTab", "index": 0 } , "keys": ["ctrl+shift+c"] } ])"), OriginTag::User); VERIFY_ARE_EQUAL(2u, actionMap->_ActionMap.size()); KeyChord ctrlC{ VirtualKeyModifiers::Control, static_cast('C'), 0 }; diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 0e1cab1ddc9..2afa1baa6c6 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -47,6 +47,8 @@ namespace SettingsModelUnitTests TEST_METHOD(RoundtripGenerateActionID); TEST_METHOD(NoGeneratedIDsForIterableAndNestedCommands); TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands); + TEST_METHOD(RoundtripLegacyToModernActions); + TEST_METHOD(MultipleActionsAreCollapsed); private: // Method Description: @@ -1074,4 +1076,123 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(sendInputCmd1.ID(), sendInputCmd1.ID()); } + + void SerializationTests::RoundtripLegacyToModernActions() + { + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "id": "Test.SendInput", + "command": { "action": "sendInput", "input": "just some input" }, + "keys": "ctrl+shift+w" + }, + { + "command": "unbound", + "keys": "ctrl+shift+x" + } + ] + })" }; + + // modern style: + // - no "unbound" actions, these are just keybindings that have no id + // - no keys in actions, these are keybindings with an id + static constexpr std::string_view newSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "command": { "action": "sendInput", "input": "just some input" }, + "id": "Test.SendInput" + } + ], + "keybindings": [ + { + "id": "Test.SendInput", + "keys": "ctrl+shift+w" + }, + { + "id": null, + "keys": "ctrl+shift+x" + } + ] + })" }; + + implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader.MergeInboxIntoUserSettings(); + loader.FinalizeLayering(); + VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings = winrt::make_self(std::move(loader)); + const auto oldResult{ settings->ToJson() }; + + implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); + const auto newSettings = winrt::make_self(std::move(newLoader)); + const auto newResult{ newSettings->ToJson() }; + + VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); + } + + void SerializationTests::MultipleActionsAreCollapsed() + { + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "icon": "myCoolIconPath.png", + "command": { "action": "sendInput", "input": "just some input" }, + "keys": "ctrl+shift+w" + }, + { + "command": { "action": "sendInput", "input": "just some input" }, + "keys": "ctrl+shift+x" + } + ] + })" }; + + // modern style: + // - multiple action blocks whose purpose is simply to define more keybindings for the same action + // get collapsed into one action block, with the name and iconpath preserved and have multiple keybindings instead + static constexpr std::string_view newSettingsJson{ R"( + { + "actions": [ + { + "name": "foo", + "icon": "myCoolIconPath.png", + "command": { "action": "sendInput", "input": "just some input" }, + "id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"(" + } + ], + "keybindings": [ + { + "keys": "ctrl+shift+w", + "id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"(" + }, + { + "keys": "ctrl+shift+x", + "id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"(" + } + ] + })" }; + + implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader.MergeInboxIntoUserSettings(); + loader.FinalizeLayering(); + VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings = winrt::make_self(std::move(loader)); + const auto oldResult{ settings->ToJson() }; + + implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); + const auto newSettings = winrt::make_self(std::move(newLoader)); + const auto newResult{ newSettings->ToJson() }; + + VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); + } } From ebc03e98f454acd6399849ff08e5b99d9431e538 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 2 May 2024 18:44:44 -0700 Subject: [PATCH 65/76] another test --- .../DeserializationTests.cpp | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) diff --git a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp index b0c72992729..aabd6fb06f2 100644 --- a/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/DeserializationTests.cpp @@ -58,6 +58,7 @@ namespace SettingsModelUnitTests TEST_METHOD(TestCloneInheritanceTree); TEST_METHOD(TestValidDefaults); TEST_METHOD(TestInheritedCommand); + TEST_METHOD(TestOverwriteParentCommandAndKeybinding); TEST_METHOD(LoadFragmentsWithMultipleUpdates); TEST_METHOD(FragmentActionSimple); @@ -2011,6 +2012,103 @@ namespace SettingsModelUnitTests } } + void DeserializationTests::TestOverwriteParentCommandAndKeybinding() + { + // Tests: + // - Redefine an action whose ID was originally defined in another layer + // - Redefine a keychord that exists in another layer + // - Define a keychord that points to an action in another layer + + static constexpr std::string_view settings1Json{ R"( + { + "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "profiles": [ + { + "name": "profile0", + "guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "historySize": 1, + "commandline": "cmd.exe" + } + ], + "actions": [ + { + "command": "closePane", + "id": "Parent.ClosePane" + }, + { + "command": "closePane", + "id": "Parent.ClosePane2" + } + ], + "keybindings": [ + { + "keys": "ctrl+shift+w", + "id": "Parent.ClosePane" + }, + { + "keys": "ctrl+shift+x", + "id": "Parent.ClosePane2" + } + ] + })" }; + + // this child actions and keybindings list + // - redefines Parent.ClosePane to perform a newTab action instead of a closePane action + // - redefines ctrl+shift+x to point to Child.ClosePane instead of Parent.ClosePane2 + // - defines ctrl+shift+y to point to Parent.ClosePane2 (an action that does not exist in this child layer) + static constexpr std::string_view settings2Json{ R"( + { + "defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}", + "actions": [ + { + "command": "newTab", + "id": "Parent.ClosePane" + }, + { + "command": "closePane", + "id": "Child.ClosePane" + } + ], + "keybindings": [ + { + "id": "Child.ClosePane", + "keys": "ctrl+shift+x" + }, + { + "id": "Parent.ClosePane2", + "keys": "ctrl+shift+y" + } + ] + })" }; + + const auto settings = winrt::make_self(settings2Json, settings1Json); + const KeyChord ctrlShiftW{ true, false, true, false, static_cast('W'), 0 }; + const KeyChord ctrlShiftX{ true, false, true, false, static_cast('X'), 0 }; + const KeyChord ctrlShiftY{ true, false, true, false, static_cast('Y'), 0 }; + + { + // ctrl+shift+w should point to Parent.ClosePane, however Parent.ClosePane should be a newTab action + const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftW) }; + VERIFY_IS_NOT_NULL(cmd); + VERIFY_ARE_EQUAL(cmd.ID(), L"Parent.ClosePane"); + VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::NewTab); + } + { + // ctrl+shift+x should point to Child.ClosePane + const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftX) }; + VERIFY_IS_NOT_NULL(cmd); + VERIFY_ARE_EQUAL(cmd.ID(), L"Child.ClosePane"); + VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::ClosePane); + } + { + // ctrl+shift+y should point to Parent.ClosePane2 + const auto& cmd{ settings->ActionMap().GetActionByKeyChord(ctrlShiftY) }; + VERIFY_IS_NOT_NULL(cmd); + VERIFY_ARE_EQUAL(cmd.ID(), L"Parent.ClosePane2"); + VERIFY_ARE_EQUAL(cmd.ActionAndArgs().Action(), ShortcutAction::ClosePane); + } + } + // This test ensures GH#11597, GH#12520 don't regress. void DeserializationTests::LoadFragmentsWithMultipleUpdates() { From ccf1cc9e834728d425fb7fcd808ce0da33e6d900 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 2 May 2024 19:06:51 -0700 Subject: [PATCH 66/76] nits --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 2 -- src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index ec05d5c1671..c2c7b4cdee3 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -505,8 +505,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { _KeyMap.insert_or_assign(keys, cmd.ID()); } - - cmd.RegisterKey(keys); } // Method Description: diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 2afa1baa6c6..b372182974f 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -1156,7 +1156,7 @@ namespace SettingsModelUnitTests // modern style: // - multiple action blocks whose purpose is simply to define more keybindings for the same action - // get collapsed into one action block, with the name and iconpath preserved and have multiple keybindings instead + // get collapsed into one action block, with the name and icon path preserved and have multiple keybindings instead static constexpr std::string_view newSettingsJson{ R"( { "actions": [ From 7793c5c5bc06aa3a024d7a2b5611922d6f7f7472 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 3 May 2024 13:30:09 -0700 Subject: [PATCH 67/76] schema --- doc/cascadia/profiles.schema.json | 61 +++++++++++++++++++++---------- 1 file changed, 42 insertions(+), 19 deletions(-) diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index d354bf0bcc0..ae4802d18a1 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -2044,7 +2044,7 @@ ] } }, - "Keybinding": { + "FullCommand": { "additionalProperties": false, "properties": { "command": { @@ -2169,21 +2169,6 @@ } ] }, - "keys": { - "description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key", - "oneOf": [ - { - "$ref": "#/$defs/KeyChordSegment" - }, - { - "items": { - "$ref": "#/$defs/KeyChordSegment" - }, - "minItems": 1, - "type": "array" - } - ] - }, "icon": { "$ref": "#/$defs/Icon" }, @@ -2215,7 +2200,7 @@ "commands": { "description": "List of commands to execute", "items": { - "$ref": "#/$defs/Keybinding/properties/command" + "$ref": "#/$defs/FullCommand/properties/command" }, "minItems": 1, "type": "array" @@ -2236,6 +2221,44 @@ ], "type": "object" }, + "Keybinding": { + "additionalProperties": false, + "properties": { + "id": { + "description": "The ID of the command this keybinding should execute.", + "type": "string" + }, + "keys": { + "description": "Defines the key combinations used to call the command. It must be composed of...\n -any number of modifiers (ctrl/alt/shift)\n -a non-modifier key", + "oneOf": [ + { + "$ref": "#/$defs/KeyChordSegment" + }, + { + "items": { + "$ref": "#/$defs/KeyChordSegment" + }, + "minItems": 1, + "type": "array" + } + ] + } + }, + "anyOf": [ + { + "required": [ + "keys", + "id" + ] + }, + { + "required": [ + "keys" + ] + } + ], + "type": "object" + }, "Globals": { "additionalProperties": true, "description": "Properties that affect the entire window, regardless of the profile settings.", @@ -2439,12 +2462,12 @@ "actions": { "description": "Properties are specific to each custom action.", "items": { - "$ref": "#/$defs/Keybinding" + "$ref": "#/$defs/FullCommand" }, "type": "array" }, "keybindings": { - "description": "[deprecated] Use actions instead.", + "description": "A list of keychords bound to action IDs", "deprecated": true, "items": { "$ref": "#/$defs/Keybinding" From abef25d29c59a5be291b52411db6b403e98fba73 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 3 May 2024 14:51:51 -0700 Subject: [PATCH 68/76] move this to header --- .../ActionMapSerialization.cpp | 20 +++++++++---------- .../TerminalSettingsModel/Command.cpp | 8 -------- src/cascadia/TerminalSettingsModel/Command.h | 8 ++++++++ 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index f811fc108b1..fe91bc196be 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -59,22 +59,21 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // and we can call Command::FromJson on it (Command::FromJson can handle parsing both legacy or modern) // if there is no "command" field, then it is a modern style keys block - // todo: use the CommandsKey / ActionKey static string view in Command.cpp somehow - if (jsonBlock.isMember(JsonKey("commands")) || jsonBlock.isMember(JsonKey("command"))) + if (jsonBlock.isMember(JsonKey(CommandsKey)) || jsonBlock.isMember(JsonKey(ActionKey))) { AddAction(*Command::FromJson(jsonBlock, warnings, origin, withKeybindings)); // for non-nested non-iterable commands, // check if this is a legacy-style command block so we can inform the loader that fixups are needed - if (jsonBlock.isMember(JsonKey("command")) && !jsonBlock.isMember(JsonKey("iterateOn"))) + if (jsonBlock.isMember(JsonKey(ActionKey)) && !jsonBlock.isMember(JsonKey(IterateOnKey))) { - if (jsonBlock.isMember(JsonKey("keys"))) + if (jsonBlock.isMember(JsonKey(KeysKey))) { // there are keys in this command block - its the legacy style _fixUpsAppliedDuringLoad = true; } - if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey("id"))) + if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey(IDKey))) { // there's no ID in this command block - we will generate one for the user // inform the loader that the ID needs to be written into the json @@ -128,8 +127,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation auto toJson = [&keybindingsList](const KeyChord kc, const winrt::hstring cmdID) { Json::Value keyIDPair{ Json::ValueType::objectValue }; - JsonUtils::SetValueForKey(keyIDPair, "keys", kc); - JsonUtils::SetValueForKey(keyIDPair, "id", cmdID); + JsonUtils::SetValueForKey(keyIDPair, KeysKey, kc); + JsonUtils::SetValueForKey(keyIDPair, IDKey, cmdID); keybindingsList.append(keyIDPair); }; @@ -148,8 +147,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - If there is also an "id" field - we add the pair to our _KeyMap // - If there is no "id" field - this is an explicit unbinding, still add it to the _KeyMap, // when this key chord is queried for we will know it is an explicit unbinding - // todo: use the KeysKey and IDKey static strings from Command.cpp - const auto keysJson{ json[JsonKey("keys")] }; + const auto keysJson{ json[JsonKey(KeysKey)] }; if (keysJson.isArray() && keysJson.size() > 1) { warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); @@ -158,7 +156,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Control::KeyChord keys{ nullptr }; winrt::hstring idJson; - if (JsonUtils::GetValueForKey(json, "keys", keys)) + if (JsonUtils::GetValueForKey(json, KeysKey, keys)) { // if these keys are already bound to some command, // we need to update that command to erase these keys as we are about to overwrite them @@ -169,7 +167,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine - JsonUtils::GetValueForKey(json, "id", idJson); + JsonUtils::GetValueForKey(json, IDKey, idJson); // any existing keybinding with the same keychord in this layer will get overwritten _KeyMap.insert_or_assign(keys, idJson); diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 65ee04a028e..7313f2057a8 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -20,14 +20,6 @@ namespace winrt namespace WUX = Windows::UI::Xaml; } -static constexpr std::string_view NameKey{ "name" }; -static constexpr std::string_view IDKey{ "id" }; -static constexpr std::string_view IconKey{ "icon" }; -static constexpr std::string_view ActionKey{ "command" }; -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}" }; static constexpr std::string_view SchemeNameToken{ "${scheme.name}" }; diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index ed85159e153..9a3bdd77e31 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -31,6 +31,14 @@ namespace SettingsModelUnitTests class CommandTests; }; +static constexpr std::string_view NameKey{ "name" }; +static constexpr std::string_view IDKey{ "id" }; +static constexpr std::string_view IconKey{ "icon" }; +static constexpr std::string_view ActionKey{ "command" }; +static constexpr std::string_view IterateOnKey{ "iterateOn" }; +static constexpr std::string_view CommandsKey{ "commands" }; +static constexpr std::string_view KeysKey{ "keys" }; + namespace winrt::Microsoft::Terminal::Settings::Model::implementation { struct Command : CommandT From 3e31bda6f2c3b427979552f74070466cc9374523 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 6 May 2024 15:29:24 -0700 Subject: [PATCH 69/76] generate here instead --- .../TerminalSettingsModel/ActionMap.cpp | 69 ++++++++++--------- .../TerminalSettingsModel/Command.cpp | 6 -- 2 files changed, 37 insertions(+), 38 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index c2c7b4cdee3..02ffe765114 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -419,49 +419,54 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // 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 maskingCmd. + // - Try to add the new command to _ActionMap // Arguments: // - cmd: the action we're trying to register - // - oldCmd: the action found in _ActionMap, if one already exists - // - maskingAction: the action found in a parent layer, if one already exists void ActionMap::_TryUpdateActionMap(const Model::Command& cmd) { - // only add to the _ActionMap if there is an ID and the shortcut action is valid - // (if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that) - if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) - { - // in the legacy scenario, a user might have several of the same action but only one of them has defined an icon or a name - // eg. { "command": "paste", "name": "myPaste", "keys":"ctrl+a" } - // { "command": "paste", "keys": "ctrl+b" } - // once they port over to the new implementation, we will reduce it to just one Command object with a generated ID - // but several key binding entries, like so - // { "command": "newTab", "id": "User.paste" } -> in the actions map - // { "keys": "ctrl+a", "id": "User.paste" } -> in the keybindings map - // { "keys": "ctrl+b", "id": "User.paste" } -> in the keybindings map - // however, we have to make sure that we preserve the icon/name that might have been there in one of the command objects - // to do that, we check if this command we're adding had an ID that was generated - // if so, we check if there already exists a command with that generated ID, and if there is we port over any name/icon there might be - // (this may cause us to overwrite in scenarios where the user has an existing command that has the same generated ID but - // performs a different action or has different args, but that falls under "play stupid games") + // if the shortcut action is invalid, then this is for unbinding and _TryUpdateKeyChord will handle that + if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + { const auto cmdImpl{ get_self(cmd) }; - if (cmdImpl->IdWasGenerated()) + if (cmd.Origin() == OriginTag::User && cmd.ID().empty()) + { + // the user did not define an ID for their non-nested, non-iterable, valid command - generate one for them + cmdImpl->GenerateID(); + } + + // only add to the _ActionMap if there is an ID + if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) { - if (const auto foundCmd{ _GetActionByID(cmdID) }) + // in the legacy scenario, a user might have several of the same action but only one of them has defined an icon or a name + // eg. { "command": "paste", "name": "myPaste", "keys":"ctrl+a" } + // { "command": "paste", "keys": "ctrl+b" } + // once they port over to the new implementation, we will reduce it to just one Command object with a generated ID + // but several key binding entries, like so + // { "command": "newTab", "id": "User.paste" } -> in the actions map + // { "keys": "ctrl+a", "id": "User.paste" } -> in the keybindings map + // { "keys": "ctrl+b", "id": "User.paste" } -> in the keybindings map + // however, we have to make sure that we preserve the icon/name that might have been there in one of the command objects + // to do that, we check if this command we're adding had an ID that was generated + // if so, we check if there already exists a command with that generated ID, and if there is we port over any name/icon there might be + // (this may cause us to overwrite in scenarios where the user has an existing command that has the same generated ID but + // performs a different action or has different args, but that falls under "play stupid games") + if (cmdImpl->IdWasGenerated()) { - const auto foundCmdImpl{ get_self(foundCmd) }; - if (foundCmdImpl->HasName() && !cmdImpl->HasName()) - { - cmdImpl->Name(foundCmdImpl->Name()); - } - if (!foundCmdImpl->IconPath().empty() && cmdImpl->IconPath().empty()) + if (const auto foundCmd{ _GetActionByID(cmdID) }) { - cmdImpl->IconPath(foundCmdImpl->IconPath()); + const auto foundCmdImpl{ get_self(foundCmd) }; + if (foundCmdImpl->HasName() && !cmdImpl->HasName()) + { + cmdImpl->Name(foundCmdImpl->Name()); + } + if (!foundCmdImpl->IconPath().empty() && cmdImpl->IconPath().empty()) + { + cmdImpl->IconPath(foundCmdImpl->IconPath()); + } } } + _ActionMap.insert_or_assign(cmdID, cmd); } - _ActionMap.insert_or_assign(cmdID, cmd); } } diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 7313f2057a8..97ff954a77d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -324,12 +324,6 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto actionJson{ json[JsonKey(ActionKey)] }) { result->_ActionAndArgs = *ActionAndArgs::FromJson(actionJson, warnings); - - // if this is a user-defined, non-iterable, valid command and they did not provide an id, generate one for them - if (result->_ID.empty() && result->_IterateOn == ExpandCommandType::None && result->_ActionAndArgs.Action() != ShortcutAction::Invalid && origin == OriginTag::User) - { - result->GenerateID(); - } } else { From 14d83b5f5c1c9e2ac2ad89c5384c408098876f02 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 6 May 2024 16:50:01 -0700 Subject: [PATCH 70/76] delete user actions that are identical to inbox actions --- .../TerminalSettingsModel/ActionMap.cpp | 74 +++++++++++++++++++ .../TerminalSettingsModel/ActionMap.h | 2 + .../GlobalAppSettings.cpp | 1 + 3 files changed, 77 insertions(+) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 02ffe765114..ec44e53ad83 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -58,6 +58,80 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return hasher.finalize(); } + // Method Description: + // - Detects if any of the user's actions are identical to the inbox actions, + // and if so, deletes them and redirects their keybindings to the inbox actions + // - We have to do this here instead of when loading since we don't actually have + // any parents while loading the user settings, the parents are added after + void ActionMap::_FinalizeInheritance() + { + // first, gather the inbox actions from the relevant parent + std::unordered_map InboxActions; + winrt::com_ptr foundParent{ nullptr }; + for (const auto parent : _parents) + { + for (const auto [_, cmd] : parent->_ActionMap) + { + if (cmd.Origin() != OriginTag::InBox) + { + // only one parent contains all the inbox actions and that parent contains only inbox actions, + // so if we found a non-inbox action we can just skip to the next parent + break; + } + foundParent = parent; + break; + } + } + + if (foundParent) + { + for (const auto [_, cmd] : foundParent->_ActionMap) + { + InboxActions.emplace(Hash(cmd.ActionAndArgs()), cmd); + } + } + + // now, look through our _ActionMap for commands that + // - had an ID generated for them + // - do not have a name/icon path + // - have a hash that matches a command in the inbox actions + std::unordered_set IdsToRemove; + for (const auto [userID, userCmd] : _ActionMap) + { + const auto userCmdImpl{ get_self(userCmd) }; + + // Note we don't need to explicitly check for the origin tag here since we only generate IDs for user actions, + // so if we ID was generated it means this is a user action + if (userCmdImpl->IdWasGenerated() && !userCmdImpl->HasName() && userCmd.IconPath().empty()) + { + const auto userActionHash = Hash(userCmd.ActionAndArgs()); + if (const auto inboxCmd = InboxActions.find(userActionHash); inboxCmd != InboxActions.end()) + { + for (auto [key, cmdID] : _KeyMap) + { + // for any of our keys that point to the user action, point them to the inbox action instead + if (cmdID == userID) + { + _KeyMap.insert_or_assign(key, inboxCmd->second.ID()); + + // register the keys with the inbox action + inboxCmd->second.RegisterKey(key); + } + } + + // add this ID to our set of IDs to remove + IdsToRemove.insert(userID); + } + } + } + + // now, remove the commands with the IDs we found + for (const auto id : IdsToRemove) + { + _ActionMap.erase(id); + } + } + bool ActionMap::FixUpsAppliedDuringLoad() const { return _fixUpsAppliedDuringLoad; diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index a32a6f16d08..8d4b078a340 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -49,6 +49,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation struct ActionMap : ActionMapT, IInheritable { + void _FinalizeInheritance() override; + // views Windows::Foundation::Collections::IMapView AvailableActions(); Windows::Foundation::Collections::IMapView NameMap(); diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index c878a49dc00..834513489c1 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -45,6 +45,7 @@ void GlobalAppSettings::_FinalizeInheritance() } } } + _actionMap->_FinalizeInheritance(); } winrt::com_ptr GlobalAppSettings::Copy() const From 6c6dd46e0265de1af21188d9ddcdc5679910fbbe Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Mon, 20 May 2024 15:06:31 -0700 Subject: [PATCH 71/76] leonard comments --- .../TerminalSettingsModel/ActionMap.cpp | 57 ++++++--------- .../ActionMapSerialization.cpp | 71 +++++++++---------- 2 files changed, 55 insertions(+), 73 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index ec44e53ad83..d4fa34aaf56 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -66,17 +66,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void ActionMap::_FinalizeInheritance() { // first, gather the inbox actions from the relevant parent - std::unordered_map InboxActions; + std::unordered_map inboxActions; winrt::com_ptr foundParent{ nullptr }; - for (const auto parent : _parents) + for (const auto& parent : _parents) { - for (const auto [_, cmd] : parent->_ActionMap) + for (const auto& [_, cmd] : parent->_ActionMap) { if (cmd.Origin() != OriginTag::InBox) { // only one parent contains all the inbox actions and that parent contains only inbox actions, // so if we found a non-inbox action we can just skip to the next parent - break; + continue; } foundParent = parent; break; @@ -85,9 +85,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (foundParent) { - for (const auto [_, cmd] : foundParent->_ActionMap) + for (const auto& [_, cmd] : foundParent->_ActionMap) { - InboxActions.emplace(Hash(cmd.ActionAndArgs()), cmd); + inboxActions.emplace(Hash(cmd.ActionAndArgs()), cmd); } } @@ -105,9 +105,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (userCmdImpl->IdWasGenerated() && !userCmdImpl->HasName() && userCmd.IconPath().empty()) { const auto userActionHash = Hash(userCmd.ActionAndArgs()); - if (const auto inboxCmd = InboxActions.find(userActionHash); inboxCmd != InboxActions.end()) + if (const auto inboxCmd = inboxActions.find(userActionHash); inboxCmd != inboxActions.end()) { - for (auto [key, cmdID] : _KeyMap) + for (const auto& [key, cmdID] : _KeyMap) { // for any of our keys that point to the user action, point them to the inbox action instead if (cmdID == userID) @@ -126,10 +126,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // now, remove the commands with the IDs we found - for (const auto id : IdsToRemove) - { - _ActionMap.erase(id); - } + std::erase_if(_ActionMap, [&IdsToRemove](const auto& pair) { return IdsToRemove.contains(pair.first); }); } bool ActionMap::FixUpsAppliedDuringLoad() const @@ -158,7 +155,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return cmd; } - for (const auto parent : _parents) + for (const auto& parent : _parents) { if (const auto inheritedCmd = parent->_GetActionByID(actionID)) { @@ -311,7 +308,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // there might be a collision here, where there could be 2 different commands with the same name // in this case, prioritize the user's action // TODO GH #17166: we should no longer use Command.Name to identify commands anywhere - if (nameMap.find(name) == nameMap.end() || cmd.Origin() == OriginTag::User) + if (!nameMap.contains(name) || cmd.Origin() == OriginTag::User) { // either a command with this name does not exist, or this is a user-defined command with a name // in either case, update the name map with the command (if this is a user-defined command with @@ -329,13 +326,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { for (const auto& [keys, cmdID] : _KeyMap) { - if (keyBindingsMap.find(keys) == keyBindingsMap.end()) + if (!keyBindingsMap.contains(keys)) { keyBindingsMap.emplace(keys, cmdID); } } - for (const auto parent : _parents) + for (const auto& parent : _parents) { parent->_PopulateCumulativeKeyMap(keyBindingsMap); } @@ -348,13 +345,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { for (const auto& [cmdID, cmd] : _ActionMap) { - if (actionMap.find(cmdID) == actionMap.end()) + if (!actionMap.contains(cmdID)) { actionMap.emplace(cmdID, cmd); } } - for (const auto parent : _parents) + for (const auto& parent : _parents) { parent->_PopulateCumulativeActionMap(actionMap); } @@ -388,7 +385,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _PopulateCumulativeKeyMap(accumulatedKeybindingsMap); _PopulateCumulativeActionMap(accumulatedActionsMap); - for (const auto [keys, cmdID] : accumulatedKeybindingsMap) + for (const auto& [keys, cmdID] : accumulatedKeybindingsMap) { if (const auto idCmdPair = accumulatedActionsMap.find(cmdID); idCmdPair != accumulatedActionsMap.end()) { @@ -576,14 +573,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // i.e. they provided an ID for a null command (which they really shouldn't, there's no purpose) // in this case, we do _not_ want to use the id they provided, we want to use an empty id // (empty id in the _KeyMap indicates the keychord was explicitly unbound) - if (cmd.ActionAndArgs().Action() == ShortcutAction::Invalid) - { - _KeyMap.insert_or_assign(keys, L""); - } - else - { - _KeyMap.insert_or_assign(keys, cmd.ID()); - } + const auto action = cmd.ActionAndArgs().Action(); + const auto id = action == ShortcutAction::Invalid ? hstring{} : cmd.ID(); + _KeyMap.insert_or_assign(keys, id); } // Method Description: @@ -626,15 +618,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end()) { - if (const auto cmdID = keyIDPair->second; !cmdID.empty()) - { - return cmdID; - } - else - { - // the keychord is defined in this layer, but points to an empty string - explicitly unbound - return L""; - } + // the keychord is defined in this layer, return the ID (ID be empty, in which case this key is explicitly unbound) + return keyIDPair->second; } // search through our parents diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index fe91bc196be..bbb229bdf9a 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -125,17 +125,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { Json::Value keybindingsList{ Json::ValueType::arrayValue }; - auto toJson = [&keybindingsList](const KeyChord kc, const winrt::hstring cmdID) { - Json::Value keyIDPair{ Json::ValueType::objectValue }; - JsonUtils::SetValueForKey(keyIDPair, KeysKey, kc); - JsonUtils::SetValueForKey(keyIDPair, IDKey, cmdID); - keybindingsList.append(keyIDPair); - }; - // Serialize all standard keybinding objects in the current layer for (const auto& [keys, cmdID] : _KeyMap) { - toJson(keys, cmdID); + Json::Value keyIDPair{ Json::ValueType::objectValue }; + JsonUtils::SetValueForKey(keyIDPair, KeysKey, keys); + JsonUtils::SetValueForKey(keyIDPair, IDKey, cmdID); + keybindingsList.append(keyIDPair); } return keybindingsList; @@ -151,40 +147,41 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (keysJson.isArray() && keysJson.size() > 1) { warnings.push_back(SettingsLoadWarnings::TooManyKeysForChord); + return; } - else + + Control::KeyChord keys{ nullptr }; + winrt::hstring idJson; + if (!JsonUtils::GetValueForKey(json, KeysKey, keys)) { - Control::KeyChord keys{ nullptr }; - winrt::hstring idJson; - if (JsonUtils::GetValueForKey(json, KeysKey, keys)) - { - // if these keys are already bound to some command, - // we need to update that command to erase these keys as we are about to overwrite them - if (const auto foundCommand = _GetActionByKeyChordInternal(keys); foundCommand && *foundCommand) - { - const auto foundCommandImpl{ get_self(*foundCommand) }; - foundCommandImpl->EraseKey(keys); - } + return; + } - // if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine - JsonUtils::GetValueForKey(json, IDKey, idJson); + // if these keys are already bound to some command, + // we need to update that command to erase these keys as we are about to overwrite them + if (const auto foundCommand = _GetActionByKeyChordInternal(keys); foundCommand && *foundCommand) + { + const auto foundCommandImpl{ get_self(*foundCommand) }; + foundCommandImpl->EraseKey(keys); + } - // any existing keybinding with the same keychord in this layer will get overwritten - _KeyMap.insert_or_assign(keys, idJson); + // if the "id" field doesn't exist in the json, then idJson will be an empty string which is fine + JsonUtils::GetValueForKey(json, IDKey, idJson); - // make sure the command registers these keys - if (!idJson.empty()) - { - // TODO GH#17160 - // if the command with this id is only going to appear later during settings load - // then this will return null, meaning that the command created later on will not register this keybinding - // the keybinding will still work fine within the app, its just that the Command object itself won't know about this key mapping - // we are going to move away from Command needing to know its key mappings in a followup, so this shouldn't matter for very long - if (const auto cmd = _GetActionByID(idJson)) - { - cmd.RegisterKey(keys); - } - } + // any existing keybinding with the same keychord in this layer will get overwritten + _KeyMap.insert_or_assign(keys, idJson); + + // make sure the command registers these keys + if (!idJson.empty()) + { + // TODO GH#17160 + // if the command with this id is only going to appear later during settings load + // then this will return null, meaning that the command created later on will not register this keybinding + // the keybinding will still work fine within the app, its just that the Command object itself won't know about this key mapping + // we are going to move away from Command needing to know its key mappings in a followup, so this shouldn't matter for very long + if (const auto cmd = _GetActionByID(idJson)) + { + cmd.RegisterKey(keys); } } return; From b88a8c58843637771798d2c53e224d0ca7ec1a3d Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Thu, 30 May 2024 13:03:03 -0700 Subject: [PATCH 72/76] eraseif --- .../TerminalSettingsModel/ActionMap.cpp | 25 +++++++------------ 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index d4fa34aaf56..1834a202307 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -95,22 +95,17 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - had an ID generated for them // - do not have a name/icon path // - have a hash that matches a command in the inbox actions - std::unordered_set IdsToRemove; - for (const auto [userID, userCmd] : _ActionMap) - { - const auto userCmdImpl{ get_self(userCmd) }; - - // Note we don't need to explicitly check for the origin tag here since we only generate IDs for user actions, - // so if we ID was generated it means this is a user action - if (userCmdImpl->IdWasGenerated() && !userCmdImpl->HasName() && userCmd.IconPath().empty()) + std::erase_if(_ActionMap, [&](const auto& pair) { + const auto userCmdImpl{ get_self(pair.second) }; + if (userCmdImpl->IdWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->IconPath().empty()) { - const auto userActionHash = Hash(userCmd.ActionAndArgs()); + const auto userActionHash = Hash(userCmdImpl->ActionAndArgs()); if (const auto inboxCmd = inboxActions.find(userActionHash); inboxCmd != inboxActions.end()) { for (const auto& [key, cmdID] : _KeyMap) { // for any of our keys that point to the user action, point them to the inbox action instead - if (cmdID == userID) + if (cmdID == pair.first) { _KeyMap.insert_or_assign(key, inboxCmd->second.ID()); @@ -119,14 +114,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - // add this ID to our set of IDs to remove - IdsToRemove.insert(userID); + // remove this pair + return true; } } - } - - // now, remove the commands with the IDs we found - std::erase_if(_ActionMap, [&IdsToRemove](const auto& pair) { return IdsToRemove.contains(pair.first); }); + return false; + }); } bool ActionMap::FixUpsAppliedDuringLoad() const From 9703815f59731f96b82c5ffd154b59b45ce116d4 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 31 May 2024 16:32:21 -0700 Subject: [PATCH 73/76] nits n fixes --- .../TerminalSettingsModel/ActionMap.cpp | 51 +++--- .../TerminalSettingsModel/ActionMap.h | 8 +- .../ActionMapSerialization.cpp | 6 +- .../CascadiaSettingsSerialization.cpp | 2 +- .../TerminalSettingsModel/Command.cpp | 6 +- src/cascadia/TerminalSettingsModel/Command.h | 4 +- .../GlobalAppSettings.cpp | 10 +- .../TerminalSettingsModel/GlobalAppSettings.h | 2 +- .../TerminalSettingsModel/defaults.json | 150 +++++++++--------- .../SerializationTests.cpp | 44 +++++ 10 files changed, 169 insertions(+), 114 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index 1834a202307..e8605b41ac2 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -76,9 +76,13 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // only one parent contains all the inbox actions and that parent contains only inbox actions, // so if we found a non-inbox action we can just skip to the next parent - continue; + break; } foundParent = parent; + } + + if (foundParent) + { break; } } @@ -91,13 +95,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } + std::unordered_map keysToReassign; + // now, look through our _ActionMap for commands that // - had an ID generated for them // - do not have a name/icon path // - have a hash that matches a command in the inbox actions std::erase_if(_ActionMap, [&](const auto& pair) { const auto userCmdImpl{ get_self(pair.second) }; - if (userCmdImpl->IdWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->IconPath().empty()) + if (userCmdImpl->IDWasGenerated() && !userCmdImpl->HasName() && userCmdImpl->IconPath().empty()) { const auto userActionHash = Hash(userCmdImpl->ActionAndArgs()); if (const auto inboxCmd = inboxActions.find(userActionHash); inboxCmd != inboxActions.end()) @@ -107,7 +113,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // for any of our keys that point to the user action, point them to the inbox action instead if (cmdID == pair.first) { - _KeyMap.insert_or_assign(key, inboxCmd->second.ID()); + keysToReassign.insert_or_assign(key, inboxCmd->second.ID()); // register the keys with the inbox action inboxCmd->second.RegisterKey(key); @@ -120,11 +126,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } return false; }); + + for (const auto [key, cmdID] : keysToReassign) + { + _KeyMap.insert_or_assign(key, cmdID); + } } - bool ActionMap::FixUpsAppliedDuringLoad() const + bool ActionMap::FixupsAppliedDuringLoad() const { - return _fixUpsAppliedDuringLoad; + return _fixupsAppliedDuringLoad; } // Method Description: @@ -134,7 +145,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // - actionID: the internal ID associated with a Command // Return Value: // - The command if it exists in this layer, otherwise nullptr - Model::Command ActionMap::_GetActionByID(const winrt::hstring actionID) const + Model::Command ActionMap::_GetActionByID(const winrt::hstring& actionID) const { // Check current layer const auto actionMapPair{ _ActionMap.find(actionID) }; @@ -204,9 +215,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { // Only populate AvailableActions with actions that haven't been visited already. const auto actionID = Hash(cmd.ActionAndArgs()); - if (visitedActionIDs.find(actionID) == visitedActionIDs.end()) + if (!visitedActionIDs.contains(actionID)) { - const auto& name{ cmd.Name() }; + const auto name{ cmd.Name() }; if (!name.empty()) { // Update AvailableActions. @@ -314,7 +325,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Method Description: // - Recursively populate keyBindingsMap with ours and our parents' key -> id pairs - // - This is a bottom-up approach, ensuring that the keybindings of the parents are overridden by the children + // - This is a bottom-up approach + // - Keybindings of the parents are overridden by the children void ActionMap::_PopulateCumulativeKeyMap(std::unordered_map& keyBindingsMap) { for (const auto& [keys, cmdID] : _KeyMap) @@ -333,7 +345,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // Method Description: // - Recursively populate actionMap with ours and our parents' id -> command pairs - // - This is a bottom-up approach, ensuring that the actions of the parents are overridden by the children + // - This is a bottom-up approach + // - Actions of the parents are overridden by the children void ActionMap::_PopulateCumulativeActionMap(std::unordered_map& actionMap) { for (const auto& [cmdID, cmd] : _ActionMap) @@ -499,7 +512,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } // only add to the _ActionMap if there is an ID - if (auto cmdID = cmd.ID(); !cmdID.empty() && cmd.ActionAndArgs().Action() != ShortcutAction::Invalid) + if (auto cmdID = cmd.ID(); !cmdID.empty()) { // in the legacy scenario, a user might have several of the same action but only one of them has defined an icon or a name // eg. { "command": "paste", "name": "myPaste", "keys":"ctrl+a" } @@ -514,7 +527,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // if so, we check if there already exists a command with that generated ID, and if there is we port over any name/icon there might be // (this may cause us to overwrite in scenarios where the user has an existing command that has the same generated ID but // performs a different action or has different args, but that falls under "play stupid games") - if (cmdImpl->IdWasGenerated()) + if (cmdImpl->IDWasGenerated()) { if (const auto foundCmd{ _GetActionByID(cmdID) }) { @@ -611,7 +624,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto keyIDPair = _KeyMap.find(keys); keyIDPair != _KeyMap.end()) { - // the keychord is defined in this layer, return the ID (ID be empty, in which case this key is explicitly unbound) + // the keychord is defined in this layer, return the ID return keyIDPair->second; } @@ -624,7 +637,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation } } - // we did not find the keychord anywhere, its not bound and not explicity unbound either + // we did not find the keychord anywhere, it's not bound and not explicitly unbound either return std::nullopt; } @@ -641,7 +654,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (const auto actionIDOptional = _GetActionIdByKeyChordInternal(keys)) { - if (!(*actionIDOptional).empty()) + if (!actionIDOptional->empty()) { // there is an ID associated with these keys, find the command if (const auto foundCmd = _GetActionByID(*actionIDOptional)) @@ -649,10 +662,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation return foundCmd; } } - { - // the ID is an empty string, these keys are explicitly unbound - return nullptr; - } + // the ID is an empty string, these keys are explicitly unbound + return nullptr; } return std::nullopt; @@ -665,7 +676,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // 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(winrt::hstring cmdID) const + Control::KeyChord ActionMap::GetKeyBindingForAction(const winrt::hstring& cmdID) const { // Check our internal state. if (const auto cmd{ _GetActionByID(cmdID) }) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.h b/src/cascadia/TerminalSettingsModel/ActionMap.h index 8d4b078a340..87bd5efb585 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.h +++ b/src/cascadia/TerminalSettingsModel/ActionMap.h @@ -61,7 +61,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation // queries Model::Command GetActionByKeyChord(const Control::KeyChord& keys) const; bool IsKeyChordExplicitlyUnbound(const Control::KeyChord& keys) const; - Control::KeyChord GetKeyBindingForAction(winrt::hstring cmdID) const; + Control::KeyChord GetKeyBindingForAction(const winrt::hstring& cmdID) const; // population void AddAction(const Model::Command& cmd); @@ -71,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector LayerJson(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson() const; Json::Value KeyBindingsToJson() const; - bool FixUpsAppliedDuringLoad() const; + bool FixupsAppliedDuringLoad() const; // modification bool RebindKeys(const Control::KeyChord& oldKeys, const Control::KeyChord& newKeys); @@ -85,7 +85,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::Windows::Foundation::Collections::IVector FilterToSendInput(winrt::hstring currentCommandline); private: - Model::Command _GetActionByID(const winrt::hstring actionID) const; + Model::Command _GetActionByID(const winrt::hstring& actionID) const; std::optional _GetActionIdByKeyChordInternal(const Control::KeyChord& keys) const; std::optional _GetActionByKeyChordInternal(const Control::KeyChord& keys) const; @@ -109,7 +109,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::unordered_map _NestedCommands; std::vector _IterableCommands; - bool _fixUpsAppliedDuringLoad; + bool _fixupsAppliedDuringLoad{ false }; void _AddKeyBindingHelper(const Json::Value& json, std::vector& warnings); diff --git a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp index bbb229bdf9a..e81c9dbd050 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMapSerialization.cpp @@ -69,15 +69,15 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { if (jsonBlock.isMember(JsonKey(KeysKey))) { - // there are keys in this command block - its the legacy style - _fixUpsAppliedDuringLoad = true; + // there are keys in this command block - it's the legacy style + _fixupsAppliedDuringLoad = true; } if (origin == OriginTag::User && !jsonBlock.isMember(JsonKey(IDKey))) { // there's no ID in this command block - we will generate one for the user // inform the loader that the ID needs to be written into the json - _fixUpsAppliedDuringLoad = true; + _fixupsAppliedDuringLoad = true; } } } diff --git a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp index 009154eb199..e9acef452c5 100644 --- a/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp +++ b/src/cascadia/TerminalSettingsModel/CascadiaSettingsSerialization.cpp @@ -461,7 +461,7 @@ bool SettingsLoader::FixupUserSettings() }; auto fixedUp = userSettings.fixupsAppliedDuringLoad; - fixedUp = userSettings.globals->FixUpsAppliedDuringLoad() || fixedUp; + fixedUp = userSettings.globals->FixupsAppliedDuringLoad() || fixedUp; fixedUp = RemapColorSchemeForProfile(userSettings.baseLayerProfile) || fixedUp; for (const auto& profile : userSettings.profiles) diff --git a/src/cascadia/TerminalSettingsModel/Command.cpp b/src/cascadia/TerminalSettingsModel/Command.cpp index 97ff954a77d..7d786b9008d 100644 --- a/src/cascadia/TerminalSettingsModel/Command.cpp +++ b/src/cascadia/TerminalSettingsModel/Command.cpp @@ -121,14 +121,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation if (const auto generatedID = actionAndArgsImpl->GenerateID(); !generatedID.empty()) { _ID = generatedID; - _IdWasGenerated = true; + _IDWasGenerated = true; } } } - bool Command::IdWasGenerated() + bool Command::IDWasGenerated() { - return _IdWasGenerated; + return _IDWasGenerated; } void Command::Name(const hstring& value) diff --git a/src/cascadia/TerminalSettingsModel/Command.h b/src/cascadia/TerminalSettingsModel/Command.h index 9a3bdd77e31..f2d29daa3f9 100644 --- a/src/cascadia/TerminalSettingsModel/Command.h +++ b/src/cascadia/TerminalSettingsModel/Command.h @@ -71,7 +71,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation hstring ID() const noexcept; void GenerateID(); - bool IdWasGenerated(); + bool IDWasGenerated(); Control::KeyChord Keys() const noexcept; hstring KeyChordText() const noexcept; @@ -97,7 +97,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation std::vector _keyMappings; std::optional _name; std::wstring _ID; - bool _IdWasGenerated{ false }; + bool _IDWasGenerated{ false }; std::optional _iconPath; bool _nestedCommand{ false }; diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp index 834513489c1..7151444ce7c 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.cpp @@ -17,7 +17,7 @@ using namespace winrt::Windows::UI::Xaml; using namespace ::Microsoft::Console; using namespace winrt::Microsoft::UI::Xaml::Controls; -static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" }; +static constexpr std::string_view KeybindingsKey{ "keybindings" }; static constexpr std::string_view ActionsKey{ "actions" }; static constexpr std::string_view ThemeKey{ "theme" }; static constexpr std::string_view DefaultProfileKey{ "defaultProfile" }; @@ -158,7 +158,7 @@ void GlobalAppSettings::LayerActionsFrom(const Json::Value& json, const OriginTa { // we want to do the keybindings map after the actions map so that we overwrite any leftover keybindings // that might have existed in the first pass, in case the user did a partial update from legacy to modern - static constexpr std::array bindingsKeys{ ActionsKey, LegacyKeybindingsKey }; + static constexpr std::array bindingsKeys{ ActionsKey, KeybindingsKey }; for (const auto& jsonKey : bindingsKeys) { if (auto bindings{ json[JsonKey(jsonKey)] }) @@ -262,14 +262,14 @@ Json::Value GlobalAppSettings::ToJson() #undef GLOBAL_SETTINGS_TO_JSON json[JsonKey(ActionsKey)] = _actionMap->ToJson(); - json[JsonKey(LegacyKeybindingsKey)] = _actionMap->KeyBindingsToJson(); + json[JsonKey(KeybindingsKey)] = _actionMap->KeyBindingsToJson(); return json; } -bool GlobalAppSettings::FixUpsAppliedDuringLoad() +bool GlobalAppSettings::FixupsAppliedDuringLoad() { - return _actionMap->FixUpsAppliedDuringLoad(); + return _actionMap->FixupsAppliedDuringLoad(); } winrt::Microsoft::Terminal::Settings::Model::Theme GlobalAppSettings::CurrentTheme() noexcept diff --git a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h index 7b3e1ff9a82..7b55b7007b5 100644 --- a/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h +++ b/src/cascadia/TerminalSettingsModel/GlobalAppSettings.h @@ -53,7 +53,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation void LayerActionsFrom(const Json::Value& json, const OriginTag origin, const bool withKeybindings = true); Json::Value ToJson(); - bool FixUpsAppliedDuringLoad(); + bool FixupsAppliedDuringLoad(); const std::vector& KeybindingsWarnings() const; diff --git a/src/cascadia/TerminalSettingsModel/defaults.json b/src/cascadia/TerminalSettingsModel/defaults.json index 6424311c518..1a3e7c6c111 100644 --- a/src/cascadia/TerminalSettingsModel/defaults.json +++ b/src/cascadia/TerminalSettingsModel/defaults.json @@ -530,7 +530,7 @@ { "command": "restartConnection", "id": "Terminal.RestartConnection" }, // Clipboard Integration - { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopySelectedText" }, + { "command": { "action": "copy", "singleLine": false }, "id": "Terminal.CopyToClipboard" }, { "command": "paste", "id": "Terminal.PasteFromClipboard" }, { "command": "selectAll", "id": "Terminal.SelectAll" }, { "command": "markMode", "id": "Terminal.ToggleMarkMode" }, @@ -625,84 +625,84 @@ } ], "keybindings": [ - // Application-level Keys - { "keys": "alt+f4", "id": "Terminal.CloseWindow" }, - { "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" }, - { "keys": "f11", "id": "Terminal.ToggleFullscreen" }, - { "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" }, - { "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, - { "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, - { "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, - { "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, - { "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, - { "keys":"win+sc(41)", "id": "Terminal.QuakeMode" }, - { "keys": "alt+space", "id": "Terminal.OpenSystemMenu" }, + // Application-level Keys + { "keys": "alt+f4", "id": "Terminal.CloseWindow" }, + { "keys": "alt+enter", "id": "Terminal.ToggleFullscreen" }, + { "keys": "f11", "id": "Terminal.ToggleFullscreen" }, + { "keys": "ctrl+shift+space", "id": "Terminal.OpenNewTabDropdown" }, + { "keys": "ctrl+,", "id": "Terminal.OpenSettingsUI" }, + { "keys": "ctrl+shift+,", "id": "Terminal.OpenSettingsFile" }, + { "keys": "ctrl+alt+,", "id": "Terminal.OpenDefaultSettingsFile" }, + { "keys": "ctrl+shift+f", "id": "Terminal.FindText" }, + { "keys":"ctrl+shift+p", "id": "Terminal.ToggleCommandPalette" }, + { "keys":"win+sc(41)", "id": "Terminal.QuakeMode" }, + { "keys": "alt+space", "id": "Terminal.OpenSystemMenu" }, - // Tab Management - // "command": "closeTab" is unbound by default. - // The closeTab command closes a tab without confirmation, even if it has multiple panes. - { "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" }, - { "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" }, - { "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" }, - { "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" }, - { "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" }, - { "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" }, - { "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" }, - { "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" }, - { "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" }, - { "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" }, - { "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" }, - { "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" }, - { "keys": "ctrl+tab", "id": "Terminal.NextTab" }, - { "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" }, - { "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" }, - { "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" }, - { "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" }, - { "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" }, - { "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" }, - { "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" }, - { "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" }, - { "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" }, - { "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" }, + // Tab Management + // "command": "closeTab" is unbound by default. + // The closeTab command closes a tab without confirmation, even if it has multiple panes. + { "keys": "ctrl+shift+t", "id": "Terminal.OpenNewTab" }, + { "keys": "ctrl+shift+n", "id": "Terminal.OpenNewWindow" }, + { "keys": "ctrl+shift+1", "id": "Terminal.OpenNewTabProfile0" }, + { "keys": "ctrl+shift+2", "id": "Terminal.OpenNewTabProfile1" }, + { "keys": "ctrl+shift+3", "id": "Terminal.OpenNewTabProfile2" }, + { "keys": "ctrl+shift+4", "id": "Terminal.OpenNewTabProfile3" }, + { "keys": "ctrl+shift+5", "id": "Terminal.OpenNewTabProfile4" }, + { "keys": "ctrl+shift+6", "id": "Terminal.OpenNewTabProfile5" }, + { "keys": "ctrl+shift+7", "id": "Terminal.OpenNewTabProfile6" }, + { "keys": "ctrl+shift+8", "id": "Terminal.OpenNewTabProfile7" }, + { "keys": "ctrl+shift+9", "id": "Terminal.OpenNewTabProfile8" }, + { "keys": "ctrl+shift+d", "id": "Terminal.DuplicateTab" }, + { "keys": "ctrl+tab", "id": "Terminal.NextTab" }, + { "keys": "ctrl+shift+tab", "id": "Terminal.PrevTab" }, + { "keys": "ctrl+alt+1", "id": "Terminal.SwitchToTab0" }, + { "keys": "ctrl+alt+2", "id": "Terminal.SwitchToTab1" }, + { "keys": "ctrl+alt+3", "id": "Terminal.SwitchToTab2" }, + { "keys": "ctrl+alt+4", "id": "Terminal.SwitchToTab3" }, + { "keys": "ctrl+alt+5", "id": "Terminal.SwitchToTab4" }, + { "keys": "ctrl+alt+6", "id": "Terminal.SwitchToTab5" }, + { "keys": "ctrl+alt+7", "id": "Terminal.SwitchToTab6" }, + { "keys": "ctrl+alt+8", "id": "Terminal.SwitchToTab7" }, + { "keys": "ctrl+alt+9", "id": "Terminal.SwitchToLastTab" }, - // Pane Management - { "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, - { "keys": "alt+shift+-", "id": "Terminal.DuplicatePaneDown" }, - { "keys": "alt+shift+plus", "id": "Terminal.DuplicatePaneRight" }, - { "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, - { "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, - { "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, - { "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" }, - { "keys": "alt+down", "id": "Terminal.MoveFocusDown" }, - { "keys": "alt+left", "id": "Terminal.MoveFocusLeft" }, - { "keys": "alt+right", "id": "Terminal.MoveFocusRight" }, - { "keys": "alt+up", "id": "Terminal.MoveFocusUp" }, - { "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" }, + // Pane Management + { "keys": "ctrl+shift+w", "id": "Terminal.ClosePane" }, + { "keys": "alt+shift+-", "id": "Terminal.DuplicatePaneDown" }, + { "keys": "alt+shift+plus", "id": "Terminal.DuplicatePaneRight" }, + { "keys": "alt+shift+down", "id": "Terminal.ResizePaneDown" }, + { "keys": "alt+shift+left", "id": "Terminal.ResizePaneLeft" }, + { "keys": "alt+shift+right", "id": "Terminal.ResizePaneRight" }, + { "keys": "alt+shift+up", "id": "Terminal.ResizePaneUp" }, + { "keys": "alt+down", "id": "Terminal.MoveFocusDown" }, + { "keys": "alt+left", "id": "Terminal.MoveFocusLeft" }, + { "keys": "alt+right", "id": "Terminal.MoveFocusRight" }, + { "keys": "alt+up", "id": "Terminal.MoveFocusUp" }, + { "keys": "ctrl+alt+left", "id": "Terminal.MoveFocusPrevious" }, - // Clipboard Integration - { "keys": "ctrl+shift+c", "id": "Terminal.CopySelectedText" }, - { "keys": "ctrl+insert", "id": "Terminal.CopySelectedText" }, - { "keys": "enter", "id": "Terminal.CopySelectedText" }, - { "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" }, - { "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" }, - { "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" }, - { "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" }, - { "keys": "menu", "id": "Terminal.ShowContextMenu" }, + // Clipboard Integration + { "keys": "ctrl+shift+c", "id": "Terminal.CopyToClipboard" }, + { "keys": "ctrl+insert", "id": "Terminal.CopyToClipboard" }, + { "keys": "enter", "id": "Terminal.CopyToClipboard" }, + { "keys": "ctrl+shift+v", "id": "Terminal.PasteFromClipboard" }, + { "keys": "shift+insert", "id": "Terminal.PasteFromClipboard" }, + { "keys": "ctrl+shift+a", "id": "Terminal.SelectAll" }, + { "keys": "ctrl+shift+m", "id": "Terminal.ToggleMarkMode" }, + { "keys": "menu", "id": "Terminal.ShowContextMenu" }, - // Scrollback - { "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" }, - { "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" }, - { "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" }, - { "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, - { "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, - { "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, + // Scrollback + { "keys": "ctrl+shift+down", "id": "Terminal.ScrollDown" }, + { "keys": "ctrl+shift+pgdn", "id": "Terminal.ScrollDownPage" }, + { "keys": "ctrl+shift+up", "id": "Terminal.ScrollUp" }, + { "keys": "ctrl+shift+pgup", "id": "Terminal.ScrollUpPage" }, + { "keys": "ctrl+shift+home", "id": "Terminal.ScrollToTop" }, + { "keys": "ctrl+shift+end", "id": "Terminal.ScrollToBottom" }, - // Visual Adjustments - { "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, - { "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" }, - { "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" }, - { "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" }, - { "keys": "ctrl+0", "id": "Terminal.ResetFontSize" }, - { "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" }, + // Visual Adjustments + { "keys": "ctrl+plus", "id": "Terminal.IncreaseFontSize" }, + { "keys": "ctrl+minus", "id": "Terminal.DecreaseFontSize" }, + { "keys": "ctrl+numpad_plus", "id": "Terminal.IncreaseFontSize" }, + { "keys": "ctrl+numpad_minus", "id": "Terminal.DecreaseFontSize" }, + { "keys": "ctrl+0", "id": "Terminal.ResetFontSize" }, + { "keys": "ctrl+numpad_0", "id": "Terminal.ResetFontSize" }, ] } diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index b372182974f..433c98ba3f9 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -48,6 +48,7 @@ namespace SettingsModelUnitTests TEST_METHOD(NoGeneratedIDsForIterableAndNestedCommands); TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands); TEST_METHOD(RoundtripLegacyToModernActions); + TEST_METHOD(RoundtripUserActionsSameAsInBoxAreRemoved); TEST_METHOD(MultipleActionsAreCollapsed); private: @@ -1136,6 +1137,49 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); } + void SerializationTests::RoundtripUserActionsSameAsInBoxAreRemoved() + { + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "command": "paste", + "keys": "ctrl+shift+x" + } + ] + })" }; + + // this action is the same as in inbox one, + // so we will delete this action from the user's file but retain the keybinding + static constexpr std::string_view newSettingsJson{ R"( + { + "actions": [ + ], + "keybindings": [ + { + "id": "Terminal.PasteFromClipboard", + "keys": "ctrl+shift+x" + } + ] + })" }; + + implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader.MergeInboxIntoUserSettings(); + loader.FinalizeLayering(); + VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings = winrt::make_self(std::move(loader)); + const auto oldResult{ settings->ToJson() }; + + implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); + const auto newSettings = winrt::make_self(std::move(newLoader)); + const auto newResult{ newSettings->ToJson() }; + + VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); + } + void SerializationTests::MultipleActionsAreCollapsed() { static constexpr std::string_view oldSettingsJson{ R"( From a80316dfa50007a177d0e45687ea51fee07b857c Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 31 May 2024 16:56:34 -0700 Subject: [PATCH 74/76] 1 more test --- .../SerializationTests.cpp | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 433c98ba3f9..7bf629d9e28 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -24,6 +24,12 @@ using namespace winrt::Microsoft::Terminal::Control; #define SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH "A020D2" #endif +#if defined(_M_IX86) +#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "56911147" +#else +#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "58D1971" +#endif + namespace SettingsModelUnitTests { class SerializationTests : public JsonTestClass @@ -49,6 +55,7 @@ namespace SettingsModelUnitTests TEST_METHOD(GeneratedActionIDsEqualForIdenticalCommands); TEST_METHOD(RoundtripLegacyToModernActions); TEST_METHOD(RoundtripUserActionsSameAsInBoxAreRemoved); + TEST_METHOD(RoundtripActionsSameNameDifferentCommandsAreRetained); TEST_METHOD(MultipleActionsAreCollapsed); private: @@ -1180,6 +1187,57 @@ namespace SettingsModelUnitTests VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); } + void SerializationTests::RoundtripActionsSameNameDifferentCommandsAreRetained() + { + static constexpr std::string_view oldSettingsJson{ R"( + { + "actions": [ + { + "command": { "action": "sendInput", "input": "just some input" }, + "name": "mySendInput" + }, + { + "command": { "action": "sendInput", "input": "just some input 2" }, + "name": "mySendInput" + } + ] + })" }; + + // There are two different actions with the same name, + // ensure that both are kept but have different IDs generated for them + static constexpr std::string_view newSettingsJson{ R"( + { + "actions": [ + { + "name": "mySendInput", + "command": { "action": "sendInput", "input": "just some input" }, + "id": "User.sendInput.)" SEND_INPUT_ARCH_SPECIFIC_ACTION_HASH R"(" + }, + { + "name": "mySendInput", + "command": { "action": "sendInput", "input": "just some input 2" }, + "id": "User.sendInput.)" SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH R"(" + } + ] + })" }; + + implementation::SettingsLoader loader{ oldSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + loader.MergeInboxIntoUserSettings(); + loader.FinalizeLayering(); + VERIFY_IS_TRUE(loader.FixupUserSettings(), L"Validate that this will indicate we need to write them back to disk"); + const auto settings = winrt::make_self(std::move(loader)); + const auto oldResult{ settings->ToJson() }; + + implementation::SettingsLoader newLoader{ newSettingsJson, implementation::LoadStringResource(IDR_DEFAULTS) }; + newLoader.MergeInboxIntoUserSettings(); + newLoader.FinalizeLayering(); + VERIFY_IS_FALSE(newLoader.FixupUserSettings(), L"Validate that there is no need to write back to disk"); + const auto newSettings = winrt::make_self(std::move(newLoader)); + const auto newResult{ newSettings->ToJson() }; + + VERIFY_ARE_EQUAL(toString(newResult), toString(oldResult)); + } + void SerializationTests::MultipleActionsAreCollapsed() { static constexpr std::string_view oldSettingsJson{ R"( From 625753cc41e7f9995b04ee4e0e1f97e9ed55160f Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 31 May 2024 17:52:20 -0700 Subject: [PATCH 75/76] x86 hash --- src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp index 7bf629d9e28..57dc4e1f868 100644 --- a/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp +++ b/src/cascadia/UnitTests_SettingsModel/SerializationTests.cpp @@ -25,7 +25,7 @@ using namespace winrt::Microsoft::Terminal::Control; #endif #if defined(_M_IX86) -#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "56911147" +#define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "35488AA6" #else #define SEND_INPUT2_ARCH_SPECIFIC_ACTION_HASH "58D1971" #endif From 406312f2f821ea130f1e1e475adde25b9de83c33 Mon Sep 17 00:00:00 2001 From: Pankaj Bhojwani Date: Fri, 31 May 2024 19:33:07 -0700 Subject: [PATCH 76/76] fix loops --- src/cascadia/TerminalSettingsModel/ActionMap.cpp | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/src/cascadia/TerminalSettingsModel/ActionMap.cpp b/src/cascadia/TerminalSettingsModel/ActionMap.cpp index e8605b41ac2..3458f1e180e 100644 --- a/src/cascadia/TerminalSettingsModel/ActionMap.cpp +++ b/src/cascadia/TerminalSettingsModel/ActionMap.cpp @@ -70,19 +70,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation winrt::com_ptr foundParent{ nullptr }; for (const auto& parent : _parents) { - for (const auto& [_, cmd] : parent->_ActionMap) + const auto parentMap = parent->_ActionMap; + if (parentMap.begin() != parentMap.end() && parentMap.begin()->second.Origin() == OriginTag::InBox) { - if (cmd.Origin() != OriginTag::InBox) - { - // only one parent contains all the inbox actions and that parent contains only inbox actions, - // so if we found a non-inbox action we can just skip to the next parent - break; - } + // only one parent contains all the inbox actions and that parent contains only inbox actions, + // so if we found an inbox action we know this is the parent we are looking for foundParent = parent; - } - - if (foundParent) - { break; } }