Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preliminary work to add Swap Panes functionality (GH Issues 1000, 4922) #10638

Merged
11 commits merged into from
Jul 22, 2021
Merged
19 changes: 19 additions & 0 deletions doc/cascadia/profiles.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@
"identifyWindow",
"identifyWindows",
"moveFocus",
"movePane",
"moveTab",
"newTab",
"newWindow",
Expand Down Expand Up @@ -514,6 +515,23 @@
],
"required": [ "direction" ]
},
"MovePaneAction": {
"description": "Arguments corresponding to a Move Pane Action",
"allOf": [
{ "$ref": "#/definitions/ShortcutAction" },
{
"properties": {
"action": { "type": "string", "pattern": "movePane" },
"direction": {
"$ref": "#/definitions/FocusDirection",
"default": "left",
"description": "The direction to move the focus pane in, swapping panes. Direction can be 'previous' to swap with the most recently used pane."
}
}
}
],
"required": [ "direction" ]
},
"ResizePaneAction": {
"description": "Arguments corresponding to a Resize Pane Action",
"allOf": [
Expand Down Expand Up @@ -952,6 +970,7 @@
{ "$ref": "#/definitions/NewTabAction" },
{ "$ref": "#/definitions/SwitchToTabAction" },
{ "$ref": "#/definitions/MoveFocusAction" },
{ "$ref": "#/definitions/MovePaneAction" },
{ "$ref": "#/definitions/ResizePaneAction" },
{ "$ref": "#/definitions/SendInputAction" },
{ "$ref": "#/definitions/SplitPaneAction" },
Expand Down
119 changes: 119 additions & 0 deletions src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ namespace TerminalAppLocalTests
TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseMoveFocusArgs);
TEST_METHOD(ParseMovePaneArgs);
TEST_METHOD(ParseArgumentsWithParsingTerminators);
TEST_METHOD(ParseFocusPaneArgs);

Expand Down Expand Up @@ -1207,6 +1208,124 @@ namespace TerminalAppLocalTests
}
}

void CommandlineTest::ParseMovePaneArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:useShortForm", L"{false, true}")
END_TEST_METHOD_PROPERTIES()

INIT_TEST_PROPERTY(bool, useShortForm, L"If true, use `mp` instead of `move-pane`");
const wchar_t* subcommand = useShortForm ? L"mp" : L"move-pane";

{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand };
Log::Comment(NoThrowString().Format(
L"Just the subcommand, without a direction, should fail."));

_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);

VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());

// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());

auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);

VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());

// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());

auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"up" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);

VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());

// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());

auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Up, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"down" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);

VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());

// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());

auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Down, myArgs.Direction());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"badDirection" };
Log::Comment(NoThrowString().Format(
L"move-pane with an invalid direction should fail."));
_buildCommandlinesExpectFailureHelper(appArgs, 1u, rawCommands);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", subcommand, L"left", L";", subcommand, L"right" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);

VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());

// The first action is going to always be a new-tab action
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());

auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Left, myArgs.Direction());

actionAndArgs = appArgs._startupActions.at(2);
VERIFY_ARE_EQUAL(ShortcutAction::MovePane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<MovePaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(FocusDirection::Right, myArgs.Direction());
}
}

void CommandlineTest::ParseFocusPaneArgs()
{
BEGIN_TEST_METHOD_PROPERTIES()
Expand Down
208 changes: 208 additions & 0 deletions src/cascadia/LocalTests_TerminalApp/TabTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ namespace TerminalAppLocalTests
TEST_METHOD(MoveFocusFromZoomedPane);
TEST_METHOD(CloseZoomedPane);

TEST_METHOD(MovePanes);

TEST_METHOD(NextMRUTab);
TEST_METHOD(VerifyCommandPaletteTabSwitcherOrder);

Expand Down Expand Up @@ -817,6 +819,212 @@ namespace TerminalAppLocalTests
VERIFY_SUCCEEDED(result);
}

