Add support for arbitrary args in keybindings (#3391)

## Summary of the Pull Request

Enables the user to provide arbitrary argument values to shortcut actions through a new `args` member of keybindings. For some keybindings, like `NewTabWithProfile<N>`, we previously needed 9 different `ShortcutAction`s, one for each value of `Index`. If a user wanted to have a `NewTabWithProfile11` keybinding, that was simply impossible. Now that the args are in their own separate json object, each binding can accept any number of arbitrary argument values.

So instead of:
```json
        { "command": "newTab", "keys": ["ctrl+shift+t"] },
        { "command": "newTabProfile0", "keys": ["ctrl+shift+1"] },
        { "command": "newTabProfile1", "keys": ["ctrl+shift+2"] },
        { "command": "newTabProfile2", "keys": ["ctrl+shift+3"] },
        { "command": "newTabProfile3", "keys": ["ctrl+shift+4"] },
```

We can now use:

```json
        { "command": "newTab", "keys": ["ctrl+shift+t"] },
        { "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"] },
```

Initially, this does seem more verbose. However, for cases where there are multiple args, or there's a large range of values for the args, this will quickly become a more powerful system of expressing keybindings.

The "legacy" keybindings are _left in_ in this PR. They have helper methods to generate appropriate `IActionArgs` values. Prior to releasing 1.0, I think we should remove them, if only to remove some code bloat.

## References

See [the spec](https://github.com/microsoft/terminal/blob/master/doc/specs/%231142%20-%20Keybinding%20Arguments.md) for more details.

This is part two of the implementation, part one was #2446

## PR Checklist
* [x] Closes #1142
* [x] I work here
* [x] Tests added/passed
* [x] Schema updated

## Validation Steps Performed

* Ran Tests
* Removed the legacy keybindings from the `defaults.json`, everything still works
* Tried leaving the legacy keybingings in my `profiles.json`, everything still works.

-------------------------------------------------
* this is a start, but there's a weird linker bug if I take the SetKeybinding(ShortcutAction, KeyChord) implementation out, which I don't totally understand

* a good old-fashioned clean will fix that right up

* all these things work

* hey this actually _functionally_ works

* Mostly cleanup and completion of implementation

* Hey I bet we could just make NewTab the handler for NewTabWithProfile

* Start writing tests for Keybinding args

* Add tests

* Revert a bad sln change, and clean out dead code

* Change to include "command" as a single object

  This is a change to make @dhowett-msft happy. Changes the args to be a part
  of the "command" object, as opposed to an object on their own.

  EX:

  ```jsonc

    // Old style
    { "command": "switchToTab0", "keys": ["ctrl+1"] },
    { "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },

    // new style
    { "command": "switchToTab0", "keys": ["ctrl+1"] },
    { "command": "switchToTab", "args": { "index": 0 } "keys": ["ctrl+alt+1"] },

  ```

* schemas are hard yo

* Fix the build?

* wonder why my -Wall settings are different than CI...

* this makes me hate things

* Comments from PR

  * Add a `Direction::None`
  * LOAD BEARING
  * add some GH ids to TODOs

* add a comment

* PR nits from carlos
This commit is contained in:
Mike Griese 2019-11-14 16:23:40 -06:00 committed by GitHub
parent d552959378
commit 6a4c737686
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 900 additions and 403 deletions

View file

@ -13,62 +13,177 @@
"pattern": "^\\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\\}$",
"type": "string"
},
"ShortcutActionName": {
"enum": [
"closePane",
"closeTab",
"closeWindow",
"copy",
"copyTextWithoutNewlines",
"decreaseFontSize",
"duplicateTab",
"increaseFontSize",
"moveFocus",
"moveFocusDown",
"moveFocusLeft",
"moveFocusRight",
"moveFocusUp",
"newTab",
"newTabProfile0",
"newTabProfile1",
"newTabProfile2",
"newTabProfile3",
"newTabProfile4",
"newTabProfile5",
"newTabProfile6",
"newTabProfile7",
"newTabProfile8",
"nextTab",
"openNewTabDropdown",
"openSettings",
"paste",
"prevTab",
"resizePane",
"resizePaneDown",
"resizePaneLeft",
"resizePaneRight",
"resizePaneUp",
"scrollDown",
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"splitHorizontal",
"splitVertical",
"switchToTab",
"switchToTab0",
"switchToTab1",
"switchToTab2",
"switchToTab3",
"switchToTab4",
"switchToTab5",
"switchToTab6",
"switchToTab7",
"switchToTab8",
"toggleFullscreen"
],
"type": "string"
},
"Direction": {
"enum": [
"left",
"right",
"up",
"down"
],
"type": "string"
},
"ShortcutAction": {
"properties": {
"action": {
"description": "The action to execute",
"$ref": "#/definitions/ShortcutActionName"
}
},
"required": [
"action"
],
"type": "object"
},
"CopyAction": {
"description": "Arguments corresponding to a Copy Text Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "copy" },
"trimWhitespace": {
"type": "boolean",
"default": false,
"description": "If true, will trim whitespace from the end of the line on copy."
}
}
}
]
},
"NewTabAction": {
"description": "Arguments corresponding to a New Tab Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type":"string", "pattern": "newTab" },
"index": {
"type": "integer",
"description": "The index in the new tab dropdown to open in a new tab"
}
}
}
]
},
"SwitchToTabAction": {
"description": "Arguments corresponding to a Switch To Tab Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "switchToTab" },
"index": {
"type": "integer",
"default": 0,
"description": "Which tab to switch to, with the first being 0"
}
}
}
],
"required": [ "index" ]
},
"MoveFocusAction": {
"description": "Arguments corresponding to a Move Focus Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "moveFocus" },
"direction": {
"$ref": "#/definitions/Direction",
"default": "left",
"description": "The direction to move focus in, between panes"
}
}
}
],
"required": [ "direction" ]
},
"ResizePaneAction": {
"description": "Arguments corresponding to a Resize Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "resizePane" },
"direction": {
"$ref": "#/definitions/Direction",
"default": "left",
"description": "The direction to move the pane separator in"
}
}
}
],
"required": [ "direction" ]
},
"Keybinding": {
"additionalProperties": false,
"properties": {
"command": {
"description": "The command executed when the associated key bindings are pressed.",
"enum": [
"closePane",
"closeTab",
"closeWindow",
"copy",
"copyTextWithoutNewlines",
"decreaseFontSize",
"duplicateTab",
"increaseFontSize",
"moveFocusDown",
"moveFocusLeft",
"moveFocusRight",
"moveFocusUp",
"newTab",
"newTabProfile0",
"newTabProfile1",
"newTabProfile2",
"newTabProfile3",
"newTabProfile4",
"newTabProfile5",
"newTabProfile6",
"newTabProfile7",
"newTabProfile8",
"nextTab",
"openNewTabDropdown",
"openSettings",
"paste",
"prevTab",
"resizePaneDown",
"resizePaneLeft",
"resizePaneRight",
"resizePaneUp",
"scrollDown",
"scrollDownPage",
"scrollUp",
"scrollUpPage",
"splitHorizontal",
"splitVertical",
"switchToTab",
"switchToTab0",
"switchToTab1",
"switchToTab2",
"switchToTab3",
"switchToTab4",
"switchToTab5",
"switchToTab6",
"switchToTab7",
"switchToTab8",
"toggleFullscreen"
],
"type": "string"
"description": "The action executed when the associated key bindings are pressed.",
"oneOf": [
{ "$ref": "#/definitions/CopyAction" },
{ "$ref": "#/definitions/ShortcutActionName" },
{ "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/ResizePaneAction" }
]
},
"keys": {
"description": "Defines the key combinations used to call the command.",

View file

@ -9,6 +9,8 @@
using namespace Microsoft::Console;
using namespace TerminalApp;
using namespace winrt::TerminalApp;
using namespace winrt::Microsoft::Terminal::Settings;
using namespace WEX::Logging;
using namespace WEX::TestExecution;
using namespace WEX::Common;
@ -35,11 +37,33 @@ namespace TerminalAppLocalTests
TEST_METHOD(LayerKeybindings);
TEST_METHOD(UnbindKeybindings);
TEST_METHOD(TestArbitraryArgs);
TEST_CLASS_SETUP(ClassSetup)
{
InitializeJsonReader();
return true;
}
// Function Description:
// - This is a helper to retrieve the ActionAndArgs from the keybindings
// for a given chord.
// Arguments:
// - bindings: The AppKeyBindings to lookup the ActionAndArgs from.
// - kc: The key chord to look up the bound ActionAndArgs for.
// Return Value:
// - The ActionAndArgs bound to the given key, or nullptr if nothing is bound to it.
static const ActionAndArgs KeyBindingsTests::GetActionAndArgs(const implementation::AppKeyBindings& bindings,
const KeyChord& kc)
{
const auto keyIter = bindings._keyShortcuts.find(kc);
VERIFY_IS_TRUE(keyIter != bindings._keyShortcuts.end(), L"Expected to find an action bound to the given KeyChord");
if (keyIter != bindings._keyShortcuts.end())
{
return keyIter->second;
}
return nullptr;
};
};
void KeyBindingsTests::ManyKeysSameAction()
@ -156,4 +180,162 @@ namespace TerminalAppLocalTests
appKeyBindings->LayerJson(bindings2Json);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
}
void KeyBindingsTests::TestArbitraryArgs()
{
const std::string bindings0String{ R"([
{ "command": "copy", "keys": ["ctrl+c"] },
{ "command": "copyTextWithoutNewlines", "keys": ["alt+c"] },
{ "command": { "action": "copy", "trimWhitespace": false }, "keys": ["ctrl+shift+c"] },
{ "command": { "action": "copy", "trimWhitespace": true }, "keys": ["alt+shift+c"] },
{ "command": "newTab", "keys": ["ctrl+t"] },
{ "command": { "action": "newTab", "index": 0 }, "keys": ["ctrl+shift+t"] },
{ "command": "newTabProfile0", "keys": ["alt+shift+t"] },
{ "command": { "action": "newTab", "index": 11 }, "keys": ["ctrl+shift+y"] },
{ "command": "newTabProfile8", "keys": ["alt+shift+y"] },
{ "command": { "action": "copy", "madeUpBool": true }, "keys": ["ctrl+b"] },
{ "command": { "action": "copy" }, "keys": ["ctrl+shift+b"] }
])" };
const auto bindings0Json = VerifyParseSucceeded(bindings0String);
auto appKeyBindings = winrt::make_self<implementation::AppKeyBindings>();
VERIFY_IS_NOT_NULL(appKeyBindings);
VERIFY_ARE_EQUAL(0u, appKeyBindings->_keyShortcuts.size());
appKeyBindings->LayerJson(bindings0Json);
VERIFY_ARE_EQUAL(11u, appKeyBindings->_keyShortcuts.size());
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` without args parses as Copy(TrimWhitespace=false)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copyTextWithoutNewlines` parses as Copy(TrimWhitespace=true)"));
KeyChord kc{ false, true, false, static_cast<int32_t>('C') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('C') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` with args parses them correctly"));
KeyChord kc{ false, true, true, static_cast<int32_t>('C') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_TRUE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` without args parses as NewTab(Index=null)"));
KeyChord kc{ true, false, false, static_cast<int32_t>('T') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NULL(realArgs.ProfileIndex());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` parses args correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('T') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.ProfileIndex());
VERIFY_ARE_EQUAL(0, realArgs.ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTabProfile0` parses as NewTab(Index=0)"));
KeyChord kc{ false, true, true, static_cast<int32_t>('T') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTabProfile0, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.ProfileIndex());
VERIFY_ARE_EQUAL(0, realArgs.ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTab` with an index greater than the legacy "
L"args afforded parses correctly"));
KeyChord kc{ true, false, true, static_cast<int32_t>('Y') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.ProfileIndex());
VERIFY_ARE_EQUAL(11, realArgs.ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `newTabProfile8` parses as NewTab(Index=8)"));
KeyChord kc{ false, true, true, static_cast<int32_t>('Y') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::NewTabProfile8, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_NOT_NULL(realArgs.ProfileIndex());
VERIFY_ARE_EQUAL(8, realArgs.ProfileIndex().Value());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` ignores args it doesn't understand"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
}
{
Log::Comment(NoThrowString().Format(
L"Verify that `copy` null as it's `args` parses as the default option"));
KeyChord kc{ true, false, true, static_cast<int32_t>('B') };
auto actionAndArgs = GetActionAndArgs(*appKeyBindings, kc);
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, actionAndArgs.Action());
const auto& realArgs = actionAndArgs.Args().try_as<CopyTextArgs>();
VERIFY_IS_NOT_NULL(realArgs);
// Verify the args have the expected value
VERIFY_IS_FALSE(realArgs.TrimWhitespace());
}
}
}

