Compare commits
5 commits
main
...
dev/lelian
Author | SHA1 | Date | |
---|---|---|---|
b278f2faf8 | |||
d493f6e551 | |||
a6b8d35684 | |||
387b057c79 | |||
60aefc058d |
|
@ -115,8 +115,10 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// These are special cases.
|
||||
// - QuakeMode: deserializes into a GlobalSummon, so we don't need a serializer
|
||||
// - Invalid: has no args
|
||||
// - ExplicitlyUnbound: specifically for the "unbound" case.
|
||||
{ ShortcutAction::QuakeMode, { GlobalSummonArgs::QuakeModeFromJson, nullptr } },
|
||||
{ ShortcutAction::Invalid, { nullptr, nullptr } },
|
||||
{ ShortcutAction::Unbound, { nullptr, nullptr } },
|
||||
|
||||
#define ON_ALL_ACTIONS_WITH_ARGS(action) ACTION_TO_SERIALIZERS_PAIR(action)
|
||||
ALL_SHORTCUT_ACTIONS_WITH_ARGS
|
||||
|
@ -161,6 +163,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// - The ShortcutAction corresponding to the given string, if a match exists.
|
||||
static ShortcutAction GetActionFromString(const std::string_view actionString)
|
||||
{
|
||||
if (actionString == UnboundKey)
|
||||
{
|
||||
return ShortcutAction::Unbound;
|
||||
}
|
||||
|
||||
// Try matching the command to one we have. If we can't find the
|
||||
// action name in our list of names, let's just unbind that key.
|
||||
const auto found = ActionAndArgs::ActionKeyNamesMap.find(actionString);
|
||||
|
@ -306,6 +313,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return copy;
|
||||
}
|
||||
|
||||
bool ActionAndArgs::IsUnbound() const
|
||||
{
|
||||
return _Action == ShortcutAction::Invalid || _Action == ShortcutAction::Unbound;
|
||||
}
|
||||
|
||||
winrt::hstring ActionAndArgs::GenerateName() const
|
||||
{
|
||||
// Use a magic static to initialize this map, because we won't be able
|
||||
|
|
|
@ -26,6 +26,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool IsUnbound() const;
|
||||
|
||||
WINRT_PROPERTY(ShortcutAction, Action, ShortcutAction::Invalid);
|
||||
WINRT_PROPERTY(IActionArgs, Args, nullptr);
|
||||
};
|
||||
|
|
|
@ -13,38 +13,46 @@ using namespace winrt::Microsoft::Terminal::Control;
|
|||
|
||||
namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
||||
{
|
||||
static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs)
|
||||
static InternalActionID Hash(const Model::ActionAndArgs& actionAndArgs, const std::wstring& externalID = L"")
|
||||
{
|
||||
size_t hashedAction{ HashUtils::HashProperty(actionAndArgs.Action()) };
|
||||
if (externalID.empty())
|
||||
{
|
||||
size_t hashedAction{ HashUtils::HashProperty(actionAndArgs.Action()) };
|
||||
|
||||
size_t hashedArgs{};
|
||||
if (const auto& args{ actionAndArgs.Args() })
|
||||
{
|
||||
// Args are defined, so hash them
|
||||
hashedArgs = gsl::narrow_cast<size_t>(args.Hash());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Args are not defined.
|
||||
// Check if the ShortcutAction supports args.
|
||||
switch (actionAndArgs.Action())
|
||||
size_t hashedArgs{};
|
||||
if (const auto& args{ actionAndArgs.Args() })
|
||||
{
|
||||
// Args are defined, so hash them
|
||||
hashedArgs = gsl::narrow_cast<size_t>(args.Hash());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Args are not defined.
|
||||
// Check if the ShortcutAction supports args.
|
||||
switch (actionAndArgs.Action())
|
||||
{
|
||||
#define ON_ALL_ACTIONS_WITH_ARGS(action) \
|
||||
case ShortcutAction::action: \
|
||||
/* If it does, hash the default values for the args.*/ \
|
||||
hashedArgs = EmptyHash<implementation::action##Args>(); \
|
||||
break;
|
||||
ALL_SHORTCUT_ACTIONS_WITH_ARGS
|
||||
ALL_SHORTCUT_ACTIONS_WITH_ARGS
|
||||
#undef ON_ALL_ACTIONS_WITH_ARGS
|
||||
default:
|
||||
{
|
||||
// Otherwise, hash nullptr.
|
||||
std::hash<IActionArgs> argsHash;
|
||||
hashedArgs = argsHash(nullptr);
|
||||
}
|
||||
default:
|
||||
{
|
||||
// Otherwise, hash nullptr.
|
||||
std::hash<IActionArgs> argsHash;
|
||||
hashedArgs = argsHash(nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
return hashedAction ^ hashedArgs;
|
||||
}
|
||||
return hashedAction ^ hashedArgs;
|
||||
else
|
||||
{
|
||||
return HashUtils::HashProperty(externalID);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -436,6 +444,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
if (!name.empty())
|
||||
{
|
||||
_NestedCommands.emplace(name, cmd);
|
||||
|
||||
if (!cmd.ExternalID().empty())
|
||||
{
|
||||
_ExternalIDToSpecialCommands.emplace(cmd.ExternalID(), cmd);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -444,6 +457,12 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
if (cmdImpl->IterateOn() != ExpandCommandType::None)
|
||||
{
|
||||
_IterableCommands.emplace_back(cmd);
|
||||
|
||||
if (!cmd.ExternalID().empty())
|
||||
{
|
||||
_ExternalIDToSpecialCommands.emplace(cmd.ExternalID(), cmd);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -451,6 +470,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// Add the new command to the KeyMap.
|
||||
// This map directs you to an entry in the ActionMap.
|
||||
|
||||
// Action IDs:
|
||||
// cmd.ExternalID and cmd.Action have a one-to-one relationship.
|
||||
|
||||
// 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.
|
||||
|
@ -465,10 +487,55 @@ 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);
|
||||
|
||||
_TryUpdateName(cmd, oldCmd, maskingCmd);
|
||||
_TryUpdateKeyChord(cmd, oldCmd, maskingCmd);
|
||||
// _TryUpdateActionWithID may update mergedCmd
|
||||
// that should be passed through the rest of the update functions.
|
||||
Model::Command mergedCmd{ cmd };
|
||||
if (!cmd.ExternalID().empty())
|
||||
{
|
||||
// Try to find a use of the ID in our layer, then in our parents.
|
||||
// If found in our layer, set it to oldCmd. If found in parents,
|
||||
// set it to maskingCmd.
|
||||
const auto& externalPair = _ExternalIDMap.find(cmd.ExternalID());
|
||||
if (externalPair != _ExternalIDMap.end())
|
||||
{
|
||||
oldCmd = _ActionMap.find(externalPair->second)->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
// The user is referring to an action defined in another layer.
|
||||
// We should make a copy of that action, apply the incoming action
|
||||
// on top of it (replace action, name, etc.), then re-add the action
|
||||
for (const auto& parent : _parents)
|
||||
{
|
||||
auto parentAction = parent->GetActionByExternalID(cmd.ExternalID());
|
||||
if (parentAction && *parentAction)
|
||||
{
|
||||
// We've found an action in a parent with the ID, so we want to update
|
||||
// that parent action with whatever we've got in our command.
|
||||
// In order to update, we'll make a copy of the parent action, layer
|
||||
// our command on top of it, and re-add it to the parent layer.
|
||||
const auto parentActionImpl{ get_self<implementation::Command>(*parentAction) };
|
||||
const auto parentActionCopy{ parentActionImpl->Copy() };
|
||||
parentActionCopy->LayerCommand(cmd);
|
||||
|
||||
parent->DeleteAction(Hash(parentAction->ActionAndArgs()));
|
||||
parent->AddAction(*parentActionCopy);
|
||||
|
||||
mergedCmd = *parentActionCopy;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_ExternalIDMap.emplace(cmd.ExternalID(), Hash(mergedCmd.ActionAndArgs()));
|
||||
}
|
||||
}
|
||||
|
||||
_TryUpdateActionMap(mergedCmd, oldCmd, maskingCmd);
|
||||
|
||||
_TryUpdateExternalID(mergedCmd, oldCmd, maskingCmd);
|
||||
_TryUpdateName(mergedCmd, oldCmd, maskingCmd);
|
||||
_TryUpdateKeyChord(mergedCmd, oldCmd, maskingCmd);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -481,21 +548,25 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// - 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())
|
||||
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> add the action in for the first time
|
||||
// { "command": "copy", "keys": "ctrl+shift+c" } --> update oldCmd
|
||||
if (!oldCmd)
|
||||
{
|
||||
// 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;
|
||||
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
|
||||
|
@ -516,7 +587,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// 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) };
|
||||
auto inheritedCmd{ parent->_GetActionByID(actionID) };
|
||||
if (inheritedCmd && *inheritedCmd)
|
||||
{
|
||||
const auto& inheritedCmdImpl{ get_self<Command>(*inheritedCmd) };
|
||||
|
@ -533,6 +604,76 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
}
|
||||
}
|
||||
|
||||
void ActionMap::_StashExternalIDActions(const Model::Command& cmd)
|
||||
{
|
||||
// As we continue to come across action definitions with external
|
||||
// IDs defined, we'll merge the action info into one object.
|
||||
// We'll always take the latest name and command, and we'll register
|
||||
// all the keys that are defined to this action.
|
||||
//
|
||||
// Once we've finished parsing the actions array for the layer,
|
||||
// we'll iterate through our resulting actions and add them
|
||||
// to the action map.
|
||||
//
|
||||
// Example:
|
||||
// { "id": "customaction", "keys": "ctrl+shift+g"},
|
||||
// { "id": "customaction", "name": "customaction"},
|
||||
// { "id": "customaction", "command": "copy"}, --> Results in {"id": "customaction", "command": "copy", "keys": "ctrl+shift+g", "name": "customaction"}
|
||||
// { "id": "customaction", "keys": "ctrl+v"},
|
||||
// { "id": "customaction", "command": "paste"} --> Results in {"id": "customaction", "command": "paste", "keys": {"ctrl+shift+g", "ctrl+v"}, "name": "customaction"}
|
||||
//
|
||||
if (!cmd.ExternalID().empty())
|
||||
{
|
||||
// Add it to incomplete, or combine it with an existing incomplete.
|
||||
auto stagingPair{ _ExternalIDStaging.find(cmd.ExternalID()) };
|
||||
if (stagingPair == _ExternalIDStaging.end())
|
||||
{
|
||||
_ExternalIDStaging.emplace(cmd.ExternalID(), cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Merge the incoming cmd with the one found in staging.
|
||||
// We'll overwrite the staging cmd's values if the incoming cmd has those values.
|
||||
const auto incompleteImpl{ get_self<Command>(stagingPair->second) };
|
||||
incompleteImpl->LayerCommand(cmd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ActionMap::_AddStashedActions()
|
||||
{
|
||||
for (const auto& [id, cmd]: _ExternalIDStaging)
|
||||
{
|
||||
AddAction(cmd);
|
||||
}
|
||||
_ExternalIDStaging.clear();
|
||||
}
|
||||
|
||||
void ActionMap::_TryUpdateExternalID(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& maskingCmd)
|
||||
{
|
||||
if (cmd.ExternalID().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const auto externalID{ cmd.ExternalID() };
|
||||
if (oldCmd)
|
||||
{
|
||||
if (oldCmd.ExternalID().empty())
|
||||
{
|
||||
oldCmd.ExternalID(externalID);
|
||||
}
|
||||
}
|
||||
|
||||
if (maskingCmd)
|
||||
{
|
||||
if (maskingCmd.ExternalID().empty())
|
||||
{
|
||||
maskingCmd.ExternalID(externalID);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Update our internal state with the name of the newly registered action
|
||||
// Arguments:
|
||||
|
@ -600,84 +741,93 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
// 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)
|
||||
const auto cmdKeys{ cmd.Keys() };
|
||||
if (!cmdKeys)
|
||||
{
|
||||
// the user is not trying to update the keys.
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle collisions
|
||||
const auto oldKeyPair{ _KeyMap.find(keys) };
|
||||
if (oldKeyPair != _KeyMap.end())
|
||||
// Loop through all key mappings in the command
|
||||
// Normally, new incoming commands should not have multiple
|
||||
// keymappings, but the way that actions with external IDs defined
|
||||
// are constructed make it so that the action will have multiple
|
||||
// mappings associated.
|
||||
const auto cmdImpl { get_self<implementation::Command>(cmd) };
|
||||
for (const auto& keys : cmdImpl->KeyMappings())
|
||||
{
|
||||
// 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<implementation::Command>(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())
|
||||
// Handle collisions
|
||||
const auto oldKeyPair{ _KeyMap.find(keys) };
|
||||
if (oldKeyPair != _KeyMap.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.
|
||||
// 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<implementation::Command>(conflictingCmd) };
|
||||
const auto conflictingCmdCopy{ conflictingCmdImpl->Copy() };
|
||||
conflictingCmdCopy->EraseKey(keys);
|
||||
_MaskingActions.emplace(conflictingActionID, *conflictingCmdCopy);
|
||||
conflictingCmdImpl->EraseKey(keys);
|
||||
}
|
||||
else
|
||||
else if (const auto& conflictingCmd{ GetActionByKeyChord(keys) })
|
||||
{
|
||||
// We've collided with this action before. Let's resolve a collision with a masking action.
|
||||
const auto maskingCmdImpl{ get_self<implementation::Command>(maskingCmdPair->second) };
|
||||
maskingCmdImpl->EraseKey(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<implementation::Command>(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<implementation::Command>(maskingCmdPair->second) };
|
||||
maskingCmdImpl->EraseKey(keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Assign the new action in the _KeyMap.
|
||||
const auto actionID{ Hash(cmd.ActionAndArgs()) };
|
||||
_KeyMap.insert_or_assign(keys, actionID);
|
||||
// Assign the new action in the _KeyMap.
|
||||
const auto actionID{ Hash(cmd.ActionAndArgs()) };
|
||||
_KeyMap.insert_or_assign(keys, actionID);
|
||||
|
||||
// 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<Command>(oldCmd) };
|
||||
oldCmdImpl->RegisterKey(keys);
|
||||
}
|
||||
// Additive operation:
|
||||
// Register the new key chord with oldCmd (an existing _ActionMap entry)
|
||||
// Example:
|
||||
// { "command": "copy", "keys": "ctrl+c" } --> register "ctrl+c" (section above)
|
||||
// { "command": "copy", "keys": "ctrl+shift+c" } --> also register "ctrl+shift+c" to the same Command (oldCmd)
|
||||
if (oldCmd)
|
||||
{
|
||||
// Update inner Command with new key chord
|
||||
auto oldCmdImpl{ get_self<Command>(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<Command>(maskingCmd) };
|
||||
maskingCmdImpl->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<Command>(maskingCmd) };
|
||||
maskingCmdImpl->RegisterKey(keys);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -792,6 +942,55 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return nullptr;
|
||||
}
|
||||
|
||||
std::optional<Model::Command> ActionMap::GetActionByExternalID(const winrt::hstring& externalID) const
|
||||
{
|
||||
const auto idMapPair{ _ExternalIDMap.find(externalID) };
|
||||
const auto specialMapPair{ _ExternalIDToSpecialCommands.find(externalID) };
|
||||
|
||||
if (idMapPair != _ExternalIDMap.end())
|
||||
{
|
||||
return _GetActionByID(idMapPair->second);
|
||||
}
|
||||
else if (specialMapPair != _ExternalIDToSpecialCommands.end())
|
||||
{
|
||||
return specialMapPair->second;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void ActionMap::DeleteAction(const InternalActionID& id)
|
||||
{
|
||||
if (auto action = _GetActionByID(id))
|
||||
{
|
||||
// Removing from KeyMap
|
||||
const auto& actionImpl = get_self<implementation::Command>(*action);
|
||||
for (const auto& keychord : actionImpl->KeyMappings())
|
||||
{
|
||||
// Double check that the keychord of this command is mapped to itself.
|
||||
auto keyMapPair = _KeyMap.find(keychord);
|
||||
if (keyMapPair != _KeyMap.end())
|
||||
{
|
||||
if (keyMapPair->second == id)
|
||||
{
|
||||
_KeyMap.erase(keychord);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Removing from ExternalIDMap
|
||||
if (!action->ExternalID().empty())
|
||||
{
|
||||
_ExternalIDMap.erase(action->ExternalID());
|
||||
}
|
||||
|
||||
// Remove from ActionMap
|
||||
_ActionMap.erase(id);
|
||||
|
||||
// TODO: How will I remove it if it's Nested/Iterable commands?
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Rebinds a key binding to a new key chord
|
||||
// Arguments:
|
||||
|
|
|
@ -62,6 +62,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
bool IsKeyChordExplicitlyUnbound(Control::KeyChord const& keys) const;
|
||||
Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action) const;
|
||||
Control::KeyChord GetKeyBindingForAction(ShortcutAction const& action, IActionArgs const& actionArgs) const;
|
||||
std::optional<Model::Command> GetActionByExternalID(const winrt::hstring& externalId) const;
|
||||
|
||||
// population
|
||||
void AddAction(const Model::Command& cmd);
|
||||
|
@ -72,6 +73,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
Json::Value ToJson() const;
|
||||
|
||||
// modification
|
||||
void DeleteAction(const InternalActionID& id);
|
||||
bool RebindKeys(Control::KeyChord const& oldKeys, Control::KeyChord const& newKeys);
|
||||
void DeleteKeyBinding(Control::KeyChord const& keys);
|
||||
void RegisterKeyBinding(Control::KeyChord keys, Model::ActionAndArgs action);
|
||||
|
@ -90,6 +92,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
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 _TryUpdateExternalID(const Model::Command& cmd, Model::Command& oldCmd, Model::Command& consolidatedCmd);
|
||||
void _AddStashedActions();
|
||||
void _StashExternalIDActions(const Model::Command& cmd);
|
||||
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::ActionAndArgs> _AvailableActionsCache{ nullptr };
|
||||
Windows::Foundation::Collections::IMap<hstring, Model::Command> _NameMapCache{ nullptr };
|
||||
|
@ -100,6 +105,9 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
std::vector<Model::Command> _IterableCommands;
|
||||
std::unordered_map<Control::KeyChord, InternalActionID, KeyChordHash, KeyChordEquality> _KeyMap;
|
||||
std::unordered_map<InternalActionID, Model::Command> _ActionMap;
|
||||
std::unordered_map<winrt::hstring, InternalActionID> _ExternalIDMap;
|
||||
std::unordered_map<winrt::hstring, Model::Command> _ExternalIDToSpecialCommands;
|
||||
std::unordered_map<winrt::hstring, Model::Command> _ExternalIDStaging;
|
||||
|
||||
// Masking Actions:
|
||||
// These are actions that were introduced in an ancestor,
|
||||
|
|
|
@ -11,6 +11,7 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
Boolean IsKeyChordExplicitlyUnbound(Microsoft.Terminal.Control.KeyChord keys);
|
||||
|
||||
Command GetActionByKeyChord(Microsoft.Terminal.Control.KeyChord keys);
|
||||
//Command GetActionByExternalID(String externalID);
|
||||
|
||||
Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action);
|
||||
[method_name("GetKeyBindingForActionWithArgs")] Microsoft.Terminal.Control.KeyChord GetKeyBindingForAction(ShortcutAction action, IActionArgs actionArgs);
|
||||
|
|
|
@ -50,9 +50,20 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
continue;
|
||||
}
|
||||
|
||||
AddAction(*Command::FromJson(cmdJson, warnings));
|
||||
const auto& cmd = Command::FromJson(cmdJson, warnings);
|
||||
|
||||
if (!cmd->ExternalID().empty())
|
||||
{
|
||||
_StashExternalIDActions(*cmd);
|
||||
}
|
||||
else
|
||||
{
|
||||
AddAction(*cmd);
|
||||
}
|
||||
}
|
||||
|
||||
_AddStashedActions();
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ static constexpr std::string_view ArgsKey{ "args" };
|
|||
static constexpr std::string_view IterateOnKey{ "iterateOn" };
|
||||
static constexpr std::string_view CommandsKey{ "commands" };
|
||||
static constexpr std::string_view KeysKey{ "keys" };
|
||||
static constexpr std::string_view IdKey{ "id" };
|
||||
|
||||
static constexpr std::string_view ProfileNameToken{ "${profile.name}" };
|
||||
static constexpr std::string_view ProfileIconToken{ "${profile.icon}" };
|
||||
|
@ -66,6 +67,16 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
return _subcommands ? _subcommands.GetView() : nullptr;
|
||||
}
|
||||
|
||||
void Command::NestedCommands(const IMapView<winrt::hstring, Model::Command>& commands)
|
||||
{
|
||||
_subcommands.Clear();
|
||||
for (auto kv : commands)
|
||||
{
|
||||
const auto subCmd{ winrt::get_self<Command>(kv.Value()) };
|
||||
_subcommands.Insert(kv.Key(), *subCmd->Copy());
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - reports if the current command has nested commands
|
||||
// - This CANNOT detect { "name": "foo", "commands": null }
|
||||
|
@ -254,6 +265,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
if (const auto idJson{ json[JsonKey(IdKey)] })
|
||||
{
|
||||
if (idJson.isString())
|
||||
{
|
||||
result->ExternalID(to_hstring(idJson.asCString()));
|
||||
}
|
||||
}
|
||||
|
||||
bool nested = false;
|
||||
JsonUtils::GetValueForKey(json, IterateOnKey, result->_IterateOn);
|
||||
|
||||
|
@ -637,4 +656,38 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
|
||||
return newCommands;
|
||||
}
|
||||
|
||||
void Command::LayerCommand(const Model::Command& cmd)
|
||||
{
|
||||
// Merge the incoming cmd with the one found in staging.
|
||||
// We'll overwrite the staging cmd's values if the incoming cmd has those values.
|
||||
const auto cmdImpl{ get_self<Command>(cmd) };
|
||||
if (cmdImpl->HasName())
|
||||
{
|
||||
Name(cmdImpl->Name());
|
||||
}
|
||||
|
||||
for (const auto& keys : cmdImpl->KeyMappings())
|
||||
{
|
||||
RegisterKey(keys);
|
||||
}
|
||||
|
||||
if (!cmdImpl->IconPath().empty())
|
||||
{
|
||||
IconPath(cmd.IconPath());
|
||||
}
|
||||
|
||||
// Copy over the nested commands if there are some.
|
||||
// Otherwise just copy the ActionAndArgs if they're valid.
|
||||
IterateOn(cmdImpl->IterateOn());
|
||||
if (cmdImpl->HasNestedCommands())
|
||||
{
|
||||
NestedCommands(cmd.NestedCommands());
|
||||
}
|
||||
|
||||
if (cmd.ActionAndArgs().Action() != ShortcutAction::Invalid)
|
||||
{
|
||||
ActionAndArgs(cmd.ActionAndArgs());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -38,6 +38,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
{
|
||||
Command();
|
||||
com_ptr<Command> Copy() const;
|
||||
void Merge(const com_ptr<Command>& cmd);
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json,
|
||||
std::vector<SettingsLoadWarnings>& warnings);
|
||||
|
@ -54,6 +55,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
bool HasNestedCommands() const;
|
||||
bool IsNestedCommand() const noexcept;
|
||||
Windows::Foundation::Collections::IMapView<winrt::hstring, Model::Command> NestedCommands() const;
|
||||
void NestedCommands(const Windows::Foundation::Collections::IMapView<winrt::hstring, Model::Command>& commands);
|
||||
|
||||
bool HasName() const noexcept;
|
||||
hstring Name() const noexcept;
|
||||
|
@ -68,11 +70,14 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation
|
|||
hstring IconPath() const noexcept;
|
||||
void IconPath(const hstring& val);
|
||||
|
||||
void LayerCommand(const Model::Command& cmd);
|
||||
|
||||
winrt::Windows::UI::Xaml::Data::INotifyPropertyChanged::PropertyChanged_revoker propertyChangedRevoker;
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
WINRT_PROPERTY(ExpandCommandType, IterateOn, ExpandCommandType::None);
|
||||
WINRT_PROPERTY(Model::ActionAndArgs, ActionAndArgs);
|
||||
WINRT_PROPERTY(winrt::hstring, ExternalID);
|
||||
|
||||
private:
|
||||
Json::Value _originalJson;
|
||||
|
|
|
@ -12,7 +12,8 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
{
|
||||
enum ShortcutAction
|
||||
{
|
||||
Invalid = 0, // treat Invalid as unbound actions
|
||||
Invalid = 0,
|
||||
Unbound = 1,
|
||||
|
||||
// When adding a new action, add them to AllShortcutActions.h!
|
||||
#define ON_ALL_ACTIONS(action) action,
|
||||
|
@ -26,6 +27,8 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
|
||||
IActionArgs Args;
|
||||
ShortcutAction Action;
|
||||
|
||||
Boolean IsUnbound();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
|
@ -40,6 +43,8 @@ namespace Microsoft.Terminal.Settings.Model
|
|||
|
||||
String IconPath;
|
||||
|
||||
String ExternalID;
|
||||
|
||||
Boolean HasNestedCommands { get; };
|
||||
Windows.Foundation.Collections.IMapView<String, Command> NestedCommands { get; };
|
||||
|
||||
|
|
|
@ -373,8 +373,8 @@
|
|||
// Clipboard Integration
|
||||
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+shift+c" },
|
||||
{ "command": { "action": "copy", "singleLine": false }, "keys": "ctrl+insert" },
|
||||
{ "command": "paste", "keys": "ctrl+shift+v" },
|
||||
{ "command": "paste", "keys": "shift+insert" },
|
||||
{ "id": "Terminal-Paste", "command": "paste", "keys": "ctrl+shift+v" },
|
||||
{ "id": "Terminal-Paste", "command": "paste", "keys": "shift+insert" },
|
||||
|
||||
// Scrollback
|
||||
{ "command": "scrollDown", "keys": "ctrl+shift+down" },
|
||||
|
|
Loading…
Reference in a new issue