void TabTests::MovePanes()
{
auto page = _commonSetup();

Log::Comment(L"Setup 4 panes.");
// Create the following layout
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
uint32_t firstId = 0, secondId = 0, thirdId = 0, fourthId = 0;
TestOnUIThread([&]() {
VERIFY_ARE_EQUAL(1u, page->_tabs.Size());
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
firstId = tab->_activePane->Id().value();
// We start with 1 tab, split vertically to get
// -------------------
// | 1 | 2 |
// | | |
// -------------------
page->_SplitPane(SplitState::Vertical, SplitType::Duplicate, 0.5f, nullptr);
secondId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `2` pane is focused, go back to `1` being focused
page->_MoveFocus(FocusDirection::Left);
});
Sleep(250);
TestOnUIThread([&]() {
// Split again to make the 3rd tab
// -------------------
// | 1 | |
// | | |
// ---------| 2 |
// | 3 | |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
// Split again to make the 3rd tab
thirdId = tab->_activePane->Id().value();
});
Sleep(250);
TestOnUIThread([&]() {
// After this the `3` pane is focused, go back to `2` being focused
page->_MoveFocus(FocusDirection::Right);
});
Sleep(250);
TestOnUIThread([&]() {
// Split to create the final pane
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 3 | 4 |
// | | |
// -------------------
page->_SplitPane(SplitState::Horizontal, SplitType::Duplicate, 0.5f, nullptr);
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
fourthId = tab->_activePane->Id().value();
});

Sleep(250);
TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// just to be complete, make sure we actually have 4 different ids
VERIFY_ARE_NOT_EQUAL(firstId, fourthId);
VERIFY_ARE_NOT_EQUAL(secondId, fourthId);
VERIFY_ARE_NOT_EQUAL(thirdId, fourthId);
VERIFY_ARE_NOT_EQUAL(firstId, thirdId);
VERIFY_ARE_NOT_EQUAL(secondId, thirdId);
VERIFY_ARE_NOT_EQUAL(firstId, secondId);
});

// Gratuitous use of sleep to make sure that the UI has updated properly
// after each operation.
Sleep(250);
// Now try to move the pane through the tree
Log::Comment(L"Move pane to the left. This should swap panes 3 and 4");
// -------------------
// | 1 | 2 |
// | | |
// -------------------
// | 4 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Left };
ActionEventArgs eventArgs{ args };

page->_HandleMovePane(nullptr, eventArgs);
});

Sleep(250);

TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());

// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_secondChild->Id().value());
});

Sleep(250);

Log::Comment(L"Move pane to up. This should swap panes 1 and 4");
// -------------------
// | 4 | 2 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Up };
ActionEventArgs eventArgs{ args };

page->_HandleMovePane(nullptr, eventArgs);
});

Sleep(250);

TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());

// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_firstChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(firstId, tab->_rootPane->_firstChild->_secondChild->Id().value());
});

Sleep(250);

Log::Comment(L"Move pane to the right. This should swap panes 2 and 4");
// -------------------
// | 2 | 4 |
// | | |
// -------------------
// | 1 | 3 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Right };
ActionEventArgs eventArgs{ args };

page->_HandleMovePane(nullptr, eventArgs);
});

Sleep(250);

TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());

// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_firstChild->Id().value());
VERIFY_ARE_EQUAL(secondId, tab->_rootPane->_firstChild->_firstChild->Id().value());
});

Sleep(250);

Log::Comment(L"Move pane down. This should swap panes 3 and 4");
// -------------------
// | 2 | 3 |
// | | |
// -------------------
// | 1 | 4 |
// | | |
// -------------------
TestOnUIThread([&]() {
// Set up action
MovePaneArgs args{ FocusDirection::Down };
ActionEventArgs eventArgs{ args };

page->_HandleMovePane(nullptr, eventArgs);
});

Sleep(250);

TestOnUIThread([&]() {
auto tab = page->_GetTerminalTabImpl(page->_tabs.GetAt(0));
VERIFY_ARE_EQUAL(4, tab->GetLeafPaneCount());
// Our currently focused pane should be `4`
VERIFY_ARE_EQUAL(fourthId, tab->_activePane->Id().value());

// Inspect the tree to make sure we swapped
VERIFY_ARE_EQUAL(fourthId, tab->_rootPane->_secondChild->_secondChild->Id().value());
VERIFY_ARE_EQUAL(thirdId, tab->_rootPane->_secondChild->_firstChild->Id().value());
});
}

void TabTests::NextMRUTab()
{
// This is a test for GH#8025 - we want to make sure that we can do both
Expand Down
Loading