Compare commits

...

5 commits

Author SHA1 Message Date
Leon Liang b278f2faf8 good lord 2021-09-22 14:12:34 -07:00
Leon Liang d493f6e551 so it mostly works, haven't tested nested or iterable commands 2021-09-13 23:02:23 -07:00
Leon Liang a6b8d35684 saving progress... 2021-09-13 10:04:27 -07:00
Leon Liang 387b057c79 Merge branch 'main' into dev/lelian/actionid/1 2021-09-07 14:41:51 -07:00
Leon Liang 60aefc058d saving progress... 2021-09-02 11:40:38 -07:00
10 changed files with 401 additions and 105 deletions

View file

@ -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

View file

@ -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);
};

View file

@ -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:

View file

@ -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,

View file

@ -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);

View file

@ -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;
}

View file

@ -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());
}
}
}

View file

@ -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;

View file

@ -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; };

View file

@ -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" },