View file

@ -0,0 +1,11 @@
#include "pch.h"
#include "ActionAndArgs.h"
#include "ActionAndArgs.g.cpp"
// We define everything necessary for the ActionAndArgs class in the header, but
// we still need this file to compile the ActionAndArgs.g.cpp file, and we can't
// just include that file in the header.
namespace winrt::TerminalApp::implementation
{
}

View file

@ -0,0 +1,18 @@
#pragma once
#include "ActionAndArgs.g.h"
#include "..\inc\cppwinrt_utils.h"
namespace winrt::TerminalApp::implementation
{
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
{
ActionAndArgs() = default;
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
GETSET_PROPERTY(IActionArgs, Args, nullptr);
};
}
namespace winrt::TerminalApp::factory_implementation
{
BASIC_FACTORY(ActionAndArgs);
}

View file

@ -7,7 +7,7 @@
#include "ActionEventArgs.g.cpp"
#include "CopyTextArgs.g.cpp"
#include "NewTabWithProfileArgs.g.cpp"
#include "NewTabArgs.g.cpp"
#include "SwitchToTabArgs.g.cpp"
#include "ResizePaneArgs.g.cpp"
#include "MoveFocusArgs.g.cpp"

View file

@ -7,13 +7,14 @@
// *.g.cpp to ActionArgs.cpp!
#include "ActionEventArgs.g.h"
#include "CopyTextArgs.g.h"
#include "NewTabWithProfileArgs.g.h"
#include "NewTabArgs.g.h"
#include "SwitchToTabArgs.g.h"
#include "ResizePaneArgs.g.h"
#include "MoveFocusArgs.g.h"
#include "AdjustFontSizeArgs.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include "Utils.h"
// Notes on defining ActionArgs and ActionEventArgs:
// * All properties specific to an action should be defined as an ActionArgs
@ -36,30 +37,135 @@ namespace winrt::TerminalApp::implementation
{
CopyTextArgs() = default;
GETSET_PROPERTY(bool, TrimWhitespace, false);
static constexpr std::string_view TrimWhitespaceKey{ "trimWhitespace" };
public:
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<CopyTextArgs>();
if (auto trimWhitespace{ json[JsonKey(TrimWhitespaceKey)] })
{
args->_TrimWhitespace = trimWhitespace.asBool();
}
return *args;
}
};
struct NewTabWithProfileArgs : public NewTabWithProfileArgsT<NewTabWithProfileArgs>
struct NewTabArgs : public NewTabArgsT<NewTabArgs>
{
NewTabWithProfileArgs() = default;
GETSET_PROPERTY(int32_t, ProfileIndex, 0);
NewTabArgs() = default;
GETSET_PROPERTY(Windows::Foundation::IReference<int32_t>, ProfileIndex, nullptr);
static constexpr std::string_view ProfileIndexKey{ "index" };
public:
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<NewTabArgs>();
if (auto profileIndex{ json[JsonKey(ProfileIndexKey)] })
{
args->_ProfileIndex = profileIndex.asInt();
}
return *args;
}
};
struct SwitchToTabArgs : public SwitchToTabArgsT<SwitchToTabArgs>
{
SwitchToTabArgs() = default;
GETSET_PROPERTY(int32_t, TabIndex, 0);
static constexpr std::string_view TabIndexKey{ "index" };
public:
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<SwitchToTabArgs>();
if (auto tabIndex{ json[JsonKey(TabIndexKey)] })
{
args->_TabIndex = tabIndex.asInt();
}
return *args;
}
};
// Possible Direction values
// TODO:GH#2550/#3475 - move these to a centralized deserializing place
static constexpr std::string_view LeftString{ "left" };
static constexpr std::string_view RightString{ "right" };
static constexpr std::string_view UpString{ "up" };
static constexpr std::string_view DownString{ "down" };
// Function Description:
// - Helper function for parsing a Direction from a string
// Arguments:
// - directionString: the string to attempt to parse
// Return Value:
// - The encoded Direction value, or Direction::None if it was an invalid string
static TerminalApp::Direction ParseDirection(const std::string& directionString)
{
if (directionString == LeftString)
{
return TerminalApp::Direction::Left;
}
else if (directionString == RightString)
{
return TerminalApp::Direction::Right;
}
else if (directionString == UpString)
{
return TerminalApp::Direction::Up;
}
else if (directionString == DownString)
{
return TerminalApp::Direction::Down;
}
// default behavior for invalid data
return TerminalApp::Direction::None;
};
struct ResizePaneArgs : public ResizePaneArgsT<ResizePaneArgs>
{
ResizePaneArgs() = default;
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left);
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::None);
static constexpr std::string_view DirectionKey{ "direction" };
public:
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<ResizePaneArgs>();
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
return *args;
}
};
struct MoveFocusArgs : public MoveFocusArgsT<MoveFocusArgs>
{
MoveFocusArgs() = default;
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::Left);
GETSET_PROPERTY(TerminalApp::Direction, Direction, TerminalApp::Direction::None);
static constexpr std::string_view DirectionKey{ "direction" };
public:
static winrt::TerminalApp::IActionArgs FromJson(const Json::Value& json)
{
// LOAD BEARING: Not using make_self here _will_ break you in the future!
auto args = winrt::make_self<MoveFocusArgs>();
if (auto directionString{ json[JsonKey(DirectionKey)] })
{
args->_Direction = ParseDirection(directionString.asString());
}
return *args;
}
};
struct AdjustFontSizeArgs : public AdjustFontSizeArgsT<AdjustFontSizeArgs>

