Add support for the Command Palette (#6635)
## Summary of the Pull Request ![command-palette-001](https://user-images.githubusercontent.com/18356694/85313480-b6dbef00-b47d-11ea-8a8f-a802d26c2f9b.gif) This adds a first iteration on the command palette. Notable missing features are: * Commandline mode: This will be a follow-up PR, following the merge of #6537 * nested and iterable commands: These will additionally be a follow-up PR. This is also additionally based off the addenda in #6532. This does not bind a key for the palette by default. That will be done when the above follow-ups are completed. ## References * #2046 - The original command palette thread * #5400 - This is the megathread for all command palette issues, which is tracking a bunch of additional follow up work * #5674 and #6532 - specs * #6537 - related ## PR Checklist * [x] Closes #2046 - incidentally also closes #6645 * [x] I work here * [x] Tests added/passed * [ ] Requires documentation to be updated - delaying this until it's more polished. ## Detailed Description of the Pull Request / Additional comments * There's a lot of code for autogenerating command names. That's all in `ActionArgs.cpp`, because each case is so _not_ boilerplate, unlike the rest of the code in `ActionArgs.h`. ## Validation Steps Performed * I've been playing with this for months. * Tests * Selfhost with the team
This commit is contained in:
parent
2fc1ef04ce
commit
aa1ed0a19c
349
src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
Normal file
349
src/cascadia/LocalTests_TerminalApp/CommandTests.cpp
Normal file
|
@ -0,0 +1,349 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
|
||||
#include "../TerminalApp/CascadiaSettings.h"
|
||||
#include "JsonTestClass.h"
|
||||
#include "TestUtils.h"
|
||||
|
||||
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;
|
||||
|
||||
namespace TerminalAppLocalTests
|
||||
{
|
||||
// TODO:microsoft/terminal#3838:
|
||||
// Unfortunately, these tests _WILL NOT_ work in our CI. We're waiting for
|
||||
// an updated TAEF that will let us install framework packages when the test
|
||||
// package is deployed. Until then, these tests won't deploy in CI.
|
||||
|
||||
class CommandTests : public JsonTestClass
|
||||
{
|
||||
// Use a custom AppxManifest to ensure that we can activate winrt types
|
||||
// from our test. This property will tell taef to manually use this as
|
||||
// the AppxManifest for this test class.
|
||||
// This does not yet work for anything XAML-y. See TabTests.cpp for more
|
||||
// details on that.
|
||||
BEGIN_TEST_CLASS(CommandTests)
|
||||
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
|
||||
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
|
||||
END_TEST_CLASS()
|
||||
|
||||
TEST_METHOD(ManyCommandsSameAction);
|
||||
TEST_METHOD(LayerCommand);
|
||||
TEST_METHOD(TestSplitPaneArgs);
|
||||
TEST_METHOD(TestResourceKeyName);
|
||||
TEST_METHOD(TestAutogeneratedName);
|
||||
TEST_METHOD(TestLayerOnAutogeneratedName);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
void CommandTests::ManyCommandsSameAction()
|
||||
{
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action1", "command": { "action": "copy", "singleLine": false } } ])" };
|
||||
const std::string commands2String{ R"([
|
||||
{ "name":"action2", "command": "paste" },
|
||||
{ "name":"action3", "command": "paste" }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(2u, commands.size());
|
||||
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
}
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
}
|
||||
|
||||
void CommandTests::LayerCommand()
|
||||
{
|
||||
// Each one of the commands in this test should layer upon the previous, overriding the action.
|
||||
const std::string commands0String{ R"([ { "name":"action0", "command": "copy" } ])" };
|
||||
const std::string commands1String{ R"([ { "name":"action0", "command": "paste" } ])" };
|
||||
const std::string commands2String{ R"([ { "name":"action0", "command": "newTab" } ])" };
|
||||
const std::string commands3String{ R"([ { "name":"action0", "command": null } ])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
const auto commands1Json = VerifyParseSucceeded(commands1String);
|
||||
const auto commands2Json = VerifyParseSucceeded(commands2String);
|
||||
const auto commands3Json = VerifyParseSucceeded(commands3String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands1Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::PasteText, command.Action().Action());
|
||||
VERIFY_IS_NULL(command.Action().Args());
|
||||
}
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands2Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
auto command = commands.at(L"action0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<NewTabArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
{
|
||||
// This last command should "unbind" the action.
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands3Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestSplitPaneArgs()
|
||||
{
|
||||
// This is the same as KeyBindingsTests::TestSplitPaneArgs, but with
|
||||
// looking up the action and its args from a map of commands, instead
|
||||
// of from keybindings.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "name": "command0", "command": { "action": "splitPane", "split": null } },
|
||||
{ "name": "command1", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "command2", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "name": "command3", "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "name": "command4", "command": { "action": "splitPane" } },
|
||||
{ "name": "command5", "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "name": "command6", "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(7u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"command0");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command1");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command2");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command3");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command4");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command5");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"command6");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestResourceKeyName()
|
||||
{
|
||||
// This test checks looking up a name from a resource key.
|
||||
|
||||
const std::string commands0String{ R"([ { "name": { "key": "DuplicateTabCommandKey"}, "command": "copy" } ])" };
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
{
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
// NOTE: We're relying on DuplicateTabCommandKey being defined as
|
||||
// "Duplicate Tab" here. If that string changes in our resources,
|
||||
// this test will break.
|
||||
auto command = commands.at(L"Duplicate tab");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::CopyText, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<CopyTextArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
}
|
||||
}
|
||||
|
||||
void CommandTests::TestAutogeneratedName()
|
||||
{
|
||||
// This test ensures that we'll correctly create commands for actions
|
||||
// that don't have given names, pursuant to the spec in GH#6532.
|
||||
|
||||
// NOTE: The keys used to look up these commands are partially generated
|
||||
// from strings in our Resources.resw. If those string values should
|
||||
// change, it's likely that this test will break.
|
||||
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane", "split": null } },
|
||||
{ "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "command": { "action": "splitPane", "split": "none" } },
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "command": { "action": "splitPane", "split": "auto" } },
|
||||
{ "command": { "action": "splitPane", "split": "foo" } }
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
|
||||
// There are only 3 commands here: all of the `"none"`, `"auto"`,
|
||||
// `"foo"`, `null`, and <no args> bindings all generate the same action,
|
||||
// which will generate just a single name for all of them.
|
||||
VERIFY_ARE_EQUAL(3u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Automatic, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
void CommandTests::TestLayerOnAutogeneratedName()
|
||||
{
|
||||
const std::string commands0String{ R"([
|
||||
{ "command": { "action": "splitPane" } },
|
||||
{ "name":"Split pane", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
])" };
|
||||
|
||||
const auto commands0Json = VerifyParseSucceeded(commands0String);
|
||||
|
||||
std::unordered_map<winrt::hstring, Command> commands;
|
||||
VERIFY_ARE_EQUAL(0u, commands.size());
|
||||
auto warnings = implementation::Command::LayerJson(commands, commands0Json);
|
||||
VERIFY_ARE_EQUAL(0u, warnings.size());
|
||||
VERIFY_ARE_EQUAL(1u, commands.size());
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
VERIFY_IS_NOT_NULL(command.Action());
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, command.Action().Action());
|
||||
const auto& realArgs = command.Action().Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -80,6 +80,8 @@ namespace TerminalAppLocalTests
|
|||
|
||||
TEST_METHOD(TestTrailingCommas);
|
||||
|
||||
TEST_METHOD(TestCommandsAndKeybindings);
|
||||
|
||||
TEST_CLASS_SETUP(ClassSetup)
|
||||
{
|
||||
InitializeJsonReader();
|
||||
|
@ -2331,4 +2333,204 @@ namespace TerminalAppLocalTests
|
|||
VERIFY_IS_TRUE(false, L"This call to LayerJson should succeed, even with the trailing comma");
|
||||
}
|
||||
}
|
||||
|
||||
void SettingsTests::TestCommandsAndKeybindings()
|
||||
{
|
||||
const std::string settingsJson{ R"(
|
||||
{
|
||||
"defaultProfile": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"profiles": [
|
||||
{
|
||||
"name": "profile0",
|
||||
"guid": "{6239a42c-0000-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 1,
|
||||
"commandline": "cmd.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile1",
|
||||
"guid": "{6239a42c-1111-49a3-80bd-e8fdd045185c}",
|
||||
"historySize": 2,
|
||||
"commandline": "pwsh.exe"
|
||||
},
|
||||
{
|
||||
"name": "profile2",
|
||||
"historySize": 3,
|
||||
"commandline": "wsl.exe"
|
||||
}
|
||||
],
|
||||
"bindings": [
|
||||
{ "keys": "ctrl+a", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "name": "ctrl+b", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+c", "name": "ctrl+c", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+d", "command": { "action": "splitPane", "split": "vertical" } },
|
||||
{ "keys": "ctrl+e", "command": { "action": "splitPane", "split": "horizontal" } },
|
||||
{ "keys": "ctrl+f", "name":null, "command": { "action": "splitPane", "split": "horizontal" } }
|
||||
]
|
||||
})" };
|
||||
|
||||
const auto guid0 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-0000-49a3-80bd-e8fdd045185c}");
|
||||
const auto guid1 = Microsoft::Console::Utils::GuidFromString(L"{6239a42c-1111-49a3-80bd-e8fdd045185c}");
|
||||
|
||||
VerifyParseSucceeded(settingsJson);
|
||||
CascadiaSettings settings{};
|
||||
settings._ParseJsonString(settingsJson, false);
|
||||
settings.LayerJson(settings._userSettings);
|
||||
settings._ValidateSettings();
|
||||
|
||||
VERIFY_ARE_EQUAL(3u, settings.GetProfiles().size());
|
||||
|
||||
const auto profile2Guid = settings._profiles.at(2).GetGuid();
|
||||
VERIFY_ARE_NOT_EQUAL(GUID{ 0 }, profile2Guid);
|
||||
|
||||
auto appKeyBindings = settings._globals._keybindings;
|
||||
VERIFY_ARE_EQUAL(5u, appKeyBindings->_keyShortcuts.size());
|
||||
|
||||
// A/D, B, C, E will be in the list of commands, for 4 total.
|
||||
// * A and D share the same name, so they'll only generate a single action.
|
||||
// * F's name is set manually to `null`
|
||||
auto commands = settings._globals.GetCommands();
|
||||
VERIFY_ARE_EQUAL(4u, commands.size());
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('A') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Note that we're skipping ctrl+B, since that doesn't have `keys` set.");
|
||||
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('C') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('D') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('E') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
KeyChord kc{ true, false, false, static_cast<int32_t>('F') };
|
||||
auto actionAndArgs = TestUtils::GetActionAndArgs(*appKeyBindings, kc);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
|
||||
Log::Comment(L"Now verify the commands");
|
||||
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Vertical");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+b");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"ctrl+c");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Vertical, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
{
|
||||
auto command = commands.at(L"Split pane, direction: Horizontal");
|
||||
VERIFY_IS_NOT_NULL(command);
|
||||
auto actionAndArgs = command.Action();
|
||||
VERIFY_IS_NOT_NULL(actionAndArgs);
|
||||
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
|
||||
const auto& realArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
|
||||
VERIFY_IS_NOT_NULL(realArgs);
|
||||
// Verify the args have the expected value
|
||||
VERIFY_ARE_EQUAL(winrt::TerminalApp::SplitState::Horizontal, realArgs.SplitStyle());
|
||||
VERIFY_IS_NOT_NULL(realArgs.TerminalArgs());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Commandline().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().StartingDirectory().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().TabTitle().empty());
|
||||
VERIFY_IS_TRUE(realArgs.TerminalArgs().Profile().empty());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -60,6 +60,7 @@
|
|||
<ClCompile Include="ProfileTests.cpp" />
|
||||
<ClCompile Include="ColorSchemeTests.cpp" />
|
||||
<ClCompile Include="KeyBindingsTests.cpp" />
|
||||
<ClCompile Include="CommandTests.cpp" />
|
||||
<ClCompile Include="TabTests.cpp" />
|
||||
<ClCompile Include="pch.cpp">
|
||||
<PrecompiledHeader>Create</PrecompiledHeader>
|
||||
|
|
|
@ -2,11 +2,7 @@
|
|||
#include "ActionArgs.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include "ActionAndArgs.g.cpp"
|
||||
|
||||
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" };
|
||||
#include <LibraryResources.h>
|
||||
|
||||
static constexpr std::string_view CopyTextKey{ "copy" };
|
||||
static constexpr std::string_view PasteTextKey{ "paste" };
|
||||
|
@ -36,6 +32,12 @@ static constexpr std::string_view ToggleFullscreenKey{ "toggleFullscreen" };
|
|||
static constexpr std::string_view SetTabColorKey{ "setTabColor" };
|
||||
static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" };
|
||||
static constexpr std::string_view RenameTabKey{ "renameTab" };
|
||||
static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" };
|
||||
|
||||
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" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
|
@ -47,7 +49,7 @@ namespace winrt::TerminalApp::implementation
|
|||
// the actual strings being pointed to. However, since both these strings and
|
||||
// the map are all const for the lifetime of the app, we have nothing to worry
|
||||
// about here.
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionNamesMap{
|
||||
const std::map<std::string_view, ShortcutAction, std::less<>> ActionAndArgs::ActionKeyNamesMap{
|
||||
{ CopyTextKey, ShortcutAction::CopyText },
|
||||
{ PasteTextKey, ShortcutAction::PasteText },
|
||||
{ OpenNewTabDropdownKey, ShortcutAction::OpenNewTabDropdown },
|
||||
|
@ -75,7 +77,8 @@ namespace winrt::TerminalApp::implementation
|
|||
{ OpenTabColorPickerKey, ShortcutAction::OpenTabColorPicker },
|
||||
{ UnboundKey, ShortcutAction::Invalid },
|
||||
{ FindKey, ShortcutAction::Find },
|
||||
{ RenameTabKey, ShortcutAction::RenameTab }
|
||||
{ RenameTabKey, ShortcutAction::RenameTab },
|
||||
{ ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette },
|
||||
};
|
||||
|
||||
using ParseResult = std::tuple<IActionArgs, std::vector<::TerminalApp::SettingsLoadWarnings>>;
|
||||
|
@ -121,8 +124,8 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
// 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::ActionNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
const auto found = ActionAndArgs::ActionKeyNamesMap.find(actionString);
|
||||
return found != ActionAndArgs::ActionKeyNamesMap.end() ? found->second : ShortcutAction::Invalid;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -219,4 +222,55 @@ namespace winrt::TerminalApp::implementation
|
|||
}
|
||||
}
|
||||
|
||||
winrt::hstring ActionAndArgs::GenerateName() const
|
||||
{
|
||||
// Use a magic static to initialize this map, because we won't be able
|
||||
// to load the resources at _init_, only at runtime.
|
||||
static const auto GeneratedActionNames = []() {
|
||||
return std::unordered_map<ShortcutAction, winrt::hstring>{
|
||||
{ ShortcutAction::CopyText, RS_(L"CopyTextCommandKey") },
|
||||
{ ShortcutAction::PasteText, RS_(L"PasteTextCommandKey") },
|
||||
{ ShortcutAction::OpenNewTabDropdown, RS_(L"OpenNewTabDropdownCommandKey") },
|
||||
{ ShortcutAction::DuplicateTab, RS_(L"DuplicateTabCommandKey") },
|
||||
{ ShortcutAction::NewTab, RS_(L"NewTabCommandKey") },
|
||||
{ ShortcutAction::NewWindow, RS_(L"NewWindowCommandKey") },
|
||||
{ ShortcutAction::CloseWindow, RS_(L"CloseWindowCommandKey") },
|
||||
{ ShortcutAction::CloseTab, RS_(L"CloseTabCommandKey") },
|
||||
{ ShortcutAction::ClosePane, RS_(L"ClosePaneCommandKey") },
|
||||
{ ShortcutAction::NextTab, RS_(L"NextTabCommandKey") },
|
||||
{ ShortcutAction::PrevTab, RS_(L"PrevTabCommandKey") },
|
||||
{ ShortcutAction::AdjustFontSize, RS_(L"AdjustFontSizeCommandKey") },
|
||||
{ ShortcutAction::ResetFontSize, RS_(L"ResetFontSizeCommandKey") },
|
||||
{ ShortcutAction::ScrollUp, RS_(L"ScrollUpCommandKey") },
|
||||
{ ShortcutAction::ScrollDown, RS_(L"ScrollDownCommandKey") },
|
||||
{ ShortcutAction::ScrollUpPage, RS_(L"ScrollUpPageCommandKey") },
|
||||
{ ShortcutAction::ScrollDownPage, RS_(L"ScrollDownPageCommandKey") },
|
||||
{ ShortcutAction::SwitchToTab, RS_(L"SwitchToTabCommandKey") },
|
||||
{ ShortcutAction::ResizePane, RS_(L"ResizePaneCommandKey") },
|
||||
{ ShortcutAction::MoveFocus, RS_(L"MoveFocusCommandKey") },
|
||||
{ ShortcutAction::OpenSettings, RS_(L"OpenSettingsCommandKey") },
|
||||
{ ShortcutAction::ToggleFullscreen, RS_(L"ToggleFullscreenCommandKey") },
|
||||
{ ShortcutAction::SplitPane, RS_(L"SplitPaneCommandKey") },
|
||||
{ ShortcutAction::Invalid, L"" },
|
||||
{ ShortcutAction::Find, RS_(L"FindCommandKey") },
|
||||
{ ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") },
|
||||
{ ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") },
|
||||
{ ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") },
|
||||
{ ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") },
|
||||
};
|
||||
}();
|
||||
|
||||
if (_Args)
|
||||
{
|
||||
auto nameFromArgs = _Args.GenerateName();
|
||||
if (!nameFromArgs.empty())
|
||||
{
|
||||
return nameFromArgs;
|
||||
}
|
||||
}
|
||||
|
||||
const auto found = GeneratedActionNames.find(_Action);
|
||||
return found != GeneratedActionNames.end() ? found->second : L"";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,11 +7,13 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
struct ActionAndArgs : public ActionAndArgsT<ActionAndArgs>
|
||||
{
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionNamesMap;
|
||||
static const std::map<std::string_view, ShortcutAction, std::less<>> ActionKeyNamesMap;
|
||||
static winrt::com_ptr<ActionAndArgs> FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
|
||||
ActionAndArgs() = default;
|
||||
hstring GenerateName() const;
|
||||
|
||||
GETSET_PROPERTY(TerminalApp::ShortcutAction, Action, TerminalApp::ShortcutAction::Invalid);
|
||||
GETSET_PROPERTY(IActionArgs, Args, nullptr);
|
||||
};
|
||||
|
|
|
@ -17,3 +17,245 @@
|
|||
#include "OpenSettingsArgs.g.cpp"
|
||||
#include "SetTabColorArgs.g.cpp"
|
||||
#include "RenameTabArgs.g.cpp"
|
||||
|
||||
#include <LibraryResources.h>
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
winrt::hstring NewTerminalArgs::GenerateName() const
|
||||
{
|
||||
std::wstringstream ss;
|
||||
|
||||
if (!_Profile.empty())
|
||||
{
|
||||
ss << fmt::format(L"profile: {}, ", _Profile);
|
||||
}
|
||||
else if (_ProfileIndex)
|
||||
{
|
||||
ss << fmt::format(L"profile index: {}, ", _ProfileIndex.Value());
|
||||
}
|
||||
|
||||
if (!_Commandline.empty())
|
||||
{
|
||||
ss << fmt::format(L"commandline: {}, ", _Commandline);
|
||||
}
|
||||
|
||||
if (!_StartingDirectory.empty())
|
||||
{
|
||||
ss << fmt::format(L"directory: {}, ", _StartingDirectory);
|
||||
}
|
||||
|
||||
if (!_TabTitle.empty())
|
||||
{
|
||||
ss << fmt::format(L"title: {}, ", _TabTitle);
|
||||
}
|
||||
auto s = ss.str();
|
||||
if (s.empty())
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring CopyTextArgs::GenerateName() const
|
||||
{
|
||||
if (_SingleLine)
|
||||
{
|
||||
return RS_(L"CopyTextAsSingleLineCommandKey");
|
||||
}
|
||||
return RS_(L"CopyTextCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring NewTabArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (newTerminalArgsStr.empty())
|
||||
{
|
||||
return RS_(L"NewTabCommandKey");
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, {}", RS_(L"NewTabCommandKey"), newTerminalArgsStr)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring SwitchToTabArgs::GenerateName() const
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(L"{}, index:{}", RS_(L"SwitchToTabCommandKey"), _TabIndex)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring ResizePaneArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"ResizePaneWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring MoveFocusArgs::GenerateName() const
|
||||
{
|
||||
winrt::hstring directionString;
|
||||
switch (_Direction)
|
||||
{
|
||||
case Direction::Left:
|
||||
directionString = RS_(L"DirectionLeft");
|
||||
break;
|
||||
case Direction::Right:
|
||||
directionString = RS_(L"DirectionRight");
|
||||
break;
|
||||
case Direction::Up:
|
||||
directionString = RS_(L"DirectionUp");
|
||||
break;
|
||||
case Direction::Down:
|
||||
directionString = RS_(L"DirectionDown");
|
||||
break;
|
||||
}
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"MoveFocusWithArgCommandKey")),
|
||||
directionString)
|
||||
};
|
||||
}
|
||||
|
||||
winrt::hstring AdjustFontSizeArgs::GenerateName() const
|
||||
{
|
||||
// If the amount is just 1 (or -1), we'll just return "Increase font
|
||||
// size" (or "Decrease font size"). If the amount delta has a greater
|
||||
// absolute value, we'll include it like"
|
||||
// * Decrease font size, amount: {delta}"
|
||||
if (_Delta < 0)
|
||||
{
|
||||
return _Delta == -1 ? RS_(L"DecreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"DecreaseFontSizeWithAmountCommandKey")),
|
||||
-_Delta)
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
return _Delta == 1 ? RS_(L"IncreaseFontSizeCommandKey") :
|
||||
winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"IncreaseFontSizeWithAmountCommandKey")),
|
||||
_Delta)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SplitPaneArgs::GenerateName() const
|
||||
{
|
||||
// The string will be similar to the following:
|
||||
// * "Duplicate pane[, split: <direction>][, new terminal arguments...]"
|
||||
// * "Split pane[, split: <direction>][, new terminal arguments...]"
|
||||
//
|
||||
// Direction will only be added to the string if the split direction is
|
||||
// not "auto".
|
||||
// If this is a "duplicate pane" action, then the new terminal arguments
|
||||
// will be omitted (as they're unused)
|
||||
|
||||
std::wstringstream ss;
|
||||
if (_SplitMode == SplitType::Duplicate)
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"DuplicatePaneCommandKey"));
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << std::wstring_view(RS_(L"SplitPaneCommandKey"));
|
||||
}
|
||||
ss << L", ";
|
||||
|
||||
// This text is intentionally _not_ localized, to attempt to mirror the
|
||||
// exact syntax that the property would have in JSON.
|
||||
switch (_SplitStyle)
|
||||
{
|
||||
case SplitState::Vertical:
|
||||
ss << L"split: vertical, ";
|
||||
break;
|
||||
case SplitState::Horizontal:
|
||||
ss << L"split: horizontal, ";
|
||||
break;
|
||||
}
|
||||
|
||||
winrt::hstring newTerminalArgsStr;
|
||||
if (_TerminalArgs)
|
||||
{
|
||||
newTerminalArgsStr = _TerminalArgs.GenerateName();
|
||||
}
|
||||
|
||||
if (_SplitMode != SplitType::Duplicate && !newTerminalArgsStr.empty())
|
||||
{
|
||||
ss << newTerminalArgsStr.c_str();
|
||||
ss << L", ";
|
||||
}
|
||||
|
||||
// Chop off the last ", "
|
||||
auto s = ss.str();
|
||||
return winrt::hstring{ s.substr(0, s.size() - 2) };
|
||||
}
|
||||
|
||||
winrt::hstring OpenSettingsArgs::GenerateName() const
|
||||
{
|
||||
switch (_Target)
|
||||
{
|
||||
case SettingsTarget::DefaultsFile:
|
||||
return RS_(L"OpenDefaultSettingsCommandKey");
|
||||
case SettingsTarget::AllFiles:
|
||||
return RS_(L"OpenBothSettingsFilesCommandKey");
|
||||
default:
|
||||
return RS_(L"OpenSettingsCommandKey");
|
||||
}
|
||||
}
|
||||
|
||||
winrt::hstring SetTabColorArgs::GenerateName() const
|
||||
{
|
||||
// "Set tab color to #RRGGBB"
|
||||
// "Reset tab color"
|
||||
if (_TabColor)
|
||||
{
|
||||
til::color c{ _TabColor.Value() };
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"SetTabColorCommandKey")),
|
||||
c.ToHexString(true))
|
||||
};
|
||||
}
|
||||
|
||||
return RS_(L"ResetTabColorCommandKey");
|
||||
}
|
||||
|
||||
winrt::hstring RenameTabArgs::GenerateName() const
|
||||
{
|
||||
// "Rename tab to \"{_Title}\""
|
||||
// "Reset tab title"
|
||||
if (!_Title.empty())
|
||||
{
|
||||
return winrt::hstring{
|
||||
fmt::format(std::wstring_view(RS_(L"RenameTabCommandKey")),
|
||||
_Title.c_str())
|
||||
};
|
||||
}
|
||||
return RS_(L"ResetTabNameCommandKey");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -59,6 +59,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view ProfileKey{ "profile" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const winrt::TerminalApp::NewTerminalArgs& other)
|
||||
{
|
||||
return other.Commandline() == _Commandline &&
|
||||
|
@ -103,6 +105,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view SingleLineKey{ "singleLine" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<CopyTextArgs>();
|
||||
|
@ -130,6 +134,8 @@ namespace winrt::TerminalApp::implementation
|
|||
GETSET_PROPERTY(winrt::TerminalApp::NewTerminalArgs, TerminalArgs, nullptr);
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<NewTabArgs>();
|
||||
|
@ -156,6 +162,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view TabIndexKey{ "index" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SwitchToTabArgs>();
|
||||
|
@ -220,6 +228,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<ResizePaneArgs>();
|
||||
|
@ -256,6 +266,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view DirectionKey{ "direction" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<MoveFocusArgs>();
|
||||
|
@ -292,6 +304,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view AdjustFontSizeDelta{ "delta" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<AdjustFontSizeArgs>();
|
||||
|
@ -358,13 +372,16 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view SplitModeKey{ "splitMode" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SplitPaneArgs>();
|
||||
if (otherAsUs)
|
||||
{
|
||||
return otherAsUs->_SplitStyle == _SplitStyle &&
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs &&
|
||||
(otherAsUs->_TerminalArgs ? otherAsUs->_TerminalArgs.Equals(_TerminalArgs) :
|
||||
otherAsUs->_TerminalArgs == _TerminalArgs) &&
|
||||
otherAsUs->_SplitMode == _SplitMode;
|
||||
}
|
||||
return false;
|
||||
|
@ -424,6 +441,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view TargetKey{ "target" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<OpenSettingsArgs>();
|
||||
|
@ -453,6 +472,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view ColorKey{ "color" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<SetTabColorArgs>();
|
||||
|
@ -488,6 +509,8 @@ namespace winrt::TerminalApp::implementation
|
|||
static constexpr std::string_view TitleKey{ "title" };
|
||||
|
||||
public:
|
||||
hstring GenerateName() const;
|
||||
|
||||
bool Equals(const IActionArgs& other)
|
||||
{
|
||||
auto otherAsUs = other.try_as<RenameTabArgs>();
|
||||
|
|
|
@ -6,6 +6,7 @@ namespace TerminalApp
|
|||
interface IActionArgs
|
||||
{
|
||||
Boolean Equals(IActionArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
interface IActionEventArgs
|
||||
|
@ -53,7 +54,9 @@ namespace TerminalApp
|
|||
// 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; };
|
||||
|
||||
Boolean Equals(NewTerminalArgs other);
|
||||
String GenerateName();
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionEventArgs : IActionEventArgs
|
||||
|
|
|
@ -238,6 +238,17 @@ namespace winrt::TerminalApp::implementation
|
|||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleToggleCommandPalette(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
// TODO GH#6677: When we add support for commandline mode, first set the
|
||||
// mode that the command palette should be in, before making it visible.
|
||||
CommandPalette().Visibility(CommandPalette().Visibility() == Visibility::Visible ?
|
||||
Visibility::Collapsed :
|
||||
Visibility::Visible);
|
||||
args.Handled(true);
|
||||
}
|
||||
|
||||
void TerminalPage::_HandleSetTabColor(const IInspectable& /*sender*/,
|
||||
const TerminalApp::ActionEventArgs& args)
|
||||
{
|
||||
|
|
|
@ -55,8 +55,11 @@ namespace winrt::TerminalApp::implementation
|
|||
{
|
||||
for (auto& kv : _keyShortcuts)
|
||||
{
|
||||
if (kv.second.Action() == actionAndArgs.Action() &&
|
||||
kv.second.Args().Equals(actionAndArgs.Args()))
|
||||
const auto action = kv.second.Action();
|
||||
const auto args = kv.second.Args();
|
||||
const auto actionMatched = action == actionAndArgs.Action();
|
||||
const auto argsMatched = args ? args.Equals(actionAndArgs.Args()) : args == actionAndArgs.Args();
|
||||
if (actionMatched && argsMatched)
|
||||
{
|
||||
return kv.first;
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
|||
|
||||
// Iterate over all the possible actions in the names list, and see if
|
||||
// it has a binding.
|
||||
for (auto& actionName : ActionAndArgs::ActionNamesMap)
|
||||
for (auto& actionName : ActionAndArgs::ActionKeyNamesMap)
|
||||
{
|
||||
const auto searchedForName = actionName.first;
|
||||
const auto searchedForAction = actionName.second;
|
||||
|
@ -85,7 +85,7 @@ Json::Value winrt::TerminalApp::implementation::AppKeyBindings::ToJson()
|
|||
// - 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
|
||||
// `command` string and a `keys` array, where `command` is one of the names
|
||||
// listed in `ActionAndArgs::ActionNamesMap`, and `keys` is an array of
|
||||
// listed in `ActionAndArgs::ActionKeyNamesMap`, and `keys` is an array of
|
||||
// keypresses. Currently, the array should contain a single string, which can
|
||||
// be deserialized into a KeyChord.
|
||||
// - Applies the deserialized keybindings to the provided `bindings` object. If
|
||||
|
|
191
src/cascadia/TerminalApp/Command.cpp
Normal file
191
src/cascadia/TerminalApp/Command.cpp
Normal file
|
@ -0,0 +1,191 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "Command.h"
|
||||
#include "Command.g.cpp"
|
||||
|
||||
#include "Utils.h"
|
||||
#include "ActionAndArgs.h"
|
||||
#include <LibraryResources.h>
|
||||
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
using namespace winrt::TerminalApp;
|
||||
|
||||
static constexpr std::string_view NameKey{ "name" };
|
||||
static constexpr std::string_view IconPathKey{ "iconPath" };
|
||||
static constexpr std::string_view ActionKey{ "command" };
|
||||
static constexpr std::string_view ArgsKey{ "args" };
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
// Function Description:
|
||||
// - attempt to get the name of this command from the provided json object.
|
||||
// * If the "name" property is a string, return that value.
|
||||
// * If the "name" property is an object, attempt to lookup the string
|
||||
// resource specified by the "key" property, to support localizable
|
||||
// command names.
|
||||
// Arguments:
|
||||
// - json: The Json::Value representing the command object we should get the name for.
|
||||
// Return Value:
|
||||
// - the empty string if we couldn't find a name, otherwise the command's name.
|
||||
static winrt::hstring _nameFromJson(const Json::Value& json)
|
||||
{
|
||||
if (const auto name{ json[JsonKey(NameKey)] })
|
||||
{
|
||||
if (name.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
if (const auto keyJson{ name[JsonKey("key")] })
|
||||
{
|
||||
// Make sure the key is present before we try
|
||||
// loading it. Otherwise we'll crash
|
||||
const auto resourceKey = GetWstringFromJson(keyJson);
|
||||
if (HasLibraryResourceWithName(resourceKey))
|
||||
{
|
||||
return GetLibraryResourceString(resourceKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
else if (name.isString())
|
||||
{
|
||||
auto nameStr = name.asString();
|
||||
return winrt::to_hstring(nameStr);
|
||||
}
|
||||
}
|
||||
|
||||
return L"";
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the name for the command specified in `json`. If there is no "name"
|
||||
// property in the provided json object, then instead generate a name for
|
||||
// the provided ActionAndArgs.
|
||||
// Arguments:
|
||||
// - json: json for the command to generate a name for.
|
||||
// - actionAndArgs: An ActionAndArgs object to use to generate a name for,
|
||||
// if the json object doesn't contain a "name".
|
||||
// Return Value:
|
||||
// - The "name" from the json, or the generated name from ActionAndArgs::GenerateName
|
||||
static winrt::hstring _nameFromJsonOrAction(const Json::Value& json,
|
||||
winrt::com_ptr<ActionAndArgs> actionAndArgs)
|
||||
{
|
||||
auto manualName = _nameFromJson(json);
|
||||
if (!manualName.empty())
|
||||
{
|
||||
return manualName;
|
||||
}
|
||||
if (!actionAndArgs)
|
||||
{
|
||||
return L"";
|
||||
}
|
||||
|
||||
return actionAndArgs->GenerateName();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deserialize a Command from the `json` object. The json object should
|
||||
// contain a "name" and "action", and optionally an "icon".
|
||||
// * "name": string|object - the name of the command to display in the
|
||||
// command palette. If this is an object, look for the "key" property,
|
||||
// and try to load the string from our resources instead.
|
||||
// * "action": string|object - A ShortcutAction, either as a name or as an
|
||||
// ActionAndArgs serialization. See ActionAndArgs::FromJson for details.
|
||||
// If this is null, we'll remove this command from the list of commands.
|
||||
// Arguments:
|
||||
// - json: the Json::Value to deserialize into a Command
|
||||
// - warnings: If there were any warnings during parsing, they'll be
|
||||
// appended to this vector.
|
||||
// Return Value:
|
||||
// - the newly constructed Command object.
|
||||
winrt::com_ptr<Command> Command::FromJson(const Json::Value& json,
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings>& warnings)
|
||||
{
|
||||
auto result = winrt::make_self<Command>();
|
||||
|
||||
// TODO GH#6644: iconPath not implemented quite yet. Can't seem to get
|
||||
// the binding quite right. Additionally, do we want it to be an image,
|
||||
// or a FontIcon? I've had difficulty binding either/or.
|
||||
|
||||
if (const auto actionJson{ json[JsonKey(ActionKey)] })
|
||||
{
|
||||
auto actionAndArgs = ActionAndArgs::FromJson(actionJson, warnings);
|
||||
|
||||
if (actionAndArgs)
|
||||
{
|
||||
result->_setAction(*actionAndArgs);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Something like
|
||||
// { name: "foo", action: "unbound" }
|
||||
// will _remove_ the "foo" command, by returning null here.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
result->_setName(_nameFromJsonOrAction(json, actionAndArgs));
|
||||
}
|
||||
else
|
||||
{
|
||||
// { name: "foo", action: null } will land in this case, which
|
||||
// should also be used for unbinding.
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (result->_Name.empty())
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Attempt to parse all the json objects in `json` into new Command
|
||||
// objects, and add them to the map of commands.
|
||||
// - If any parsed command has
|
||||
// the same Name as an existing command in commands, the new one will
|
||||
// layer on top of the existing one.
|
||||
// Arguments:
|
||||
// - commands: a map of Name->Command which new commands should be layered upon.
|
||||
// - json: A Json::Value containing an array of serialized commands
|
||||
// Return Value:
|
||||
// - A vector containing any warnings detected while parsing
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> Command::LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json)
|
||||
{
|
||||
std::vector<::TerminalApp::SettingsLoadWarnings> warnings;
|
||||
|
||||
for (const auto& value : json)
|
||||
{
|
||||
if (value.isObject())
|
||||
{
|
||||
try
|
||||
{
|
||||
auto result = Command::FromJson(value, warnings);
|
||||
if (result)
|
||||
{
|
||||
// Override commands with the same name
|
||||
commands.insert_or_assign(result->Name(), *result);
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there wasn't a parsed command, then try to get the
|
||||
// name from the json blob. If that name currently
|
||||
// exists in our list of commands, we should remove it.
|
||||
const auto name = _nameFromJson(value);
|
||||
if (!name.empty())
|
||||
{
|
||||
commands.erase(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
return warnings;
|
||||
}
|
||||
}
|
45
src/cascadia/TerminalApp/Command.h
Normal file
45
src/cascadia/TerminalApp/Command.h
Normal file
|
@ -0,0 +1,45 @@
|
|||
/*++
|
||||
Copyright (c) Microsoft Corporation
|
||||
Licensed under the MIT license.
|
||||
|
||||
Module Name:
|
||||
- Command.h
|
||||
|
||||
Abstract:
|
||||
- A command represents a single entry in the Command Palette. This is an object
|
||||
that has a user facing "name" to display to the user, and an associated action
|
||||
which can be dispatched.
|
||||
|
||||
- For more information, see GH#2046, #5400, #5674, and #6635
|
||||
|
||||
Author(s):
|
||||
- Mike Griese - June 2020
|
||||
|
||||
--*/
|
||||
#pragma once
|
||||
|
||||
#include "Command.g.h"
|
||||
#include "TerminalWarnings.h"
|
||||
#include "..\inc\cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct Command : CommandT<Command>
|
||||
{
|
||||
Command() = default;
|
||||
|
||||
static winrt::com_ptr<Command> FromJson(const Json::Value& json, std::vector<::TerminalApp::SettingsLoadWarnings>& warnings);
|
||||
static std::vector<::TerminalApp::SettingsLoadWarnings> LayerJson(std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& commands,
|
||||
const Json::Value& json);
|
||||
|
||||
WINRT_CALLBACK(PropertyChanged, Windows::UI::Xaml::Data::PropertyChangedEventHandler);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, Name, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::TerminalApp::ActionAndArgs, Action, _PropertyChangedHandlers);
|
||||
OBSERVABLE_GETSET_PROPERTY(winrt::hstring, KeyChordText, _PropertyChangedHandlers);
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(Command);
|
||||
}
|
16
src/cascadia/TerminalApp/Command.idl
Normal file
16
src/cascadia/TerminalApp/Command.idl
Normal file
|
@ -0,0 +1,16 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../ShortcutActionDispatch.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass Command : Windows.UI.Xaml.Data.INotifyPropertyChanged
|
||||
{
|
||||
Command();
|
||||
|
||||
String Name;
|
||||
ActionAndArgs Action;
|
||||
String KeyChordText;
|
||||
}
|
||||
}
|
414
src/cascadia/TerminalApp/CommandPalette.cpp
Normal file
414
src/cascadia/TerminalApp/CommandPalette.cpp
Normal file
|
@ -0,0 +1,414 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "pch.h"
|
||||
#include "CommandPalette.h"
|
||||
|
||||
#include "CommandPalette.g.cpp"
|
||||
#include <winrt/Microsoft.Terminal.Settings.h>
|
||||
|
||||
using namespace winrt;
|
||||
using namespace winrt::TerminalApp;
|
||||
using namespace winrt::Windows::UI::Core;
|
||||
using namespace winrt::Windows::UI::Xaml;
|
||||
using namespace winrt::Windows::System;
|
||||
using namespace winrt::Windows::Foundation;
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
CommandPalette::CommandPalette()
|
||||
{
|
||||
InitializeComponent();
|
||||
|
||||
_filteredActions = winrt::single_threaded_observable_vector<winrt::TerminalApp::Command>();
|
||||
_allActions = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
|
||||
if (CommandPaletteShadow())
|
||||
{
|
||||
// Hook up the shadow on the command palette to the backdrop that
|
||||
// will actually show it. This needs to be done at runtime, and only
|
||||
// if the shadow actually exists. ThemeShadow isn't supported below
|
||||
// version 18362.
|
||||
CommandPaletteShadow().Receivers().Append(_shadowBackdrop());
|
||||
// "raise" the command palette up by 16 units, so it will cast a shadow.
|
||||
_backdrop().Translation({ 0, 0, 16 });
|
||||
}
|
||||
|
||||
// Whatever is hosting us will enable us by setting our visibility to
|
||||
// "Visible". When that happens, set focus to our search box.
|
||||
RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (Visibility() == Visibility::Visible)
|
||||
{
|
||||
_searchBox().Focus(FocusState::Programmatic);
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Raise an event to return control to the Terminal.
|
||||
_close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the focus up or down the list of commands. If we're at the top,
|
||||
// we'll loop around to the bottom, and vice-versa.
|
||||
// Arguments:
|
||||
// - moveDown: if true, we're attempting to move to the next item in the
|
||||
// list. Otherwise, we're attempting to move to the previous.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_selectNextItem(const bool moveDown)
|
||||
{
|
||||
const auto selected = _filteredActionsView().SelectedIndex();
|
||||
const int numItems = ::base::saturated_cast<int>(_filteredActionsView().Items().Size());
|
||||
// Wraparound math. By adding numItems and then calculating modulo numItems,
|
||||
// we clamp the values to the range [0, numItems) while still supporting moving
|
||||
// upward from 0 to numItems - 1.
|
||||
const auto newIndex = ((numItems + selected + (moveDown ? 1 : -1)) % numItems);
|
||||
_filteredActionsView().SelectedIndex(newIndex);
|
||||
_filteredActionsView().ScrollIntoView(_filteredActionsView().SelectedItem());
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process keystrokes in the input box. This is used for moving focus up
|
||||
// and down the list of commands in Action mode, and for executing
|
||||
// commands in both Action mode and Commandline mode.
|
||||
// Arguments:
|
||||
// - e: the KeyRoutedEventArgs containing info about the keystroke.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_keyDownHandler(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e)
|
||||
{
|
||||
auto key = e.OriginalKey();
|
||||
|
||||
if (key == VirtualKey::Up)
|
||||
{
|
||||
// Action Mode: Move focus to the next item in the list.
|
||||
_selectNextItem(false);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Down)
|
||||
{
|
||||
// Action Mode: Move focus to the previous item in the list.
|
||||
_selectNextItem(true);
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Enter)
|
||||
{
|
||||
// Action Mode: Dispatch the action of the selected command.
|
||||
|
||||
if (const auto selectedItem = _filteredActionsView().SelectedItem())
|
||||
{
|
||||
if (const auto data = selectedItem.try_as<Command>())
|
||||
{
|
||||
const auto actionAndArgs = data.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
else if (key == VirtualKey::Escape)
|
||||
{
|
||||
// Action Mode: Dismiss the palette if the text is empty, otherwise clear the search string.
|
||||
if (_searchBox().Text().empty())
|
||||
{
|
||||
_close();
|
||||
}
|
||||
else
|
||||
{
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
e.Handled(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is triggered when someone clicks anywhere in the bounds of
|
||||
// the window that's _not_ the command palette UI. When that happens,
|
||||
// we'll want to dismiss the palette.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_rootPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
|
||||
{
|
||||
_close();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is only triggered when someone clicks in the space right
|
||||
// next to the text box in the command palette. We _don't_ want that click
|
||||
// to light dismiss the palette, so we'll mark it handled here.
|
||||
// Arguments:
|
||||
// - e: the PointerRoutedEventArgs that we want to mark as handled
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_backdropPointerPressed(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e)
|
||||
{
|
||||
e.Handled(true);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This event is called when the user clicks on an individual item from
|
||||
// the list. We'll get the item that was clicked and dispatch the command
|
||||
// that the user clicked on.
|
||||
// Arguments:
|
||||
// - e: an ItemClickEventArgs who's ClickedItem() will be the command that was clicked on.
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_listItemClicked(Windows::Foundation::IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::Controls::ItemClickEventArgs const& e)
|
||||
{
|
||||
if (auto command{ e.ClickedItem().try_as<TerminalApp::Command>() })
|
||||
{
|
||||
const auto actionAndArgs = command.Action();
|
||||
_dispatch.DoAction(actionAndArgs);
|
||||
_close();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Event handler for when the text in the input box changes. In Action
|
||||
// Mode, we'll update the list of displayed commands, and select the first one.
|
||||
// Arguments:
|
||||
// - <unused>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_filterTextChanged(IInspectable const& /*sender*/,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& /*args*/)
|
||||
{
|
||||
_updateFilteredActions();
|
||||
_filteredActionsView().SelectedIndex(0);
|
||||
|
||||
_noMatchesText().Visibility(_filteredActions.Size() > 0 ? Visibility::Collapsed : Visibility::Visible);
|
||||
}
|
||||
|
||||
Collections::IObservableVector<Command> CommandPalette::FilteredActions()
|
||||
{
|
||||
return _filteredActions;
|
||||
}
|
||||
|
||||
void CommandPalette::SetActions(Collections::IVector<TerminalApp::Command> const& actions)
|
||||
{
|
||||
_allActions = actions;
|
||||
_updateFilteredActions();
|
||||
}
|
||||
|
||||
// This is a helper to aid in sorting commands by their `Name`s, alphabetically.
|
||||
static bool _compareCommandNames(const TerminalApp::Command& lhs, const TerminalApp::Command& rhs)
|
||||
{
|
||||
std::wstring_view leftName{ lhs.Name() };
|
||||
std::wstring_view rightName{ rhs.Name() };
|
||||
return leftName.compare(rightName) < 0;
|
||||
}
|
||||
|
||||
// This is a helper struct to aid in sorting Commands by a given weighting.
|
||||
struct WeightedCommand
|
||||
{
|
||||
TerminalApp::Command command;
|
||||
int weight;
|
||||
|
||||
bool operator<(const WeightedCommand& other) const
|
||||
{
|
||||
// If two commands have the same weight, then we'll sort them alphabetically.
|
||||
if (weight == other.weight)
|
||||
{
|
||||
return !_compareCommandNames(command, other.command);
|
||||
}
|
||||
return weight < other.weight;
|
||||
}
|
||||
};
|
||||
|
||||
// Method Description:
|
||||
// - Update our list of filtered actions to reflect the current contents of
|
||||
// the input box. For more details on which commands will be displayed,
|
||||
// see `_getWeight`.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_updateFilteredActions()
|
||||
{
|
||||
_filteredActions.Clear();
|
||||
auto searchText = _searchBox().Text();
|
||||
const bool addAll = searchText.empty();
|
||||
|
||||
// If there's no filter text, then just add all the commands in order to the list.
|
||||
// - TODO GH#6647:Possibly add the MRU commands first in order, followed
|
||||
// by the rest of the commands.
|
||||
if (addAll)
|
||||
{
|
||||
// Add all the commands, but make sure they're sorted alphabetically.
|
||||
std::vector<TerminalApp::Command> sortedCommands;
|
||||
sortedCommands.reserve(_allActions.Size());
|
||||
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
sortedCommands.push_back(action);
|
||||
}
|
||||
std::sort(sortedCommands.begin(),
|
||||
sortedCommands.end(),
|
||||
_compareCommandNames);
|
||||
|
||||
for (auto action : sortedCommands)
|
||||
{
|
||||
_filteredActions.Append(action);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Here, there was some filter text.
|
||||
// Show these actions in a weighted order.
|
||||
// - Matching the first character of a word, then the first char of a
|
||||
// subsequent word seems better than just "the order they appear in
|
||||
// the list".
|
||||
// - TODO GH#6647:"Recently used commands" ordering also seems valuable.
|
||||
// * This could be done by weighting the recently used commands
|
||||
// higher the more recently they were used, then weighting all
|
||||
// the unused commands as 1
|
||||
|
||||
// Use a priority queue to order commands so that "better" matches
|
||||
// appear first in the list. The ordering will be determined by the
|
||||
// match weight produced by _getWeight.
|
||||
std::priority_queue<WeightedCommand> heap;
|
||||
for (auto action : _allActions)
|
||||
{
|
||||
const auto weight = CommandPalette::_getWeight(searchText, action.Name());
|
||||
if (weight > 0)
|
||||
{
|
||||
WeightedCommand wc;
|
||||
wc.command = action;
|
||||
wc.weight = weight;
|
||||
heap.push(wc);
|
||||
}
|
||||
}
|
||||
|
||||
// At this point, all the commands in heap are matches. We've also
|
||||
// sorted commands with the same weight alphabetically.
|
||||
// Remove everything in-order from the queue, and add to the list of
|
||||
// filtered actions.
|
||||
while (!heap.empty())
|
||||
{
|
||||
auto top = heap.top();
|
||||
heap.pop();
|
||||
_filteredActions.Append(top.command);
|
||||
}
|
||||
}
|
||||
|
||||
// Function Description:
|
||||
// - Calculates a "weighting" by which should be used to order a command
|
||||
// name relative to other names, given a specific search string.
|
||||
// Currently, this is based off of two factors:
|
||||
// * The weight is incremented once for each matched character of the
|
||||
// search text.
|
||||
// * If a matching character from the search text was found at the start
|
||||
// of a word in the name, then we increment the weight again.
|
||||
// * For example, for a search string "sp", we want "Split Pane" to
|
||||
// appear in the list before "Close Pane"
|
||||
// * Consecutive matches will be weighted higher than matches with
|
||||
// characters in between the search characters.
|
||||
// - This will return 0 if the command should not be shown. If all the
|
||||
// characters of search text appear in order in `name`, then this function
|
||||
// will return a positive number. There can be any number of characters
|
||||
// separating consecutive characters in searchText.
|
||||
// * For example:
|
||||
// "name": "New Tab"
|
||||
// "name": "Close Tab"
|
||||
// "name": "Close Pane"
|
||||
// "name": "[-] Split Horizontal"
|
||||
// "name": "[ | ] Split Vertical"
|
||||
// "name": "Next Tab"
|
||||
// "name": "Prev Tab"
|
||||
// "name": "Open Settings"
|
||||
// "name": "Open Media Controls"
|
||||
// * "open" should return both "**Open** Settings" and "**Open** Media Controls".
|
||||
// * "Tab" would return "New **Tab**", "Close **Tab**", "Next **Tab**" and "Prev
|
||||
// **Tab**".
|
||||
// * "P" would return "Close **P**ane", "[-] S**p**lit Horizontal", "[ | ]
|
||||
// S**p**lit Vertical", "**P**rev Tab", "O**p**en Settings" and "O**p**en Media
|
||||
// Controls".
|
||||
// * "sv" would return "[ | ] Split Vertical" (by matching the **S** in
|
||||
// "Split", then the **V** in "Vertical").
|
||||
// Arguments:
|
||||
// - searchText: the string of text to search for in `name`
|
||||
// - name: the name to check
|
||||
// Return Value:
|
||||
// - the relative weight of this match
|
||||
int CommandPalette::_getWeight(const winrt::hstring& searchText,
|
||||
const winrt::hstring& name)
|
||||
{
|
||||
int totalWeight = 0;
|
||||
bool lastWasSpace = true;
|
||||
|
||||
auto it = name.cbegin();
|
||||
|
||||
for (auto searchChar : searchText)
|
||||
{
|
||||
searchChar = std::towlower(searchChar);
|
||||
// Advance the iterator to the next character that we're looking
|
||||
// for.
|
||||
|
||||
bool lastWasMatch = true;
|
||||
while (true)
|
||||
{
|
||||
// If we are at the end of the name string, we haven't found
|
||||
// it.
|
||||
if (it == name.cend())
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// found it
|
||||
if (std::towlower(*it) == searchChar)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
lastWasSpace = *it == L' ';
|
||||
++it;
|
||||
lastWasMatch = false;
|
||||
}
|
||||
|
||||
// Advance the iterator by one character so that we don't
|
||||
// end up on the same character in the next iteration.
|
||||
++it;
|
||||
|
||||
totalWeight += 1;
|
||||
totalWeight += lastWasSpace ? 1 : 0;
|
||||
totalWeight += (lastWasMatch) ? 1 : 0;
|
||||
}
|
||||
|
||||
return totalWeight;
|
||||
}
|
||||
|
||||
void CommandPalette::SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch)
|
||||
{
|
||||
_dispatch = dispatch;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Dismiss the command palette. This will:
|
||||
// * select all the current text in the input box
|
||||
// * set our visibility to Hidden
|
||||
// * raise our Closed event, so the page can return focus to the active Terminal
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
void CommandPalette::_close()
|
||||
{
|
||||
Visibility(Visibility::Collapsed);
|
||||
|
||||
// Clear the text box each time we close the dialog. This is consistent with VsCode.
|
||||
_searchBox().Text(L"");
|
||||
}
|
||||
|
||||
}
|
48
src/cascadia/TerminalApp/CommandPalette.h
Normal file
48
src/cascadia/TerminalApp/CommandPalette.h
Normal file
|
@ -0,0 +1,48 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "CommandPalette.g.h"
|
||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||
|
||||
namespace winrt::TerminalApp::implementation
|
||||
{
|
||||
struct CommandPalette : CommandPaletteT<CommandPalette>
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> FilteredActions();
|
||||
void SetActions(Windows::Foundation::Collections::IVector<TerminalApp::Command> const& actions);
|
||||
|
||||
void SetDispatch(const winrt::TerminalApp::ShortcutActionDispatch& dispatch);
|
||||
|
||||
private:
|
||||
friend struct CommandPaletteT<CommandPalette>; // for Xaml to bind events
|
||||
|
||||
Windows::Foundation::Collections::IObservableVector<TerminalApp::Command> _filteredActions{ nullptr };
|
||||
Windows::Foundation::Collections::IVector<TerminalApp::Command> _allActions{ nullptr };
|
||||
winrt::TerminalApp::ShortcutActionDispatch _dispatch;
|
||||
|
||||
void _filterTextChanged(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::RoutedEventArgs const& args);
|
||||
void _keyDownHandler(Windows::Foundation::IInspectable const& sender,
|
||||
Windows::UI::Xaml::Input::KeyRoutedEventArgs const& e);
|
||||
|
||||
void _rootPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
void _backdropPointerPressed(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
|
||||
|
||||
void _listItemClicked(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Controls::ItemClickEventArgs const& e);
|
||||
|
||||
void _selectNextItem(const bool moveDown);
|
||||
|
||||
void _updateFilteredActions();
|
||||
static int _getWeight(const winrt::hstring& searchText, const winrt::hstring& name);
|
||||
void _close();
|
||||
};
|
||||
}
|
||||
|
||||
namespace winrt::TerminalApp::factory_implementation
|
||||
{
|
||||
BASIC_FACTORY(CommandPalette);
|
||||
}
|
18
src/cascadia/TerminalApp/CommandPalette.idl
Normal file
18
src/cascadia/TerminalApp/CommandPalette.idl
Normal file
|
@ -0,0 +1,18 @@
|
|||
// Copyright (c) Microsoft Corporation.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
import "../Command.idl";
|
||||
|
||||
namespace TerminalApp
|
||||
{
|
||||
[default_interface] runtimeclass CommandPalette : Windows.UI.Xaml.Controls.Grid
|
||||
{
|
||||
CommandPalette();
|
||||
|
||||
Windows.Foundation.Collections.IObservableVector<Command> FilteredActions { get; };
|
||||
|
||||
void SetActions(Windows.Foundation.Collections.IVector<Command> actions);
|
||||
|
||||
void SetDispatch(ShortcutActionDispatch dispatch);
|
||||
}
|
||||
}
|
204
src/cascadia/TerminalApp/CommandPalette.xaml
Normal file
204
src/cascadia/TerminalApp/CommandPalette.xaml
Normal file
|
@ -0,0 +1,204 @@
|
|||
<!-- Copyright (c) Microsoft Corporation. All rights reserved. Licensed under
|
||||
the MIT License. See LICENSE in the project root for license information. -->
|
||||
<Grid
|
||||
x:Class="TerminalApp.CommandPalette"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TerminalApp"
|
||||
xmlns:mux="using:Microsoft.UI.Xaml.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:Windows10version1903="http://schemas.microsoft.com/winfx/2006/xaml/presentation?IsApiContractPresent(Windows.Foundation.UniversalApiContract, 8)"
|
||||
PointerPressed="_rootPointerPressed"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid.Resources>
|
||||
<ResourceDictionary>
|
||||
|
||||
<!-- ThemeShadow is only on 18362. This "Windows10version1903" bit
|
||||
adds it conditionally -->
|
||||
<Windows10version1903:ThemeShadow x:Name="CommandPaletteShadow" />
|
||||
|
||||
<ResourceDictionary.ThemeDictionaries>
|
||||
<ResourceDictionary x:Key="Dark">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#333333" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlForeground" Color="#B5B5B5"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#B5B5B5"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundPointerOver" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#333333"/>
|
||||
<SolidColorBrush x:Key="TextControlForegroundFocused" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#404040"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="Light">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="#CCCCCC" />
|
||||
</Style>
|
||||
<!-- TextBox colors !-->
|
||||
<SolidColorBrush x:Key="TextControlBackground" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextBoxPlaceholderTextThemeBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrush" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForeground" Color="#636363"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundPointerOver" Color="#DADADA"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushPointerOver" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPointerOver" Color="#FF4343"/>
|
||||
|
||||
<SolidColorBrush x:Key="TextControlBackgroundFocused" Color="#CCCCCC"/>
|
||||
<SolidColorBrush x:Key="TextControlBorderBrushFocused" Color="#636363"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonForegroundPressed" Color="#FFFFFF"/>
|
||||
<SolidColorBrush x:Key="TextControlButtonBackgroundPressed" Color="#FF4343"/>
|
||||
|
||||
</ResourceDictionary>
|
||||
<ResourceDictionary x:Key="HighContrast">
|
||||
<Style x:Key="CommandPaletteBackground" TargetType="Grid">
|
||||
<Setter Property="Background" Value="{ThemeResource SystemColorWindowColor}" />
|
||||
</Style>
|
||||
</ResourceDictionary>
|
||||
</ResourceDictionary.ThemeDictionaries>
|
||||
</ResourceDictionary>
|
||||
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="2*" />
|
||||
<ColumnDefinition Width="6*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="8*"/>
|
||||
<RowDefinition Height="2*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Setting the row/col span of this shadow backdrop is a bit of a hack. In
|
||||
order to receive pointer events, an element needs to be _not_ transparent.
|
||||
However, we want to be able to eat all the clicks outside the immediate
|
||||
bounds of the command palette, and we don't want a semi-transparent overlay
|
||||
over all of the UI. Fortunately, if we make this _shadowBackdrop the size of
|
||||
the entire page, then it can be mostly transparent, and cause the root grid
|
||||
to receive clicks _anywhere_ in its bounds. -->
|
||||
|
||||
<Grid
|
||||
x:Name="_shadowBackdrop"
|
||||
Background="Transparent"
|
||||
Grid.Column="0"
|
||||
Grid.Row="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Grid.RowSpan="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="_backdrop"
|
||||
Style="{ThemeResource CommandPaletteBackground}"
|
||||
CornerRadius="{ThemeResource ControlCornerRadius}"
|
||||
PointerPressed="_backdropPointerPressed"
|
||||
Margin="8"
|
||||
Grid.Column="1"
|
||||
Grid.Row="0"
|
||||
Windows10version1903:Shadow="{StaticResource CommandPaletteShadow}"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBox
|
||||
Grid.Row="0"
|
||||
x:Uid="CommandPalette_SearchBox"
|
||||
x:Name="_searchBox"
|
||||
Margin="8"
|
||||
IsSpellCheckEnabled="False"
|
||||
TextChanged="_filterTextChanged"
|
||||
KeyDown="_keyDownHandler"
|
||||
Text="">
|
||||
</TextBox>
|
||||
|
||||
<TextBlock
|
||||
Padding="16"
|
||||
x:Name="_noMatchesText"
|
||||
x:Uid="CommandPalette_NoMatchesText"
|
||||
FontStyle="Italic"
|
||||
Visibility="Collapsed"
|
||||
Grid.Row="1">
|
||||
</TextBlock>
|
||||
|
||||
<ListView
|
||||
Grid.Row="2"
|
||||
x:Name="_filteredActionsView"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
SelectionMode="Single"
|
||||
CanReorderItems="False"
|
||||
AllowDrop="False"
|
||||
IsItemClickEnabled="True"
|
||||
ItemClick="_listItemClicked"
|
||||
ItemsSource="{x:Bind FilteredActions}">
|
||||
|
||||
<ItemsControl.ItemTemplate >
|
||||
<DataTemplate x:DataType="local:Command">
|
||||
|
||||
<!-- This HorizontalContentAlignment="Stretch" is important
|
||||
to make sure it takes the entire width of the line -->
|
||||
<ListViewItem HorizontalContentAlignment="Stretch"
|
||||
AutomationProperties.Name="{x:Bind Name, Mode=OneWay}"
|
||||
AutomationProperties.AcceleratorKey="{x:Bind KeyChordText, Mode=OneWay}">
|
||||
|
||||
<Grid HorizontalAlignment="Stretch" >
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="16"/>
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
<ColumnDefinition Width="*"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- TODO GH#6644: Add Icon to command palette entries, in column 0 -->
|
||||
|
||||
<TextBlock Grid.Column="1"
|
||||
HorizontalAlignment="Left"
|
||||
Text="{x:Bind Name, Mode=OneWay}" />
|
||||
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
BorderBrush="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Background="{ThemeResource SystemControlForegroundBaseLowBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="1"
|
||||
Padding="2,0,2,0"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Center">
|
||||
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
FontSize="12"
|
||||
Text="{x:Bind KeyChordText, Mode=OneWay}" />
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
</Grid>
|
|
@ -7,7 +7,6 @@
|
|||
#include "../../inc/DefaultSettings.h"
|
||||
#include "Utils.h"
|
||||
#include "JsonUtils.h"
|
||||
#include <sstream>
|
||||
|
||||
using namespace TerminalApp;
|
||||
using namespace winrt::Microsoft::Terminal::Settings;
|
||||
|
@ -17,7 +16,8 @@ using namespace winrt::Windows::UI::Xaml;
|
|||
using namespace ::Microsoft::Console;
|
||||
using namespace winrt::Microsoft::UI::Xaml::Controls;
|
||||
|
||||
static constexpr std::string_view KeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view LegacyKeybindingsKey{ "keybindings" };
|
||||
static constexpr std::string_view BindingsKey{ "bindings" };
|
||||
static constexpr std::string_view DefaultProfileKey{ "defaultProfile" };
|
||||
static constexpr std::string_view AlwaysShowTabsKey{ "alwaysShowTabs" };
|
||||
static constexpr std::string_view InitialRowsKey{ "initialRows" };
|
||||
|
@ -205,18 +205,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
|||
_TabWidthMode = _ParseTabWidthMode(GetWstringFromJson(tabWidthMode));
|
||||
}
|
||||
|
||||
if (auto keybindings{ json[JsonKey(KeybindingsKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(keybindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
|
||||
JsonUtils::GetBool(json, SnapToGridOnResizeKey, _SnapToGridOnResize);
|
||||
|
||||
JsonUtils::GetBool(json, ForceFullRepaintRenderingKey, _ForceFullRepaintRendering);
|
||||
|
@ -228,6 +216,31 @@ void GlobalAppSettings::LayerJson(const Json::Value& json)
|
|||
JsonUtils::GetBool(json, DebugFeaturesKey, _DebugFeaturesEnabled);
|
||||
|
||||
JsonUtils::GetBool(json, EnableStartupTaskKey, _StartOnUserLogin);
|
||||
|
||||
// This is a helper lambda to get the keybindings and commands out of both
|
||||
// and array of objects. We'll use this twice, once on the legacy
|
||||
// `keybindings` key, and again on the newer `bindings` key.
|
||||
auto parseBindings = [this, &json](auto jsonKey) {
|
||||
if (auto bindings{ json[JsonKey(jsonKey)] })
|
||||
{
|
||||
auto warnings = _keybindings->LayerJson(bindings);
|
||||
// It's possible that the user provided keybindings have some warnings
|
||||
// in them - problems that we should alert the user to, but we can
|
||||
// recover from. Most of these warnings cannot be detected later in the
|
||||
// Validate settings phase, so we'll collect them now. If there were any
|
||||
// warnings generated from parsing these keybindings, add them to our
|
||||
// list of warnings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
|
||||
// Now parse the array again, but this time as a list of commands.
|
||||
warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings);
|
||||
// It's possible that the user provided commands have some warnings
|
||||
// in them, similar to the keybindings.
|
||||
_keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end());
|
||||
}
|
||||
};
|
||||
parseBindings(LegacyKeybindingsKey);
|
||||
parseBindings(BindingsKey);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
@ -365,3 +378,8 @@ std::vector<TerminalApp::SettingsLoadWarnings> GlobalAppSettings::GetKeybindings
|
|||
{
|
||||
return _keybindingsWarnings;
|
||||
}
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GlobalAppSettings::GetCommands() const noexcept
|
||||
{
|
||||
return _commands;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ Author(s):
|
|||
#pragma once
|
||||
#include "AppKeyBindings.h"
|
||||
#include "ColorScheme.h"
|
||||
#include "Command.h"
|
||||
|
||||
// fwdecl unittest classes
|
||||
namespace TerminalAppLocalTests
|
||||
|
@ -54,6 +55,8 @@ public:
|
|||
|
||||
std::vector<TerminalApp::SettingsLoadWarnings> GetKeybindingsWarnings() const;
|
||||
|
||||
const std::unordered_map<winrt::hstring, winrt::TerminalApp::Command>& GetCommands() const noexcept;
|
||||
|
||||
// These are implemented manually to handle the string/GUID exchange
|
||||
// by higher layers in the app.
|
||||
void DefaultProfile(const GUID defaultProfile) noexcept;
|
||||
|
@ -79,7 +82,6 @@ public:
|
|||
GETSET_PROPERTY(bool, SoftwareRendering, false);
|
||||
GETSET_PROPERTY(bool, ForceVTInput, false);
|
||||
GETSET_PROPERTY(bool, DebugFeaturesEnabled); // default value set in constructor
|
||||
|
||||
GETSET_PROPERTY(bool, StartOnUserLogin, false);
|
||||
|
||||
private:
|
||||
|
@ -90,6 +92,7 @@ private:
|
|||
std::vector<::TerminalApp::SettingsLoadWarnings> _keybindingsWarnings;
|
||||
|
||||
std::unordered_map<std::wstring, ColorScheme> _colorSchemes;
|
||||
std::unordered_map<winrt::hstring, winrt::TerminalApp::Command> _commands;
|
||||
|
||||
static winrt::Windows::UI::Xaml::ElementTheme _ParseTheme(const std::wstring& themeString) noexcept;
|
||||
|
||||
|
|
|
@ -347,4 +347,154 @@
|
|||
<value>Command Prompt</value>
|
||||
<comment>This is the name of "Command Prompt", as localized in Windows. The localization here should match the one in the Windows product for "Command Prompt"</comment>
|
||||
</data>
|
||||
<data name="CommandPalette_SearchBox.PlaceholderText" xml:space="preserve">
|
||||
<value>Type a command name...</value>
|
||||
</data>
|
||||
<data name="CommandPalette_NoMatchesText.Text" xml:space="preserve">
|
||||
<value>No matching commands</value>
|
||||
</data>
|
||||
<data name="CloseWindowCommandKey" xml:space="preserve">
|
||||
<value>Close window</value>
|
||||
</data>
|
||||
<data name="ToggleFullscreenCommandKey" xml:space="preserve">
|
||||
<value>Toggle fullscreen</value>
|
||||
</data>
|
||||
<data name="OpenNewTabDropdownCommandKey" xml:space="preserve">
|
||||
<value>Open new tab dropdown</value>
|
||||
</data>
|
||||
<data name="OpenSettingsCommandKey" xml:space="preserve">
|
||||
<value>Open settings file</value>
|
||||
</data>
|
||||
<data name="OpenDefaultSettingsCommandKey" xml:space="preserve">
|
||||
<value>Open default settings file</value>
|
||||
</data>
|
||||
<data name="OpenBothSettingsFilesCommandKey" xml:space="preserve">
|
||||
<value>Open both settings and default settings files</value>
|
||||
</data>
|
||||
<data name="FindCommandKey" xml:space="preserve">
|
||||
<value>Find</value>
|
||||
</data>
|
||||
<data name="ResizePaneCommandKey" xml:space="preserve">
|
||||
<value>Resize pane</value>
|
||||
</data>
|
||||
<data name="MoveFocusCommandKey" xml:space="preserve">
|
||||
<value>Move focus</value>
|
||||
</data>
|
||||
<data name="MoveFocusWithArgCommandKey" xml:space="preserve">
|
||||
<value>Move focus {0}</value>
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="ResizePaneWithArgCommandKey" xml:space="preserve">
|
||||
<value>Resize pane {0}</value>
|
||||
<comment>{0} will be replaced with one of the four directions "DirectionLeft", "DirectionRight", "DirectionUp", or "DirectionDown"</comment>
|
||||
</data>
|
||||
<data name="DirectionLeft" xml:space="preserve">
|
||||
<value>left</value>
|
||||
</data>
|
||||
<data name="DirectionRight" xml:space="preserve">
|
||||
<value>right</value>
|
||||
</data>
|
||||
<data name="DirectionUp" xml:space="preserve">
|
||||
<value>up</value>
|
||||
</data>
|
||||
<data name="DirectionDown" xml:space="preserve">
|
||||
<value>down</value>
|
||||
</data>
|
||||
<data name="SwitchToTabCommandKey" xml:space="preserve">
|
||||
<value>Switch to tab</value>
|
||||
</data>
|
||||
<data name="NewTabCommandKey" xml:space="preserve">
|
||||
<value>New tab</value>
|
||||
</data>
|
||||
<data name="SplitPaneCommandKey" xml:space="preserve">
|
||||
<value>Split pane</value>
|
||||
</data>
|
||||
<data name="NewWindowCommandKey" xml:space="preserve">
|
||||
<value>New window</value>
|
||||
</data>
|
||||
<data name="DuplicateTabCommandKey" xml:space="preserve">
|
||||
<value>Duplicate tab</value>
|
||||
</data>
|
||||
<data name="DuplicatePaneCommandKey" xml:space="preserve">
|
||||
<value>Duplicate pane</value>
|
||||
</data>
|
||||
<data name="NextTabCommandKey" xml:space="preserve">
|
||||
<value>Next tab</value>
|
||||
</data>
|
||||
<data name="PrevTabCommandKey" xml:space="preserve">
|
||||
<value>Previous tab</value>
|
||||
</data>
|
||||
<data name="ClosePaneCommandKey" xml:space="preserve">
|
||||
<value>Close pane</value>
|
||||
</data>
|
||||
<data name="CloseTabCommandKey" xml:space="preserve">
|
||||
<value>Close tab</value>
|
||||
</data>
|
||||
<data name="SplitHorizontalCommandKey" xml:space="preserve">
|
||||
<value>Split pane horizontally</value>
|
||||
</data>
|
||||
<data name="SplitVerticalCommandKey" xml:space="preserve">
|
||||
<value>Split pane vertically</value>
|
||||
</data>
|
||||
<data name="CopyTextCommandKey" xml:space="preserve">
|
||||
<value>Copy text</value>
|
||||
</data>
|
||||
<data name="CopyTextAsSingleLineCommandKey" xml:space="preserve">
|
||||
<value>Copy text as a single line</value>
|
||||
</data>
|
||||
<data name="PasteTextCommandKey" xml:space="preserve">
|
||||
<value>Paste</value>
|
||||
</data>
|
||||
<data name="ScrollDownCommandKey" xml:space="preserve">
|
||||
<value>Scroll down one line</value>
|
||||
</data>
|
||||
<data name="ScrollDownPageCommandKey" xml:space="preserve">
|
||||
<value>Scroll down one page</value>
|
||||
</data>
|
||||
<data name="ScrollUpCommandKey" xml:space="preserve">
|
||||
<value>Scroll up one line</value>
|
||||
</data>
|
||||
<data name="ScrollUpPageCommandKey" xml:space="preserve">
|
||||
<value>Scroll up one page</value>
|
||||
</data>
|
||||
<data name="AdjustFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Adjust font size</value>
|
||||
</data>
|
||||
<data name="IncreaseFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Increase font size</value>
|
||||
</data>
|
||||
<data name="DecreaseFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Decrease font size</value>
|
||||
</data>
|
||||
<data name="IncreaseFontSizeWithAmountCommandKey" xml:space="preserve">
|
||||
<value>Increase font size, amount: {0}</value>
|
||||
<comment>{0} will be replaced with a positive number</comment>
|
||||
</data>
|
||||
<data name="DecreaseFontSizeWithAmountCommandKey" xml:space="preserve">
|
||||
<value>Decrease font size, amount: {0}</value>
|
||||
<comment>{0} will be replaced with a positive number</comment>
|
||||
</data>
|
||||
<data name="ResetFontSizeCommandKey" xml:space="preserve">
|
||||
<value>Reset font size</value>
|
||||
</data>
|
||||
<data name="ToggleCommandPaletteCommandKey" xml:space="preserve">
|
||||
<value>Toggle command palette</value>
|
||||
</data>
|
||||
<data name="SetTabColorCommandKey" xml:space="preserve">
|
||||
<value>Set tab color to {0}</value>
|
||||
<comment>{0} will be replaced with a color, displayed in hexadecimal (#RRGGBB) notation.</comment>
|
||||
</data>
|
||||
<data name="ResetTabColorCommandKey" xml:space="preserve">
|
||||
<value>Reset tab color</value>
|
||||
</data>
|
||||
<data name="OpenTabColorPickerCommandKey" xml:space="preserve">
|
||||
<value>Set the tab color...</value>
|
||||
</data>
|
||||
<data name="RenameTabCommandKey" xml:space="preserve">
|
||||
<value>Rename tab to "{0}"</value>
|
||||
<comment>{0} will be replaced with user-defined string</comment>
|
||||
</data>
|
||||
<data name="ResetTabNameCommandKey" xml:space="preserve">
|
||||
<value>Reset tab title</value>
|
||||
</data>
|
||||
</root>
|
||||
|
|
|
@ -159,6 +159,11 @@ namespace winrt::TerminalApp::implementation
|
|||
_ToggleFullscreenHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::ToggleCommandPalette:
|
||||
{
|
||||
_ToggleCommandPaletteHandlers(*this, *eventArgs);
|
||||
break;
|
||||
}
|
||||
case ShortcutAction::SetTabColor:
|
||||
{
|
||||
_SetTabColorHandlers(*this, *eventArgs);
|
||||
|
|
|
@ -23,33 +23,34 @@ namespace winrt::TerminalApp::implementation
|
|||
bool DoAction(const ActionAndArgs& actionAndArgs);
|
||||
|
||||
// clang-format off
|
||||
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenNewTabDropdown,TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenTabColorPicker,TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CopyText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PasteText, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenNewTabDropdown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(DuplicateTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NewWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseWindow, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(CloseTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ClosePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SwitchToTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(NextTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(PrevTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SplitPane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(AdjustFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResetFontSize, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUp, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDown, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollUpPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ScrollDownPage, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenSettings, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ResizePane, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(Find, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(MoveFocus, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleFullscreen, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(ToggleCommandPalette, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(OpenTabColorPicker, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs);
|
||||
// clang-format on
|
||||
|
||||
private:
|
||||
|
|
|
@ -35,7 +35,8 @@ namespace TerminalApp
|
|||
SetTabColor,
|
||||
OpenTabColorPicker,
|
||||
OpenSettings,
|
||||
RenameTab
|
||||
RenameTab,
|
||||
ToggleCommandPalette
|
||||
};
|
||||
|
||||
[default_interface] runtimeclass ActionAndArgs {
|
||||
|
@ -73,6 +74,7 @@ namespace TerminalApp
|
|||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> Find;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> MoveFocus;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleFullscreen;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> ToggleCommandPalette;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> SetTabColor;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> OpenTabColorPicker;
|
||||
event Windows.Foundation.TypedEventHandler<ShortcutActionDispatch, ActionEventArgs> RenameTab;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
#include <winrt/Windows.Storage.h>
|
||||
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
|
||||
|
||||
#include "KeyChordSerialization.h"
|
||||
#include "AzureCloudShellGenerator.h" // For AzureConnectionType
|
||||
#include "TelnetGenerator.h" // For TelnetConnectionType
|
||||
#include "TabRowControl.h"
|
||||
|
@ -49,13 +50,39 @@ namespace winrt::TerminalApp::implementation
|
|||
InitializeComponent();
|
||||
}
|
||||
|
||||
void TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI)
|
||||
winrt::fire_and_forget TerminalPage::SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings,
|
||||
bool needRefreshUI)
|
||||
{
|
||||
_settings = settings;
|
||||
if (needRefreshUI)
|
||||
{
|
||||
_RefreshUIForSettingsReload();
|
||||
}
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
co_await winrt::resume_foreground(Dispatcher());
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
// Update the command palette when settings reload
|
||||
auto commandsCollection = winrt::single_threaded_vector<winrt::TerminalApp::Command>();
|
||||
for (auto& nameAndCommand : _settings->GlobalSettings().GetCommands())
|
||||
{
|
||||
auto command = nameAndCommand.second;
|
||||
|
||||
// If there's a keybinding that's bound to exactly this command,
|
||||
// then get the string for that keychord and display it as a
|
||||
// part of the command in the UI. Each Command's KeyChordText is
|
||||
// unset by default, so we don't need to worry about clearing it
|
||||
// if there isn't a key associated with it.
|
||||
auto keyChord{ _settings->GetKeybindings().GetKeyBindingForActionWithArgs(command.Action()) };
|
||||
if (keyChord)
|
||||
{
|
||||
command.KeyChordText(KeyChordSerialization::ToString(keyChord));
|
||||
}
|
||||
commandsCollection.Append(command);
|
||||
}
|
||||
CommandPalette().SetActions(commandsCollection);
|
||||
}
|
||||
}
|
||||
|
||||
void TerminalPage::Create()
|
||||
|
@ -151,6 +178,17 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
|
||||
|
||||
CommandPalette().SetDispatch(*_actionDispatch);
|
||||
// When the visibility of the command palette changes to "collapsed",
|
||||
// the palette has been closed. Toss focus back to the currently active
|
||||
// control.
|
||||
CommandPalette().RegisterPropertyChangedCallback(UIElement::VisibilityProperty(), [this](auto&&, auto&&) {
|
||||
if (CommandPalette().Visibility() == Visibility::Collapsed)
|
||||
{
|
||||
_CommandPaletteClosed(nullptr, nullptr);
|
||||
}
|
||||
});
|
||||
|
||||
// Once the page is actually laid out on the screen, trigger all our
|
||||
// startup actions. Things like Panes need to know at least how big the
|
||||
// window will be, so they can subdivide that space.
|
||||
|
@ -765,6 +803,7 @@ namespace winrt::TerminalApp::implementation
|
|||
_actionDispatch->Find({ this, &TerminalPage::_HandleFind });
|
||||
_actionDispatch->ResetFontSize({ this, &TerminalPage::_HandleResetFontSize });
|
||||
_actionDispatch->ToggleFullscreen({ this, &TerminalPage::_HandleToggleFullscreen });
|
||||
_actionDispatch->ToggleCommandPalette({ this, &TerminalPage::_HandleToggleCommandPalette });
|
||||
_actionDispatch->SetTabColor({ this, &TerminalPage::_HandleSetTabColor });
|
||||
_actionDispatch->OpenTabColorPicker({ this, &TerminalPage::_HandleOpenTabColorPicker });
|
||||
_actionDispatch->RenameTab({ this, &TerminalPage::_HandleRenameTab });
|
||||
|
@ -2041,6 +2080,16 @@ namespace winrt::TerminalApp::implementation
|
|||
// TODO GH#3327: Look at what to do with the NC area when we have XAML theming
|
||||
}
|
||||
|
||||
void TerminalPage::_CommandPaletteClosed(const IInspectable& /*sender*/,
|
||||
const RoutedEventArgs& /*eventArgs*/)
|
||||
{
|
||||
// Return focus to the active control
|
||||
if (auto index{ _GetFocusedTabIndex() })
|
||||
{
|
||||
_GetStrongTabImpl(index.value())->SetFocused(true);
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace winrt::TerminalApp::implementation
|
|||
public:
|
||||
TerminalPage();
|
||||
|
||||
void SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
|
||||
winrt::fire_and_forget SetSettings(std::shared_ptr<::TerminalApp::CascadiaSettings> settings, bool needRefreshUI);
|
||||
|
||||
void Create();
|
||||
|
||||
|
@ -173,6 +173,8 @@ namespace winrt::TerminalApp::implementation
|
|||
|
||||
void _CompleteInitialization();
|
||||
|
||||
void _CommandPaletteClosed(const IInspectable& sender, const Windows::UI::Xaml::RoutedEventArgs& eventArgs);
|
||||
|
||||
#pragma region ActionHandlers
|
||||
// These are all defined in AppActionHandlers.cpp
|
||||
void _HandleOpenNewTabDropdown(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
|
@ -201,6 +203,8 @@ namespace winrt::TerminalApp::implementation
|
|||
void _HandleSetTabColor(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleOpenTabColorPicker(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleRenameTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
void _HandleToggleCommandPalette(const IInspectable& sender, const TerminalApp::ActionEventArgs& args);
|
||||
// Make sure to hook new actions up in _RegisterActionCallbacks!
|
||||
#pragma endregion
|
||||
|
||||
friend class TerminalAppLocalTests::TabTests;
|
||||
|
|
|
@ -53,5 +53,11 @@ the MIT License. See LICENSE in the project root for license information. -->
|
|||
DefaultButton="Primary"
|
||||
PrimaryButtonClick="_CloseWarningPrimaryButtonOnClick">
|
||||
</ContentDialog>
|
||||
|
||||
<local:CommandPalette
|
||||
x:Name="CommandPalette"
|
||||
Grid.Row="1"
|
||||
Visibility="Collapsed"
|
||||
VerticalAlignment="Stretch" />
|
||||
</Grid>
|
||||
</Page>
|
||||
|
|
|
@ -63,6 +63,9 @@
|
|||
<Page Include="../ColorPickupFlyout.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
<Page Include="../CommandPalette.xaml">
|
||||
<SubType>Designer</SubType>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- ========================= Headers ======================== -->
|
||||
<ItemGroup>
|
||||
|
@ -85,6 +88,12 @@
|
|||
<ClInclude Include="../ColorPickupFlyout.h">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandPalette.h">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Command.h">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../Tab.h">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
</ClInclude>
|
||||
|
@ -103,7 +112,7 @@
|
|||
<ClInclude Include="../WslDistroGenerator.h" />
|
||||
<ClInclude Include="../AzureCloudShellGenerator.h" />
|
||||
<ClInclude Include="../TelnetGenerator.h" />
|
||||
<ClInclude Include="..\ColorHelper.h" />
|
||||
<ClInclude Include="../ColorHelper.h" />
|
||||
<ClInclude Include="pch.h" />
|
||||
<ClInclude Include="../ShortcutActionDispatch.h">
|
||||
<DependentUpon>../ShortcutActionDispatch.idl</DependentUpon>
|
||||
|
@ -146,6 +155,12 @@
|
|||
<ClCompile Include="../ColorPickupFlyout.cpp">
|
||||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../CommandPalette.cpp">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Command.cpp">
|
||||
<DependentUpon>../Command.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
<ClCompile Include="../Tab.cpp">
|
||||
<DependentUpon>../Tab.idl</DependentUpon>
|
||||
</ClCompile>
|
||||
|
@ -229,7 +244,12 @@
|
|||
<DependentUpon>../ColorPickupFlyout.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../Tab.idl"/>
|
||||
<Midl Include="../CommandPalette.idl">
|
||||
<DependentUpon>../CommandPalette.xaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Midl>
|
||||
<Midl Include="../Command.idl" />
|
||||
<Midl Include="../Tab.idl" />
|
||||
</ItemGroup>
|
||||
<!-- ========================= Misc Files ======================== -->
|
||||
<ItemGroup>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<Natvis Include="$(SolutionDir)tools\ConsoleTypes.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PRIResource Include="../Resources/en-US/Resources.resw" />
|
||||
<PRIResource Include="..\Resources\en-US\Resources.resw" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="../init.cpp" />
|
||||
|
@ -59,7 +59,13 @@
|
|||
<ClCompile Include="../Pane.LayoutSizeNode.cpp">
|
||||
<Filter>pane</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\ColorHelper.cpp" />
|
||||
<ClCompile Include="../AppCommandlineArgs.cpp" />
|
||||
<ClCompile Include="../Commandline.cpp" />
|
||||
<ClCompile Include="../ColorHelper.cpp" />
|
||||
<ClCompile Include="../DebugTapConnection.cpp" />
|
||||
<ClCompile Include="../CommandSerialization.cpp">
|
||||
<Filter>settings</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="../Utils.h" />
|
||||
|
@ -107,8 +113,16 @@
|
|||
<ClInclude Include="../Tab.h">
|
||||
<Filter>tab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../TelnetGenerator.h" />
|
||||
<ClInclude Include="..\ColorHelper.h" />
|
||||
<ClInclude Include="../AppCommandlineArgs.h" />
|
||||
<ClInclude Include="../Commandline.h" />
|
||||
<ClInclude Include="../DebugTapConnection.h" />
|
||||
<ClInclude Include="../ColorHelper.h" />
|
||||
<ClInclude Include="../TelnetGenerator.h">
|
||||
<Filter>profileGeneration</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="../CommandSerialization.h">
|
||||
<Filter>settings</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Midl Include="../AppLogic.idl">
|
||||
|
@ -126,6 +140,10 @@
|
|||
<Midl Include="../Tab.idl">
|
||||
<Filter>tab</Filter>
|
||||
</Midl>
|
||||
<Midl Include="../IF7Listener.idl" />
|
||||
<Midl Include="../Command.idl">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Midl>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="../packages.config" />
|
||||
|
@ -146,6 +164,9 @@
|
|||
<Page Include="../ColorPickupFlyout.xaml">
|
||||
<Filter>controls</Filter>
|
||||
</Page>
|
||||
<Page Include="../CommandPalette.xaml">
|
||||
<Filter>commandPalette</Filter>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Filter Include="app">
|
||||
|
@ -169,6 +190,9 @@
|
|||
<Filter Include="tab">
|
||||
<UniqueIdentifier>{6d40e12f-b83f-462e-8f93-fa421f87b27e}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="commandPalette">
|
||||
<UniqueIdentifier>{2ad498e1-d8ea-4381-9464-a74c141bd7dd}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ApplicationDefinition Include="..\App.xaml">
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
|
||||
#include <winrt/Windows.Foundation.h>
|
||||
#include <winrt/Windows.Foundation.Collections.h>
|
||||
#include <winrt/Windows.Foundation.Metadata.h>
|
||||
#include <winrt/windows.ui.core.h>
|
||||
#include <winrt/Windows.ui.input.h>
|
||||
#include <winrt/Windows.UI.Text.h>
|
||||
|
|
|
@ -94,3 +94,11 @@ try
|
|||
return loader.GetLocalizedString(key);
|
||||
}
|
||||
CATCH_FAIL_FAST()
|
||||
|
||||
bool HasLibraryResourceWithName(const std::wstring_view key)
|
||||
try
|
||||
{
|
||||
static auto loader{ GetLibraryResourceLoader() };
|
||||
return loader.HasResourceWithName(key);
|
||||
}
|
||||
CATCH_FAIL_FAST()
|
||||
|
|
|
@ -68,3 +68,4 @@ namespace Microsoft::Console::Utils
|
|||
__declspec(selectany) extern const wchar_t* g_WinRTUtilsLibraryResourceScope{ (x) };
|
||||
|
||||
winrt::hstring GetLibraryResourceString(const std::wstring_view key);
|
||||
bool HasLibraryResourceWithName(const std::wstring_view key);
|
||||
|
|
|
@ -107,15 +107,16 @@ public: \
|
|||
private: \
|
||||
type _##name{ __VA_ARGS__ };
|
||||
|
||||
// Use this macro to quickly implement both the getter and setter for an observable property.
|
||||
// This is similar to the GETSET_PROPERTY macro above, except this will also raise a
|
||||
// PropertyChanged event with the name of the property that has changed inside of the setter.
|
||||
// Use this macro to quickly implement both the getter and setter for an
|
||||
// observable property. This is similar to the GETSET_PROPERTY macro above,
|
||||
// except this will also raise a PropertyChanged event with the name of the
|
||||
// property that has changed inside of the setter. This also implements a
|
||||
// private _setName() method, that the class can internally use to change the
|
||||
// value when it _knows_ it doesn't need to raise the PropertyChanged event
|
||||
// (like when the class is being initialized).
|
||||
#define OBSERVABLE_GETSET_PROPERTY(type, name, event) \
|
||||
public: \
|
||||
type name() { return _##name; }; \
|
||||
\
|
||||
private: \
|
||||
const type _##name; \
|
||||
void name(const type& value) \
|
||||
{ \
|
||||
if (_##name != value) \
|
||||
|
@ -123,6 +124,13 @@ private:
|
|||
const_cast<type&>(_##name) = value; \
|
||||
event(*this, Windows::UI::Xaml::Data::PropertyChangedEventArgs{ L#name }); \
|
||||
} \
|
||||
}; \
|
||||
\
|
||||
private: \
|
||||
const type _##name; \
|
||||
void _set##name(const type& value) \
|
||||
{ \
|
||||
const_cast<type&>(_##name) = value; \
|
||||
};
|
||||
|
||||
// Use this macro for quickly defining the factory_implementation part of a
|
||||
|
|
|
@ -148,10 +148,19 @@ namespace til // Terminal Implementation Library. Also: "Today I Learned"
|
|||
std::wstring to_string() const
|
||||
{
|
||||
std::wstringstream wss;
|
||||
wss << L"Color #" << std::uppercase << std::setfill(L'0') << std::hex;
|
||||
wss << L"Color " << ToHexString(false);
|
||||
return wss.str();
|
||||
}
|
||||
std::wstring ToHexString(const bool omitAlpha = false) const
|
||||
{
|
||||
std::wstringstream wss;
|
||||
wss << L"#" << std::uppercase << std::setfill(L'0') << std::hex;
|
||||
// Force the compiler to promote from byte to int. Without it, the
|
||||
// stringstream will try to write the components as chars
|
||||
wss << std::setw(2) << static_cast<int>(a);
|
||||
if (!omitAlpha)
|
||||
{
|
||||
wss << std::setw(2) << static_cast<int>(a);
|
||||
}
|
||||
wss << std::setw(2) << static_cast<int>(r);
|
||||
wss << std::setw(2) << static_cast<int>(g);
|
||||
wss << std::setw(2) << static_cast<int>(b);
|
||||
|
|
Loading…
Reference in a new issue