View file

@ -15,7 +15,8 @@ namespace TerminalApp
enum Direction
{
Left = 0,
None = 0,
Left,
Right,
Up,
Down
@ -31,9 +32,11 @@ namespace TerminalApp
Boolean TrimWhitespace { get; };
};
[default_interface] runtimeclass NewTabWithProfileArgs : IActionArgs
[default_interface] runtimeclass NewTabArgs : IActionArgs
{
Int32 ProfileIndex { get; };
// ProfileIndex can be null (for "use the default"), so this needs to be
// a IReference, so it's nullable
Windows.Foundation.IReference<Int32> ProfileIndex { get; };
};
[default_interface] runtimeclass SwitchToTabArgs : IActionArgs
@ -55,4 +58,5 @@ namespace TerminalApp
{
Int32 Delta { get; };
};
}

View file

@ -26,12 +26,6 @@ namespace winrt
namespace winrt::TerminalApp::implementation
{
void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
_OpenNewTab(std::nullopt);
args.Handled(true);
}
void TerminalPage::_HandleOpenNewTabDropdown(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
@ -138,12 +132,24 @@ namespace winrt::TerminalApp::implementation
args.Handled(true);
}
void TerminalPage::_HandleNewTabWithProfile(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
void TerminalPage::_HandleNewTab(const IInspectable& /*sender*/,
const TerminalApp::ActionEventArgs& args)
{
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::NewTabWithProfileArgs>())
if (args == nullptr)
{
_OpenNewTab({ realArgs.ProfileIndex() });
_OpenNewTab(std::nullopt);
args.Handled(true);
}
else if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::NewTabArgs>())
{
if (realArgs.ProfileIndex() == nullptr)
{
_OpenNewTab(std::nullopt);
}
else
{
_OpenNewTab(realArgs.ProfileIndex().Value());
}
args.Handled(true);
}
}
@ -163,8 +169,16 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::ResizePaneArgs>())
{
_ResizePane(realArgs.Direction());
args.Handled(true);
if (realArgs.Direction() == TerminalApp::Direction::None)
{
// Do nothing
args.Handled(false);
}
else
{
_ResizePane(realArgs.Direction());
args.Handled(true);
}
}
}
@ -173,8 +187,16 @@ namespace winrt::TerminalApp::implementation
{
if (const auto& realArgs = args.ActionArgs().try_as<TerminalApp::MoveFocusArgs>())
{
_MoveFocus(realArgs.Direction());
args.Handled(true);
if (realArgs.Direction() == TerminalApp::Direction::None)
{
// Do nothing
args.Handled(false);
}
else
{
_MoveFocus(realArgs.Direction());
args.Handled(true);
}
}
}

View file

@ -12,10 +12,10 @@ using namespace winrt::TerminalApp;
namespace winrt::TerminalApp::implementation
{
void AppKeyBindings::SetKeyBinding(const TerminalApp::ShortcutAction& action,
void AppKeyBindings::SetKeyBinding(const TerminalApp::ActionAndArgs& actionAndArgs,
const Settings::KeyChord& chord)
{
_keyShortcuts[chord] = action;
_keyShortcuts[chord] = actionAndArgs;
}
// Method Description:
@ -33,7 +33,7 @@ namespace winrt::TerminalApp::implementation
{
for (auto& kv : _keyShortcuts)
{
if (kv.second == action)
if (kv.second.Action() == action)
{
return kv.first;
}
@ -46,374 +46,185 @@ namespace winrt::TerminalApp::implementation
const auto keyIter = _keyShortcuts.find(kc);
if (keyIter != _keyShortcuts.end())
{
const auto action = keyIter->second;
return _DoAction(action);
const auto actionAndArgs = keyIter->second;
return _DoAction(actionAndArgs);
}
return false;
}
bool AppKeyBindings::_DoAction(ShortcutAction action)
bool AppKeyBindings::_DoAction(ActionAndArgs actionAndArgs)
{
const auto& action = actionAndArgs.Action();
const auto& args = actionAndArgs.Args();
auto eventArgs = args ? winrt::make_self<ActionEventArgs>(args) :
winrt::make_self<ActionEventArgs>();
switch (action)
{
case ShortcutAction::CopyText:
{
auto args = winrt::make_self<CopyTextArgs>();
args->TrimWhitespace(true);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_CopyTextHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::CopyTextWithoutNewlines:
{
auto args = winrt::make_self<CopyTextArgs>();
args->TrimWhitespace(false);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_CopyTextHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::PasteText:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_PasteTextHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTab:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_NewTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::OpenNewTabDropdown:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_OpenNewTabDropdownHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::DuplicateTab:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_DuplicateTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::OpenSettings:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_OpenSettingsHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::NewTab:
case ShortcutAction::NewTabProfile0:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(0);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile1:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(1);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile2:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(2);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile3:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(3);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile4:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(4);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile5:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(5);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile6:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(6);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile7:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(7);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::NewTabProfile8:
{
auto args = winrt::make_self<NewTabWithProfileArgs>();
args->ProfileIndex(8);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_NewTabWithProfileHandlers(*this, *eventArgs);
return eventArgs->Handled();
_NewTabHandlers(*this, *eventArgs);
break;
}
case ShortcutAction::NewWindow:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_NewWindowHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::CloseWindow:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_CloseWindowHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::CloseTab:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_CloseTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ClosePane:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ClosePaneHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ScrollUp:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ScrollUpHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ScrollDown:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ScrollDownHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ScrollUpPage:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ScrollUpPageHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ScrollDownPage:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ScrollDownPageHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::NextTab:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_NextTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::PrevTab:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_PrevTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::SplitVertical:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_SplitVerticalHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::SplitHorizontal:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_SplitHorizontalHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::SwitchToTab:
case ShortcutAction::SwitchToTab0:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(0);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab1:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(1);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab2:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(2);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab3:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(3);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab4:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(4);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab5:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(5);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab6:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(6);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab7:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(7);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::SwitchToTab8:
{
auto args = winrt::make_self<SwitchToTabArgs>();
args->TabIndex(8);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_SwitchToTabHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ResizePane:
case ShortcutAction::ResizePaneLeft:
{
auto args = winrt::make_self<ResizePaneArgs>();
args->Direction(Direction::Left);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_ResizePaneHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::ResizePaneRight:
{
auto args = winrt::make_self<ResizePaneArgs>();
args->Direction(Direction::Right);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_ResizePaneHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::ResizePaneUp:
{
auto args = winrt::make_self<ResizePaneArgs>();
args->Direction(Direction::Up);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_ResizePaneHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::ResizePaneDown:
{
auto args = winrt::make_self<ResizePaneArgs>();
args->Direction(Direction::Down);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_ResizePaneHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::MoveFocus:
case ShortcutAction::MoveFocusLeft:
{
auto args = winrt::make_self<MoveFocusArgs>();
args->Direction(Direction::Left);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_MoveFocusHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::MoveFocusRight:
{
auto args = winrt::make_self<MoveFocusArgs>();
args->Direction(Direction::Right);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_MoveFocusHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::MoveFocusUp:
{
auto args = winrt::make_self<MoveFocusArgs>();
args->Direction(Direction::Up);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_MoveFocusHandlers(*this, *eventArgs);
return eventArgs->Handled();
}
case ShortcutAction::MoveFocusDown:
{
auto args = winrt::make_self<MoveFocusArgs>();
args->Direction(Direction::Down);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_MoveFocusHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::IncreaseFontSize:
{
auto args = winrt::make_self<AdjustFontSizeArgs>();
args->Delta(1);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_AdjustFontSizeHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::DecreaseFontSize:
{
auto args = winrt::make_self<AdjustFontSizeArgs>();
args->Delta(-1);
auto eventArgs = winrt::make_self<ActionEventArgs>(*args);
_AdjustFontSizeHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
case ShortcutAction::ToggleFullscreen:
{
auto eventArgs = winrt::make_self<ActionEventArgs>();
_ToggleFullscreenHandlers(*this, *eventArgs);
return eventArgs->Handled();
break;
}
default:
return false;
}
return false;
return eventArgs->Handled();
}
// Method Description:

View file

@ -41,7 +41,9 @@ namespace winrt::TerminalApp::implementation
AppKeyBindings() = default;
bool TryKeyChord(winrt::Microsoft::Terminal::Settings::KeyChord const& kc);
void SetKeyBinding(TerminalApp::ShortcutAction const& action, winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
void SetKeyBinding(TerminalApp::ActionAndArgs const& actionAndArgs,
winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
void ClearKeyBinding(winrt::Microsoft::Terminal::Settings::KeyChord const& chord);
Microsoft::Terminal::Settings::KeyChord GetKeyBinding(TerminalApp::ShortcutAction const& action);
@ -54,10 +56,9 @@ namespace winrt::TerminalApp::implementation
// clang-format off
TYPED_EVENT(CopyText, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(PasteText, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(OpenNewTabDropdown,TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(DuplicateTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewTabWithProfile, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(NewWindow, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseWindow, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
TYPED_EVENT(CloseTab, TerminalApp::AppKeyBindings, TerminalApp::ActionEventArgs);
@ -79,8 +80,8 @@ namespace winrt::TerminalApp::implementation
// clang-format on
private:
std::unordered_map<winrt::Microsoft::Terminal::Settings::KeyChord, TerminalApp::ShortcutAction, KeyChordHash, KeyChordEquality> _keyShortcuts;
bool _DoAction(ShortcutAction action);
std::unordered_map<winrt::Microsoft::Terminal::Settings::KeyChord, TerminalApp::ActionAndArgs, KeyChordHash, KeyChordEquality> _keyShortcuts;
bool _DoAction(ActionAndArgs actionAndArgs);
friend class TerminalAppLocalTests::SettingsTests;
friend class TerminalAppLocalTests::KeyBindingsTests;

View file

@ -4,24 +4,27 @@ import "../ActionArgs.idl";
namespace TerminalApp
{
// TODO: GH#1069 - Many of these shortcut actions are "legacy" now that we
// have support for arbitrary args (#1142). We should remove them, and our
// legacy deserializers.
enum ShortcutAction
{
Invalid = 0,
CopyText,
CopyTextWithoutNewlines,
PasteText,
NewTab,
OpenNewTabDropdown,
DuplicateTab,
NewTabProfile0,
NewTabProfile1,
NewTabProfile2,
NewTabProfile3,
NewTabProfile4,
NewTabProfile5,
NewTabProfile6,
NewTabProfile7,
NewTabProfile8,
NewTab,
NewTabProfile0, // Legacy
NewTabProfile1, // Legacy
NewTabProfile2, // Legacy
NewTabProfile3, // Legacy
NewTabProfile4, // Legacy
NewTabProfile5, // Legacy
NewTabProfile6, // Legacy
NewTabProfile7, // Legacy
NewTabProfile8, // Legacy
NewWindow,
CloseWindow,
CloseTab,
@ -30,39 +33,49 @@ namespace TerminalApp
PrevTab,
SplitVertical,
SplitHorizontal,
SwitchToTab0,
SwitchToTab1,
SwitchToTab2,
SwitchToTab3,
SwitchToTab4,
SwitchToTab5,
SwitchToTab6,
SwitchToTab7,
SwitchToTab8,
SwitchToTab,
SwitchToTab0, // Legacy
SwitchToTab1, // Legacy
SwitchToTab2, // Legacy
SwitchToTab3, // Legacy
SwitchToTab4, // Legacy
SwitchToTab5, // Legacy
SwitchToTab6, // Legacy
SwitchToTab7, // Legacy
SwitchToTab8, // Legacy
IncreaseFontSize,
DecreaseFontSize,
ScrollUp,
ScrollDown,
ScrollUpPage,
ScrollDownPage,
ResizePaneLeft,
ResizePaneRight,
ResizePaneUp,
ResizePaneDown,
MoveFocusLeft,
MoveFocusRight,
MoveFocusUp,
MoveFocusDown,
ResizePane,
ResizePaneLeft, // Legacy
ResizePaneRight, // Legacy
ResizePaneUp, // Legacy
ResizePaneDown, // Legacy
MoveFocus,
MoveFocusLeft, // Legacy
MoveFocusRight, // Legacy
MoveFocusUp, // Legacy
MoveFocusDown, // Legacy
ToggleFullscreen,
OpenSettings
};
[default_interface] runtimeclass ActionAndArgs {
ActionAndArgs();
IActionArgs Args;
ShortcutAction Action;
};
[default_interface] runtimeclass AppKeyBindings : Microsoft.Terminal.Settings.IKeyBindings
{
AppKeyBindings();
void SetKeyBinding(ShortcutAction action, Microsoft.Terminal.Settings.KeyChord chord);
void SetKeyBinding(ActionAndArgs actionAndArgs, Microsoft.Terminal.Settings.KeyChord chord);
void ClearKeyBinding(Microsoft.Terminal.Settings.KeyChord chord);
Microsoft.Terminal.Settings.KeyChord GetKeyBinding(ShortcutAction action);
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CopyText;
@ -70,7 +83,6 @@ namespace TerminalApp
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewTab;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> OpenNewTabDropdown;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> DuplicateTab;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewTabWithProfile;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> NewWindow;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CloseWindow;
event Windows.Foundation.TypedEventHandler<AppKeyBindings, ActionEventArgs> CloseTab;

View file

@ -8,6 +8,7 @@
#include "pch.h"
#include "AppKeyBindings.h"
#include "ActionAndArgs.h"
#include "KeyChordSerialization.h"
#include "Utils.h"
#include "JsonUtils.h"
@ -18,25 +19,26 @@ using namespace winrt::TerminalApp;
static constexpr std::string_view KeysKey{ "keys" };
static constexpr std::string_view CommandKey{ "command" };
static constexpr std::string_view ActionKey{ "action" };
// This key is reserved to remove a keybinding, instead of mapping it to an action.
static constexpr std::string_view UnboundKey{ "unbound" };
static constexpr std::string_view CopyTextKey{ "copy" };
static constexpr std::string_view CopyTextWithoutNewlinesKey{ "copyTextWithoutNewlines" };
static constexpr std::string_view CopyTextWithoutNewlinesKey{ "copyTextWithoutNewlines" }; // Legacy
static constexpr std::string_view PasteTextKey{ "paste" };
static constexpr std::string_view NewTabKey{ "newTab" };
static constexpr std::string_view OpenNewTabDropdownKey{ "openNewTabDropdown" };
static constexpr std::string_view DuplicateTabKey{ "duplicateTab" };
static constexpr std::string_view NewTabWithProfile0Key{ "newTabProfile0" };
static constexpr std::string_view NewTabWithProfile1Key{ "newTabProfile1" };
static constexpr std::string_view NewTabWithProfile2Key{ "newTabProfile2" };
static constexpr std::string_view NewTabWithProfile3Key{ "newTabProfile3" };
static constexpr std::string_view NewTabWithProfile4Key{ "newTabProfile4" };
static constexpr std::string_view NewTabWithProfile5Key{ "newTabProfile5" };
static constexpr std::string_view NewTabWithProfile6Key{ "newTabProfile6" };
static constexpr std::string_view NewTabWithProfile7Key{ "newTabProfile7" };
static constexpr std::string_view NewTabWithProfile8Key{ "newTabProfile8" };
static constexpr std::string_view NewTabKey{ "newTab" };
static constexpr std::string_view NewTabWithProfile0Key{ "newTabProfile0" }; // Legacy
static constexpr std::string_view NewTabWithProfile1Key{ "newTabProfile1" }; // Legacy
static constexpr std::string_view NewTabWithProfile2Key{ "newTabProfile2" }; // Legacy
static constexpr std::string_view NewTabWithProfile3Key{ "newTabProfile3" }; // Legacy
static constexpr std::string_view NewTabWithProfile4Key{ "newTabProfile4" }; // Legacy
static constexpr std::string_view NewTabWithProfile5Key{ "newTabProfile5" }; // Legacy
static constexpr std::string_view NewTabWithProfile6Key{ "newTabProfile6" }; // Legacy
static constexpr std::string_view NewTabWithProfile7Key{ "newTabProfile7" }; // Legacy
static constexpr std::string_view NewTabWithProfile8Key{ "newTabProfile8" }; // Legacy
static constexpr std::string_view NewWindowKey{ "newWindow" };
static constexpr std::string_view CloseWindowKey{ "closeWindow" };
static constexpr std::string_view CloseTabKey{ "closeTab" };
@ -50,26 +52,29 @@ static constexpr std::string_view ScrollupKey{ "scrollUp" };
static constexpr std::string_view ScrolldownKey{ "scrollDown" };
static constexpr std::string_view ScrolluppageKey{ "scrollUpPage" };
static constexpr std::string_view ScrolldownpageKey{ "scrollDownPage" };
static constexpr std::string_view SwitchToTab0Key{ "switchToTab0" };
static constexpr std::string_view SwitchToTab1Key{ "switchToTab1" };
static constexpr std::string_view SwitchToTab2Key{ "switchToTab2" };
static constexpr std::string_view SwitchToTab3Key{ "switchToTab3" };
static constexpr std::string_view SwitchToTab4Key{ "switchToTab4" };
static constexpr std::string_view SwitchToTab5Key{ "switchToTab5" };
static constexpr std::string_view SwitchToTab6Key{ "switchToTab6" };
static constexpr std::string_view SwitchToTab7Key{ "switchToTab7" };
static constexpr std::string_view SwitchToTab8Key{ "switchToTab8" };
static constexpr std::string_view OpenSettingsKey{ "openSettings" };
static constexpr std::string_view SwitchToTabKey{ "switchToTab" };
static constexpr std::string_view SwitchToTab0Key{ "switchToTab0" }; // Legacy
static constexpr std::string_view SwitchToTab1Key{ "switchToTab1" }; // Legacy
static constexpr std::string_view SwitchToTab2Key{ "switchToTab2" }; // Legacy
static constexpr std::string_view SwitchToTab3Key{ "switchToTab3" }; // Legacy
static constexpr std::string_view SwitchToTab4Key{ "switchToTab4" }; // Legacy
static constexpr std::string_view SwitchToTab5Key{ "switchToTab5" }; // Legacy
static constexpr std::string_view SwitchToTab6Key{ "switchToTab6" }; // Legacy
static constexpr std::string_view SwitchToTab7Key{ "switchToTab7" }; // Legacy
static constexpr std::string_view SwitchToTab8Key{ "switchToTab8" }; // Legacy
static constexpr std::string_view OpenSettingsKey{ "openSettings" }; // Legacy
static constexpr std::string_view SplitHorizontalKey{ "splitHorizontal" };
static constexpr std::string_view SplitVerticalKey{ "splitVertical" };
static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" };
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" };
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" };
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" };
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" };
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" };
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" };
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" };
static constexpr std::string_view ResizePaneKey{ "resizePane" };
static constexpr std::string_view ResizePaneLeftKey{ "resizePaneLeft" }; // Legacy
static constexpr std::string_view ResizePaneRightKey{ "resizePaneRight" }; // Legacy
static constexpr std::string_view ResizePaneUpKey{ "resizePaneUp" }; // Legacy
static constexpr std::string_view ResizePaneDownKey{ "resizePaneDown" }; // Legacy
static constexpr std::string_view MoveFocusKey{ "moveFocus" };
static constexpr std::string_view MoveFocusLeftKey{ "moveFocusLeft" }; // Legacy
static constexpr std::string_view MoveFocusRightKey{ "moveFocusRight" }; // Legacy
static constexpr std::string_view MoveFocusUpKey{ "moveFocusUp" }; // Legacy
static constexpr std::string_view MoveFocusDownKey{ "moveFocusDown" }; // Legacy
static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
// Specifically use a map here over an unordered_map. We want to be able to
@ -84,9 +89,9 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ CopyTextKey, ShortcutAction::CopyText },
{ CopyTextWithoutNewlinesKey, ShortcutAction::CopyTextWithoutNewlines },
{ PasteTextKey, ShortcutAction::PasteText },
{ NewTabKey, ShortcutAction::NewTab },
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
{ DuplicateTabKey, ShortcutAction::DuplicateTab },
{ NewTabKey, ShortcutAction::NewTab },
{ NewTabWithProfile0Key, ShortcutAction::NewTabProfile0 },
{ NewTabWithProfile1Key, ShortcutAction::NewTabProfile1 },
{ NewTabWithProfile2Key, ShortcutAction::NewTabProfile2 },
@ -108,6 +113,7 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ ScrolldownKey, ShortcutAction::ScrollDown },
{ ScrolluppageKey, ShortcutAction::ScrollUpPage },
{ ScrolldownpageKey, ShortcutAction::ScrollDownPage },
{ SwitchToTabKey, ShortcutAction::SwitchToTab },
{ SwitchToTab0Key, ShortcutAction::SwitchToTab0 },
{ SwitchToTab1Key, ShortcutAction::SwitchToTab1 },
{ SwitchToTab2Key, ShortcutAction::SwitchToTab2 },
@ -119,10 +125,12 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ SwitchToTab8Key, ShortcutAction::SwitchToTab8 },
{ SplitHorizontalKey, ShortcutAction::SplitHorizontal },
{ SplitVerticalKey, ShortcutAction::SplitVertical },
{ ResizePaneKey, ShortcutAction::ResizePane },
{ ResizePaneLeftKey, ShortcutAction::ResizePaneLeft },
{ ResizePaneRightKey, ShortcutAction::ResizePaneRight },
{ ResizePaneUpKey, ShortcutAction::ResizePaneUp },
{ ResizePaneDownKey, ShortcutAction::ResizePaneDown },
{ MoveFocusKey, ShortcutAction::MoveFocus },
{ MoveFocusLeftKey, ShortcutAction::MoveFocusLeft },
{ MoveFocusRightKey, ShortcutAction::MoveFocusRight },
{ MoveFocusUpKey, ShortcutAction::MoveFocusUp },
@ -132,6 +140,151 @@ static const std::map<std::string_view, ShortcutAction, std::less<>> commandName
{ UnboundKey, ShortcutAction::Invalid },
};
// Function Description:
// - Creates a function that can be used to generate a MoveFocusArgs for the
// legacy MoveFocus[Direction] actions. These actions don't accept args from
// json, instead, they just return a MoveFocusArgs with the Direction already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// MoveFocus[Direction] args.
std::function<IActionArgs(const Json::Value&)> LegacyParseMoveFocusArgs(Direction direction)
{
auto pfn = [direction](const Json::Value & /*value*/) -> IActionArgs {
auto args = winrt::make_self<winrt::TerminalApp::implementation::MoveFocusArgs>();
args->Direction(direction);
return *args;
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a ResizePaneArgs for the
// legacy ResizePane[Direction] actions. These actions don't accept args from
// json, instead, they just return a ResizePaneArgs with the Direction already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// ResizePane[Direction] args.
std::function<IActionArgs(const Json::Value&)> LegacyParseResizePaneArgs(Direction direction)
{
auto pfn = [direction](const Json::Value & /*value*/) -> IActionArgs {
auto args = winrt::make_self<winrt::TerminalApp::implementation::ResizePaneArgs>();
args->Direction(direction);
return *args;
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a NewTabWithProfileArgs for
// the legacy NewTabWithProfile[Index] actions. These actions don't accept
// args from json, instead, they just return a NewTabWithProfileArgs with the
// index already per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - index: the profile index to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// NewTabWithProfile[Index] args.
std::function<IActionArgs(const Json::Value&)> LegacyParseNewTabWithProfileArgs(int index)
{
auto pfn = [index](const Json::Value & /*value*/) -> IActionArgs {
auto args = winrt::make_self<winrt::TerminalApp::implementation::NewTabArgs>();
args->ProfileIndex(index);
return *args;
};
return pfn;
}
// Function Description:
// - Creates a function that can be used to generate a SwitchToTabArgs for the
// legacy SwitchToTab[Index] actions. These actions don't accept args from
// json, instead, they just return a SwitchToTabArgs with the index already
// per-defined, based on the input param.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - index: the tab index to create the parse function for.
// Return Value:
// - A function that can be used to "parse" json into one of the legacy
// SwitchToTab[Index] args.
std::function<IActionArgs(const Json::Value&)> LegacyParseSwitchToTabArgs(int index)
{
auto pfn = [index](const Json::Value & /*value*/) -> IActionArgs {
auto args = winrt::make_self<winrt::TerminalApp::implementation::SwitchToTabArgs>();
args->TabIndex(index);
return *args;
};
return pfn;
}
// Function Description:
// - Used to generate a CopyTextArgs for the legacy CopyTextWithoutNewlines
// action.
// - TODO: GH#1069 Remove this before 1.0, and force an upgrade to the new args.
// Arguments:
// - direction: the direction to create the parse function for.
// Return Value:
// - A CopyTextArgs with TrimWhitespace set to true, to emulate "CopyTextWithoutNewlines".
IActionArgs LegacyParseCopyTextWithoutNewlinesArgs(const Json::Value& /*json*/)
{
auto args = winrt::make_self<winrt::TerminalApp::implementation::CopyTextArgs>();
args->TrimWhitespace(true);
return *args;
};
// This is a map of ShortcutAction->function<IActionArgs(Json::Value)>. It holds
// a set of deserializer functions that can be used to deserialize a IActionArgs
// from json. Each type of IActionArgs that can accept arbitrary args should be
// placed into this map, with the corresponding deserializer function as the
// value.
static const std::map<ShortcutAction, std::function<IActionArgs(const Json::Value&)>, std::less<>> argParsers{
{ ShortcutAction::CopyText, winrt::TerminalApp::implementation::CopyTextArgs::FromJson },
{ ShortcutAction::CopyTextWithoutNewlines, LegacyParseCopyTextWithoutNewlinesArgs },
{ ShortcutAction::NewTab, winrt::TerminalApp::implementation::NewTabArgs::FromJson },
{ ShortcutAction::NewTabProfile0, LegacyParseNewTabWithProfileArgs(0) },
{ ShortcutAction::NewTabProfile1, LegacyParseNewTabWithProfileArgs(1) },
{ ShortcutAction::NewTabProfile2, LegacyParseNewTabWithProfileArgs(2) },
{ ShortcutAction::NewTabProfile3, LegacyParseNewTabWithProfileArgs(3) },
{ ShortcutAction::NewTabProfile4, LegacyParseNewTabWithProfileArgs(4) },
{ ShortcutAction::NewTabProfile5, LegacyParseNewTabWithProfileArgs(5) },
{ ShortcutAction::NewTabProfile6, LegacyParseNewTabWithProfileArgs(6) },
{ ShortcutAction::NewTabProfile7, LegacyParseNewTabWithProfileArgs(7) },
{ ShortcutAction::NewTabProfile8, LegacyParseNewTabWithProfileArgs(8) },
{ ShortcutAction::SwitchToTab, winrt::TerminalApp::implementation::SwitchToTabArgs::FromJson },
{ ShortcutAction::SwitchToTab0, LegacyParseSwitchToTabArgs(0) },
{ ShortcutAction::SwitchToTab1, LegacyParseSwitchToTabArgs(1) },
{ ShortcutAction::SwitchToTab2, LegacyParseSwitchToTabArgs(2) },
{ ShortcutAction::SwitchToTab3, LegacyParseSwitchToTabArgs(3) },
{ ShortcutAction::SwitchToTab4, LegacyParseSwitchToTabArgs(4) },
{ ShortcutAction::SwitchToTab5, LegacyParseSwitchToTabArgs(5) },
{ ShortcutAction::SwitchToTab6, LegacyParseSwitchToTabArgs(6) },
{ ShortcutAction::SwitchToTab7, LegacyParseSwitchToTabArgs(7) },
{ ShortcutAction::SwitchToTab8, LegacyParseSwitchToTabArgs(8) },
{ ShortcutAction::ResizePane, winrt::TerminalApp::implementation::ResizePaneArgs::FromJson },
{ ShortcutAction::ResizePaneLeft, LegacyParseResizePaneArgs(Direction::Left) },
{ ShortcutAction::ResizePaneRight, LegacyParseResizePaneArgs(Direction::Right) },
{ ShortcutAction::ResizePaneUp, LegacyParseResizePaneArgs(Direction::Up) },
{ ShortcutAction::ResizePaneDown, LegacyParseResizePaneArgs(Direction::Down) },
{ ShortcutAction::MoveFocus, winrt::TerminalApp::implementation::MoveFocusArgs::FromJson },
{ ShortcutAction::MoveFocusLeft, LegacyParseMoveFocusArgs(Direction::Left) },
{ ShortcutAction::MoveFocusRight, LegacyParseMoveFocusArgs(Direction::Right) },
{ ShortcutAction::MoveFocusUp, LegacyParseMoveFocusArgs(Direction::Up) },
{ ShortcutAction::MoveFocusDown, LegacyParseMoveFocusArgs(Direction::Down) },
{ ShortcutAction::Invalid, nullptr },
};
// Function Description:
// - Small helper to create a json value serialization of a single
// KeyBinding->Action maping. The created object is of schema:
@ -192,6 +345,21 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
return bindingsArray;
}
// Function Description:
// - Attempts to match a string to a ShortcutAction. If there's no match, then
// returns ShortcutAction::Invalid
// Arguments:
// - actionString: the string to match to a ShortcutAction
// Return Value:
// - The ShortcutAction corresponding to the given string, if a match exists.
static ShortcutAction GetActionFromString(const std::string_view actionString)
{
// 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 = commandNames.find(actionString);
return found != commandNames.end() ? found->second : ShortcutAction::Invalid;
}
// Method Description:
// - Deserialize an AppKeyBindings from the key mappings that are in the array
// `json`. The json array should contain an array of objects with both a
@ -227,18 +395,48 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
// Invalid is our placeholder that the action was not parsed.
ShortcutAction action = ShortcutAction::Invalid;
// Keybindings can be serialized in two styles:
// { "command": "switchToTab0", "keys": ["ctrl+1"] },
// { "command": { "action": "switchToTab", "index": 0 }, "keys": ["ctrl+alt+1"] },
// 1. In the first case, the "command" is a string, that's the
// action name. There are no provided args, so we'll pass
// Json::Value::null to the parse function.
// 2. In the second case, the "command" is an object. We'll use the
// "action" in that object as the action name. We'll then pass
// the "command" object to the arg parser, for furhter parsing.
auto argsVal = Json::Value::null;
// Only try to parse the action if it's actually a string value.
// `null` will not pass this check.
if (commandVal.isString())
{
auto commandString = commandVal.asString();
// 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 = commandNames.find(commandString);
if (found != commandNames.end())
action = GetActionFromString(commandString);
}
else if (commandVal.isObject())
{
const auto actionVal = commandVal[JsonKey(ActionKey)];
if (actionVal.isString())
{
action = found->second;
auto actionString = actionVal.asString();
action = GetActionFromString(actionString);
argsVal = commandVal;
}
}
// Some keybindings can accept other arbitrary arguments. If it
// does, we'll try to deserialize any "args" that were provided with
// the binding.
IActionArgs args{ nullptr };
const auto deserializersIter = argParsers.find(action);
if (deserializersIter != argParsers.end())
{
auto pfn = deserializersIter->second;
if (pfn)
{
args = pfn(argsVal);
}
}
@ -253,7 +451,10 @@ void winrt::TerminalApp::implementation::AppKeyBindings::LayerJson(const Json::V
// found.
if (action != ShortcutAction::Invalid)
{
SetKeyBinding(action, chord);
auto actionAndArgs = winrt::make_self<ActionAndArgs>();
actionAndArgs->Action(action);
actionAndArgs->Args(args);
SetKeyBinding(*actionAndArgs, chord);
}
else
{

View file

@ -182,6 +182,10 @@ void CascadiaSettings::_ValidateSettings()
// TODO:GH#2548 ensure there's at least one key bound. Display a warning if
// there's _NO_ keys bound to any actions. That's highly irregular, and
// likely an indication of an error somehow.
// TODO:GH#3522 With variable args to keybindings, it's possible that a user
// set a keybinding without all the required args for an action. Display a
// warning if an action didn't have a required arg.
}
// Method Description:

View file

@ -557,7 +557,6 @@ namespace winrt::TerminalApp::implementation
// They should all be hooked up here, regardless of whether or not
// there's an actual keychord for them.
bindings.NewTab({ this, &TerminalPage::_HandleNewTab });
bindings.OpenNewTabDropdown({ this, &TerminalPage::_HandleOpenNewTabDropdown });
bindings.DuplicateTab({ this, &TerminalPage::_HandleDuplicateTab });
bindings.CloseTab({ this, &TerminalPage::_HandleCloseTab });
@ -573,7 +572,7 @@ namespace winrt::TerminalApp::implementation
bindings.ScrollDownPage({ this, &TerminalPage::_HandleScrollDownPage });
bindings.OpenSettings({ this, &TerminalPage::_HandleOpenSettings });
bindings.PasteText({ this, &TerminalPage::_HandlePasteText });
bindings.NewTabWithProfile({ this, &TerminalPage::_HandleNewTabWithProfile });
bindings.NewTab({ this, &TerminalPage::_HandleNewTab });
bindings.SwitchToTab({ this, &TerminalPage::_HandleSwitchToTab });
bindings.ResizePane({ this, &TerminalPage::_HandleResizePane });
bindings.MoveFocus({ this, &TerminalPage::_HandleMoveFocus });

View file

@ -132,7 +132,6 @@ namespace winrt::TerminalApp::implementation
#pragma region ActionHandlers
// These are all defined in AppActionHandlers.cpp
void _HandleNewTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleDuplicateTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleCloseTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
@ -147,7 +146,7 @@ namespace winrt::TerminalApp::implementation
void _HandleScrollDownPage(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleOpenSettings(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandlePasteText(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleNewTabWithProfile(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleNewTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleSwitchToTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleResizePane(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
void _HandleMoveFocus(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);

View file

@ -205,15 +205,15 @@
{ "command": "copy", "keys": ["ctrl+shift+c"] },
{ "command": "duplicateTab", "keys": ["ctrl+shift+d"] },
{ "command": "newTab", "keys": ["ctrl+shift+t"] },
{ "command": "newTabProfile0", "keys": ["ctrl+shift+1"] },
{ "command": "newTabProfile1", "keys": ["ctrl+shift+2"] },
{ "command": "newTabProfile2", "keys": ["ctrl+shift+3"] },
{ "command": "newTabProfile3", "keys": ["ctrl+shift+4"] },
{ "command": "newTabProfile4", "keys": ["ctrl+shift+5"] },
{ "command": "newTabProfile5", "keys": ["ctrl+shift+6"] },
{ "command": "newTabProfile6", "keys": ["ctrl+shift+7"] },
{ "command": "newTabProfile7", "keys": ["ctrl+shift+8"] },
{ "command": "newTabProfile8", "keys": ["ctrl+shift+9"] },
{ "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": "nextTab", "keys": ["ctrl+tab"] },
{ "command": "openNewTabDropdown", "keys": ["ctrl+shift+space"] },
{ "command": "openSettings", "keys": ["ctrl+,"] },
@ -223,15 +223,15 @@
{ "command": "scrollDownPage", "keys": ["ctrl+shift+pgdn"] },
{ "command": "scrollUp", "keys": ["ctrl+shift+up"] },
{ "command": "scrollUpPage", "keys": ["ctrl+shift+pgup"] },
{ "command": "switchToTab0", "keys": ["ctrl+alt+1"] },
{ "command": "switchToTab1", "keys": ["ctrl+alt+2"] },
{ "command": "switchToTab2", "keys": ["ctrl+alt+3"] },
{ "command": "switchToTab3", "keys": ["ctrl+alt+4"] },
{ "command": "switchToTab4", "keys": ["ctrl+alt+5"] },
{ "command": "switchToTab5", "keys": ["ctrl+alt+6"] },
{ "command": "switchToTab6", "keys": ["ctrl+alt+7"] },
{ "command": "switchToTab7", "keys": ["ctrl+alt+8"] },
{ "command": "switchToTab8", "keys": ["ctrl+alt+9"] },
{ "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": 8 }, "keys": ["ctrl+alt+9"] },
{ "command": "decreaseFontSize", "keys": ["ctrl+-"] },
{ "command": "increaseFontSize", "keys": ["ctrl+="] },
{ "command": "toggleFullscreen", "keys": ["alt+enter"] },

View file

@ -90,6 +90,9 @@
<ClInclude Include="../ActionArgs.h" >
<DependentUpon>../ActionArgs.idl</DependentUpon>
</ClInclude>
<ClInclude Include="../ActionAndArgs.h" >
<DependentUpon>../ActionArgs.idl</DependentUpon>
</ClInclude>
<ClInclude Include="../AppKeyBindings.h" >
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
</ClInclude>
@ -138,6 +141,9 @@
<ClCompile Include="../AppKeyBindings.cpp" >
<DependentUpon>../AppKeyBindings.idl</DependentUpon>
</ClCompile>
<ClCompile Include="../ActionAndArgs.cpp" >
<DependentUpon>../ActionArgs.idl</DependentUpon>
</ClCompile>
<ClCompile Include="../ActionArgs.cpp" >
<DependentUpon>../ActionArgs.idl</DependentUpon>
</ClCompile>

View file

@ -40,3 +40,9 @@ Author(s):
// UnitTests run in CI, while the LocalTests do not. However, since the CI can't
// run XAML islands or unpackaged WinRT, any tests using those features will
// need to be added to the LocalTests.
// These however are okay, for some _basic_ winrt things:
#include <unknwn.h>
#include <hstring.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>