Merge remote-tracking branch 'origin/master' into dev/migrie/b/3490-resizing-but-no-invalidateall-ing

This commit is contained in:
Mike Griese
2020-01-27 09:57:15 -06:00
52 changed files with 10330 additions and 70 deletions

7821
dep/CLI11/CLI11.hpp Normal file

File diff suppressed because it is too large Load Diff

5
dep/CLI11/README.md Normal file
View File

@@ -0,0 +1,5 @@
# CLI11
Taken from [release v1.8.0](https://github.com/CLIUtils/CLI11/releases/tag/v1.8.0), source commit
[13becad](https://github.com/CLIUtils/CLI11/commit/13becaddb657eacd090537719a669d66d393b8b2)

View File

@@ -291,7 +291,7 @@
"description": "The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or 'system'.",
"maximum": 999,
"minimum": 0,
"type": "integer"
"type": ["integer", "string"]
},
"keybindings": {
"description": "Properties are specific to each custom key binding.",

View File

@@ -0,0 +1,156 @@
---
author: Mike Griese @zadjii-msft
created on: 2020-01-16
last updated: 2020-01-17
---
# Using the `wt.exe` Commandline
As of [#4023], the Windows Terminal now supports accepting arguments on the
commandline, to enable launching the Terminal in a non-default configuration.
This document serves as a reference for all the parameters you can currently
pass, and gives some examples of how to use the `wt` commandline.
> NOTE: If you're running the Terminal built straight from the repo, you'll need
> to use `wtd.exe` and `wtd` instead of `wt.exe` and `wt`.
1. [Commandline Reference](#Reference)
1. [Commandline Examples](#Examples)
## Reference
### Options
#### `--help,-h,-?,/?,`
Display the help message.
## Subcommands
#### `new-tab`
`new-tab [terminal_parameters]`
Opens a new tab with the given customizations. On its _first_ invocation, also
opens a new window. Subsequent `new-tab` commands will all open new tabs in the
same window.
**Parameters**:
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `split-pane`
`split-pane [--target,-t target-pane] [-H]|[-V] [terminal_parameters]`
Creates a new pane in the currently focused tab by splitting the given pane
vertically or horizontally.
**Parameters**:
* `--target,-t target-pane`: Creates a new split in the given `target-pane`.
Each pane has a unique index (per-tab) which can be used to identify them.
These indicies are assigned in the order the panes were created. If omitted,
defaults to the index of the currently focused pane.
* `-H`, `-V`: Used to indicate which direction to split the pane. `-V` is
"vertically" (think `[|]`), and `-H` is "horizontally" (think `[-]`). If
omitted, defaults to "auto", which splits the current pane in whatever the
larger dimension is. If both `-H` and `-V` are provided, defaults to vertical.
* `[terminal_parameters]`: See [[terminal_parameters]](#terminal_parameters).
#### `focus-tab`
`focus-tab [--target,-t tab-index]|[--next,-n]|[--previous,-p]`
Moves focus to a given tab.
**Parameters**:
* `--target,-t tab-index`: moves focus to the tab at index `tab-index`. If
omitted, defaults to `0` (the first tab). Will display an error if combined
with either of `--next` or `--previous`.
* `-n,--next`: Move focus to the next tab. Will display an error if combined
with either of `--previous` or `--target`.
* `-p,--previous`: Move focus to the previous tab. Will display an error if
combined with either of `--next` or `--target`.
#### `[terminal_parameters]`
Some of the preceding commands are used to create a new terminal instance.
These commands are listed above as accepting `[terminal_parameters]` as a
parameter. For these commands, `[terminal_parameters]` can be any of the
following:
`[--profile,-p profile-name] [--startingDirectory,-d starting-directory] [commandline]`
* `--profile,-p profile-name`: Use the given profile to open the new tab/pane,
where `profile-name` is the `name` or `guid` of a profile. If `profile-name`
does not match _any_ profiles, uses the default.
* `--startingDirectory,-d starting-directory`: Overrides the value of
`startingDirectory` of the specified profile, to start in `starting-directory`
instead.
* `commandline`: A commandline to replace the default commandline of the
selected profile. If the user wants to use a `;` in this commandline, it
should be escaped as `\;`.
## Examples
### Open Windows Terminal in the current directory
```
wt -d .
```
This will launch a new Windows Terminal window in the current working directory.
It will use your default profile, but instead of using the `startingDirectory`
property from that it will use the current path. This is especially useful for
launching the Windows Terminal in a directory you currently have open in an
`explorer.exe` window.
### Opening with multiple panes
If you want to open with multiple panes in the same tab all at once, you can use
the `split-pane` command to create new panes.
Consider the following commandline:
```
wt ; split-pane -p "Windows PowerShell" ; split-pane -H wsl.exe
```
This creates a new Windows Terminal window with one tab, and 3 panes:
* `wt`: Creates the new tab with the default profile
* `split-pane -p "Windows PowerShell"`: This will create a new pane, split from
the parent with the default profile. This pane will open with the "Windows
PowerShell" profile
* `split-pane -H wsl.exe`: This will create a third pane, slit _horizontally_
from the "Windows PowerShell" pane. It will be running the default profile,
and will use `wsl.exe` as the commandline (instead of the default profile's
`commandline`).
### Running a specific linux distro in a specific distro
Say you wanted to open a "Debian" WSL instance in `C:\Users`. According to the
above reference, you might try:
```
wt -d c:\Users wsl -d Debian
```
Unfortunately, this _won't_ work, and will give you an error about "expected
exactly 1 argument to `--startingDirectory`, got 2". This is because we'll
erroneously try to parse the `-d Debian` parameter as a second
`--startingDirectory` value. This is unexpected, and is a bug currently tracked
in [#4277].
As a workaround, you can try the following commandline:
```
wt -d c:\Users -- wsl -d Debian
```
In this commandline, the `--` will indicate to the Terminal that it should treat
everything after that like a commandline.
[#4023]: https://github.com/microsoft/terminal/pull/4023
[#4277]: https://github.com/microsoft/terminal/issues/4277

View File

@@ -0,0 +1,991 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include <WexTestClass.h>
#include "../TerminalApp/AppCommandlineArgs.h"
using namespace WEX::Logging;
using namespace WEX::Common;
using namespace WEX::TestExecution;
using namespace winrt::TerminalApp;
using namespace ::TerminalApp;
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 CommandlineTest
{
// 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(CommandlineTest)
TEST_CLASS_PROPERTY(L"RunAs", L"UAP")
TEST_CLASS_PROPERTY(L"UAP:AppXManifest", L"TestHostAppXManifest.xml")
END_TEST_CLASS()
TEST_METHOD(ParseSimpleCommandline);
TEST_METHOD(ParseTrickyCommandlines);
TEST_METHOD(TestEscapeDelimiters);
TEST_METHOD(ParseSimpleHelp);
TEST_METHOD(ParseBadOptions);
TEST_METHOD(ParseSubcommandHelp);
TEST_METHOD(ParseBasicCommandlineIntoArgs);
TEST_METHOD(ParseNewTabCommand);
TEST_METHOD(ParseSplitPaneIntoArgs);
TEST_METHOD(ParseComboCommandlineIntoArgs);
TEST_METHOD(ParseFocusTabArgs);
TEST_METHOD(ParseNoCommandIsNewTab);
TEST_METHOD(ValidateFirstCommandIsNewTab);
TEST_METHOD(CheckTypos);
private:
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
const size_t expectedSubcommands,
std::vector<const wchar_t*>& rawCommands)
{
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(expectedSubcommands, commandlines.size());
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
VERIFY_ARE_EQUAL(0, result);
}
appArgs.ValidateStartupCommands();
}
void _logCommandline(std::vector<const wchar_t*>& rawCommands)
{
std::wstring buffer;
for (const auto s : rawCommands)
{
buffer += s;
buffer += L" ";
}
Log::Comment(NoThrowString().Format(L"%s", buffer.c_str()));
}
};
void CommandlineTest::ParseSimpleCommandline()
{
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"an arg with spaces" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--parameter", L"an arg with spaces" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(3u, commandlines.at(0).Argc());
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L";" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(2u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL("new-tab", commandlines.at(0).Args().at(1));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L";" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(2u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L";", L";" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(3u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(2).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(2).Args().at(0));
}
}
void CommandlineTest::ParseSimpleHelp()
{
static std::vector<std::vector<const wchar_t*>> commandsToTest{
{ L"wt.exe", L"/?" },
{ L"wt.exe", L"-?" },
{ L"wt.exe", L"-h" },
{ L"wt.exe", L"--help" }
};
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:testPass", L"{0, 1, 2, 3}")
END_TEST_METHOD_PROPERTIES();
int testPass;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"testPass", testPass), L"Get a commandline to test");
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*>& rawCommands{ commandsToTest.at(testPass) };
_logCommandline(rawCommands);
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
VERIFY_ARE_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
}
}
void CommandlineTest::ParseBadOptions()
{
static std::vector<std::vector<const wchar_t*>> commandsToTest{
{ L"wt.exe", L"/Z" },
{ L"wt.exe", L"-q" },
{ L"wt.exe", L"--bar" }
};
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:testPass", L"{0, 1, 2}")
END_TEST_METHOD_PROPERTIES();
int testPass;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"testPass", testPass), L"Get a commandline to test");
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*>& rawCommands{ commandsToTest.at(testPass) };
_logCommandline(rawCommands);
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
VERIFY_ARE_NOT_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
}
}
void CommandlineTest::ParseSubcommandHelp()
{
static std::vector<std::vector<const wchar_t*>> commandsToTest{
{ L"wt.exe", L"new-tab", L"-h" },
{ L"wt.exe", L"new-tab", L"--help" },
{ L"wt.exe", L"split-pane", L"-h" },
{ L"wt.exe", L"split-pane", L"--help" }
};
BEGIN_TEST_METHOD_PROPERTIES()
TEST_METHOD_PROPERTY(L"Data:testPass", L"{0, 1, 2, 3}")
END_TEST_METHOD_PROPERTIES();
int testPass;
VERIFY_SUCCEEDED(TestData::TryGetValue(L"testPass", testPass), L"Get a commandline to test");
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*>& rawCommands{ commandsToTest.at(testPass) };
_logCommandline(rawCommands);
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(3u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
VERIFY_ARE_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
}
}
void CommandlineTest::ParseTrickyCommandlines()
{
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab;" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(2u, commandlines.size());
VERIFY_ARE_EQUAL(2u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL("new-tab", commandlines.at(0).Args().at(1));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L";new-tab;" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(3u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(2u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
VERIFY_ARE_EQUAL("new-tab", commandlines.at(1).Args().at(1));
VERIFY_ARE_EQUAL(1u, commandlines.at(2).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(2).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe;" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(2u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe;;" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(3u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
VERIFY_ARE_EQUAL(1u, commandlines.at(2).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(2).Args().at(0));
}
{
std::vector<const wchar_t*> rawCommands{ L"wt.exe;foo;bar;baz" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(4u, commandlines.size());
VERIFY_ARE_EQUAL(1u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
VERIFY_ARE_EQUAL(2u, commandlines.at(1).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(1).Args().at(0));
VERIFY_ARE_EQUAL("foo", commandlines.at(1).Args().at(1));
VERIFY_ARE_EQUAL(2u, commandlines.at(2).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(2).Args().at(0));
VERIFY_ARE_EQUAL("bar", commandlines.at(2).Args().at(1));
VERIFY_ARE_EQUAL(2u, commandlines.at(3).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(3).Args().at(0));
VERIFY_ARE_EQUAL("baz", commandlines.at(3).Args().at(1));
}
}
void CommandlineTest::TestEscapeDelimiters()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg ; with spaces" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
{
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
auto myCommand = myArgs.TerminalArgs().Commandline();
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg \"", myCommand);
}
{
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
auto myCommand = myArgs.TerminalArgs().Commandline();
VERIFY_ARE_EQUAL(L"\" with spaces\"", myCommand);
}
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg \\; with spaces" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
{
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
auto myCommand = myArgs.TerminalArgs().Commandline();
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg ; with spaces\"", myCommand);
}
}
}
void CommandlineTest::ParseBasicCommandlineIntoArgs()
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
}
void CommandlineTest::ParseNewTabCommand()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"--profile", L"cmd" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"--startingDirectory", L"c:\\Foo" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_FALSE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\Foo", myArgs.TerminalArgs().StartingDirectory());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg with spaces" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
auto myCommand = myArgs.TerminalArgs().Commandline();
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", myCommand);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"powershell.exe", L"This is an arg with spaces", L"another-arg", L"more spaces in this one" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
auto myCommand = myArgs.TerminalArgs().Commandline();
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\" another-arg \"more spaces in this one\"", myCommand);
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"-p", L"Windows PowerShell" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"Windows PowerShell", myArgs.TerminalArgs().Profile());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"wsl", L"-d", L"Alpine" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L"-p", L"1", L"wsl", L"-d", L"Alpine" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
}
}
void CommandlineTest::ParseSplitPaneIntoArgs()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane" };
_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());
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-H" };
_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());
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Horizontal, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-V" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
_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());
// The one we actually want to test here is the SplitPane action we created
auto actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"wsl", L"-d", L"Alpine" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
_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::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"-H", L"wsl", L"-d", L"Alpine" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
_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::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Horizontal, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"wsl -d Alpine", myArgs.TerminalArgs().Commandline());
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L"-p", L"1", L"wsl", L"-d", L"Alpine", L"-H" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
_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::SplitPane, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SplitPaneArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(SplitState::Vertical, myArgs.SplitStyle());
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"wsl -d Alpine -H", myArgs.TerminalArgs().Commandline());
VERIFY_ARE_EQUAL(L"1", myArgs.TerminalArgs().Profile());
}
}
void CommandlineTest::ParseComboCommandlineIntoArgs()
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L";", L"split-pane" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, appArgs._startupActions.at(1).Action());
}
void CommandlineTest::ParseNoCommandIsNewTab()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--profile", L"cmd" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"--startingDirectory", L"c:\\Foo" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_FALSE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"c:\\Foo", myArgs.TerminalArgs().StartingDirectory());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"powershell.exe" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"powershell.exe", L"This is an arg with spaces" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", myArgs.TerminalArgs().Commandline());
}
}
void CommandlineTest::ParseFocusTabArgs()
{
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab" };
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
VERIFY_ARE_EQUAL(1u, 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());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-n" };
_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::NextTab, actionAndArgs.Action());
VERIFY_IS_NULL(actionAndArgs.Args());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-p" };
_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::PrevTab, actionAndArgs.Action());
VERIFY_IS_NULL(actionAndArgs.Args());
}
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-t", L"2" };
_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::SwitchToTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<SwitchToTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_ARE_EQUAL(2, myArgs.TabIndex());
}
{
Log::Comment(NoThrowString().Format(
L"Attempt an invalid combination of flags"));
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"focus-tab", L"-p", L"-n" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(4u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
VERIFY_ARE_NOT_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
}
}
}
void CommandlineTest::ValidateFirstCommandIsNewTab()
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"split-pane", L";", L"split-pane" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(3u, appArgs._startupActions.size());
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, appArgs._startupActions.at(0).Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, appArgs._startupActions.at(1).Action());
VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, appArgs._startupActions.at(2).Action());
}
void CommandlineTest::CheckTypos()
{
Log::Comment(NoThrowString().Format(
L"Check what happens when the user typos a subcommand. It should "
L"be treated as a commandline, unless other args are present that "
L"the new-tab subcommand doesn't understand"));
{
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"new-tab", L";", L"slpit-pane" };
_buildCommandlinesHelper(appArgs, 2u, rawCommands);
VERIFY_ARE_EQUAL(2u, appArgs._startupActions.size());
auto actionAndArgs = appArgs._startupActions.at(0);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
actionAndArgs = appArgs._startupActions.at(1);
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
VERIFY_IS_NOT_NULL(myArgs);
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
VERIFY_ARE_EQUAL(L"slpit-pane", myArgs.TerminalArgs().Commandline());
}
{
Log::Comment(NoThrowString().Format(
L"Pass a flag that would be accepted by split-pane, but isn't accepted by new-tab"));
AppCommandlineArgs appArgs{};
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"slpit-pane", L"-H" };
auto commandlines = AppCommandlineArgs::BuildCommands(rawCommands);
VERIFY_ARE_EQUAL(1u, commandlines.size());
VERIFY_ARE_EQUAL(3u, commandlines.at(0).Argc());
VERIFY_ARE_EQUAL("wt.exe", commandlines.at(0).Args().at(0));
for (auto& cmdBlob : commandlines)
{
const auto result = appArgs.ParseCommand(cmdBlob);
Log::Comment(NoThrowString().Format(
L"Exit Message:\n%hs",
appArgs._exitMessage.c_str()));
VERIFY_ARE_NOT_EQUAL(0, result);
VERIFY_ARE_NOT_EQUAL("", appArgs._exitMessage);
}
}
}
}

View File

@@ -38,6 +38,14 @@
<Import Project="$(SolutionDir)\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)\src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="pch.h" />
@@ -47,6 +55,7 @@
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="CommandlineTest.cpp" />
<ClCompile Include="SettingsTests.cpp" />
<ClCompile Include="ProfileTests.cpp" />
<ClCompile Include="ColorSchemeTests.cpp" />
@@ -80,7 +89,7 @@
<!-- ====================== Compiler & Linker Flags ===================== -->
<ItemDefinitionGroup>
<ClCompile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;$(OpenConsoleDir)src\inc;$(OpenConsoleDir)src\inc\test;$(WinRT_IncludePath)\..\cppwinrt\winrt;"$(OpenConsoleDir)\src\cascadia\TerminalApp\lib\Generated Files";%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->

View File

@@ -58,3 +58,6 @@ Author(s):
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <regex>
#include <CLI11/CLI11.hpp>

View File

@@ -0,0 +1,609 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "AppCommandlineArgs.h"
#include "ActionArgs.h"
using namespace winrt::TerminalApp;
using namespace TerminalApp;
// Either a ; at the start of a line, or a ; preceeded by any non-\ char.
const std::wregex AppCommandlineArgs::_commandDelimiterRegex{ LR"(^;|[^\\];)" };
AppCommandlineArgs::AppCommandlineArgs()
{
_buildParser();
_resetStateToDefault();
}
// Method Description:
// - Attempt to parse a given command as a single commandline. If the command
// doesn't have a subcommand, we'll try parsing the commandline again, as a
// new-tab command.
// - Actions generated by this command are added to our _startupActions list.
// Arguments:
// - command: The individual commandline to parse as a command.
// Return Value:
// - 0 if the commandline was successfully parsed
// - nonzero return values are defined in CLI::ExitCodes
int AppCommandlineArgs::ParseCommand(const Commandline& command)
{
const int argc = static_cast<int>(command.Argc());
// Stash a pointer to the current Commandline instance we're parsing.
// When we're trying to parse the commandline for a new-tab/split-pane
// subcommand, we'll need to inspect the original Args from this
// Commandline to find the entirety of the commandline args for the new
// terminal instance. Discard the pointer when we leave this method. The
// pointer will be safe for usage, since the parse callback will be
// executed on the same thread, higher on the stack.
_currentCommandline = &command;
auto clearPointer = wil::scope_exit([this]() { _currentCommandline = nullptr; });
try
{
// CLI11 needs a mutable vector<string>, so copy out the args here.
// * When we're using the vector<string> parse(), it also expects that
// there isn't a leading executable name in the args, so slice that
// out.
// - In AppCommandlineArgs::BuildCommands, we'll make sure each
// subsequent command in a single commandline starts with a wt.exe.
// Our very first argument might not be "wt.exe", it could be `wt`,
// or `wtd.exe`, etc. Regardless, we want to ignore the first arg of
// every Commandline
// * Not only that, but this particular overload of parse() wants the
// args _reversed_ here.
std::vector<std::string> args{ command.Args().begin() + 1, command.Args().end() };
std::reverse(args.begin(), args.end());
// Revert our state to the initial state. As this function can be called
// multiple times during the parsing of a single commandline (once for each
// sub-command), we don't want the leftover state from previous calls to
// pollute this run's state.
_resetStateToDefault();
// Manually check for the "/?" or "-?" flags, to manually trigger the help text.
if (argc == 2 && (NixHelpFlag == til::at(command.Args(), 1) || WindowsHelpFlag == til::at(command.Args(), 1)))
{
throw CLI::CallForHelp();
}
// Clear the parser's internal state
_app.clear();
// attempt to parse the commandline
_app.parse(args);
// If we parsed the commandline, and _no_ subcommands were provided, try
// parsing again as a "new-tab" command.
if (_noCommandsProvided())
{
_newTabCommand.subcommand->clear();
_newTabCommand.subcommand->parse(args);
}
}
catch (const CLI::CallForHelp& e)
{
return _handleExit(_app, e);
}
catch (const CLI::ParseError& e)
{
// If we parsed the commandline, and _no_ subcommands were provided, try
// parsing again as a "new-tab" command.
if (_noCommandsProvided())
{
try
{
// CLI11 mutated the original vector the first time it tried to
// parse the args. Reconstruct it the way CLI11 wants here.
// "See above for why it's begin() + 1"
std::vector<std::string> args{ command.Args().begin() + 1, command.Args().end() };
std::reverse(args.begin(), args.end());
_newTabCommand.subcommand->clear();
_newTabCommand.subcommand->parse(args);
}
catch (const CLI::ParseError& e)
{
return _handleExit(*_newTabCommand.subcommand, e);
}
}
else
{
return _handleExit(_app, e);
}
}
return 0;
}
// Method Description:
// - Calls App::exit() for the provided command, and collects it's output into
// our _exitMessage buffer.
// Arguments:
// - command: Either the root App object, or a subcommand for which to call exit() on.
// - e: the CLI::Error to process as the exit reason for parsing.
// Return Value:
// - 0 if the command exited successfully
// - nonzero return values are defined in CLI::ExitCodes
int AppCommandlineArgs::_handleExit(const CLI::App& command, const CLI::Error& e)
{
// Create some streams to collect the output that would otherwise go to stdout.
std::ostringstream out;
std::ostringstream err;
const auto result = command.exit(e, out, err);
// I believe only CallForHelp will return 0
if (result == 0)
{
_exitMessage = out.str();
}
else
{
_exitMessage = err.str();
}
return result;
}
// Method Description:
// - Add each subcommand and options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildParser()
{
_buildNewTabParser();
_buildSplitPaneParser();
_buildFocusTabParser();
}
// Method Description:
// - Adds the `new-tab` subcommand and related options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildNewTabParser()
{
_newTabCommand.subcommand = _app.add_subcommand("new-tab", NEEDS_LOC("Create a new tab"));
_addNewTerminalArgs(_newTabCommand);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
_newTabCommand.subcommand->callback([&, this]() {
// Buld the NewTab action from the values we've parsed on the commandline.
auto newTabAction = winrt::make_self<implementation::ActionAndArgs>();
newTabAction->Action(ShortcutAction::NewTab);
auto args = winrt::make_self<implementation::NewTabArgs>();
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
args->TerminalArgs(_getNewTerminalArgs(_newTabCommand));
newTabAction->Args(*args);
_startupActions.push_back(*newTabAction);
});
}
// Method Description:
// - Adds the `split-pane` subcommand and related options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildSplitPaneParser()
{
_newPaneCommand.subcommand = _app.add_subcommand("split-pane", NEEDS_LOC("Create a new pane"));
_addNewTerminalArgs(_newPaneCommand);
_horizontalOption = _newPaneCommand.subcommand->add_flag("-H,--horizontal",
_splitHorizontal,
NEEDS_LOC("Create the new pane as a horizontal split (think [-])"));
_verticalOption = _newPaneCommand.subcommand->add_flag("-V,--vertical",
_splitVertical,
NEEDS_LOC("Create the new pane as a vertical split (think [|])"));
_verticalOption->excludes(_horizontalOption);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
_newPaneCommand.subcommand->callback([&, this]() {
// Buld the SplitPane action from the values we've parsed on the commandline.
auto splitPaneActionAndArgs = winrt::make_self<implementation::ActionAndArgs>();
splitPaneActionAndArgs->Action(ShortcutAction::SplitPane);
auto args = winrt::make_self<implementation::SplitPaneArgs>();
// _getNewTerminalArgs MUST be called before parsing any other options,
// as it might clear those options while finding the commandline
args->TerminalArgs(_getNewTerminalArgs(_newPaneCommand));
args->SplitStyle(SplitState::Automatic);
// Make sure to use the `Option`s here to check if they were set -
// _getNewTerminalArgs might reset them while parsing a commandline
if ((*_horizontalOption || *_verticalOption) && (_splitHorizontal))
{
if (_splitHorizontal)
{
args->SplitStyle(SplitState::Horizontal);
}
else if (_splitVertical)
{
args->SplitStyle(SplitState::Horizontal);
}
}
splitPaneActionAndArgs->Args(*args);
_startupActions.push_back(*splitPaneActionAndArgs);
});
}
// Method Description:
// - Adds the `new-tab` subcommand and related options to the commandline parser.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_buildFocusTabParser()
{
_focusTabCommand = _app.add_subcommand("focus-tab", NEEDS_LOC("Move focus to another tab"));
auto* indexOpt = _focusTabCommand->add_option("-t,--target", _focusTabIndex, NEEDS_LOC("Move focus the tab at the given index"));
auto* nextOpt = _focusTabCommand->add_flag("-n,--next",
_focusNextTab,
NEEDS_LOC("Move focus to the next tab"));
auto* prevOpt = _focusTabCommand->add_flag("-p,--previous",
_focusPrevTab,
NEEDS_LOC("Move focus to the previous tab"));
nextOpt->excludes(prevOpt);
indexOpt->excludes(prevOpt);
indexOpt->excludes(nextOpt);
// When ParseCommand is called, if this subcommand was provided, this
// callback function will be triggered on the same thread. We can be sure
// that `this` will still be safe - this function just lets us know this
// command was parsed.
_focusTabCommand->callback([&, this]() {
// Buld the action from the values we've parsed on the commandline.
auto focusTabAction = winrt::make_self<implementation::ActionAndArgs>();
if (_focusTabIndex >= 0)
{
focusTabAction->Action(ShortcutAction::SwitchToTab);
auto args = winrt::make_self<implementation::SwitchToTabArgs>();
args->TabIndex(_focusTabIndex);
focusTabAction->Args(*args);
_startupActions.push_back(*focusTabAction);
}
else if (_focusNextTab || _focusPrevTab)
{
focusTabAction->Action(_focusNextTab ? ShortcutAction::NextTab : ShortcutAction::PrevTab);
_startupActions.push_back(*focusTabAction);
}
});
}
// Method Description:
// - Add the `NewTerminalArgs` parameters to the given subcommand. This enables
// that subcommand to support all the properties in a NewTerminalArgs.
// Arguments:
// - subcommand: the command to add the args to.
// Return Value:
// - <none>
void AppCommandlineArgs::_addNewTerminalArgs(AppCommandlineArgs::NewTerminalSubcommand& subcommand)
{
subcommand.profileNameOption = subcommand.subcommand->add_option("-p,--profile",
_profileName,
NEEDS_LOC("Open with the given profile. Accepts either the name or guid of a profile"));
subcommand.startingDirectoryOption = subcommand.subcommand->add_option("-d,--startingDirectory",
_startingDirectory,
NEEDS_LOC("Open in the given directory instead of the profile's set startingDirectory"));
subcommand.commandlineOption = subcommand.subcommand->add_option("cmdline",
_commandline,
NEEDS_LOC("Commandline to run in the given profile"));
}
// Method Description:
// - Build a NewTerminalArgs instance from the data we've parsed
// Arguments:
// - <none>
// Return Value:
// - A fully initialized NewTerminalArgs corresponding to values we've currently parsed.
NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewTerminalSubcommand& subcommand)
{
auto args = winrt::make_self<implementation::NewTerminalArgs>();
// If a commandline was provided, we need to get a little tricky parsing
// here. The behavior we want is that once we see a string that we don't
// recognize, we want to treat the rest of the command as the commandline to
// pass to the NewTerminalArgs. However, if that commandline contains any
// options that _we_ understand, CLI11 will try to first grab those options
// from the provided string to parse our own args, and _not_ include them in
// the _commandline here. Consider for example, the following command:
//
// wt.exe new-tab wsl.exe -d Alpine
//
// We want the commandline here to be "wsl.exe -d Alpine zsh". However, by
// default, that'll be parsed by CLI11 as a _startingDirectory of "Alpine",
// with a commandline of "wsl.exe zsh".
//
// So, instead, when we see that there's a commandlineOption that's been
// parsed, we'll use that as our cue to handle the commandline ourselves.
// * We'll start by going through all the options that were parsed by
// CLI11, and clearing all of the ones after the first commandline
// option. This will prevent use from acting on their values later.
// * Then, we'll grab all the strings starting with the first commandline
// string, and add them to the commandline for the NewTerminalArgs.
if (*subcommand.commandlineOption)
{
const std::vector<CLI::Option*>& opts = subcommand.subcommand->parse_order();
auto foundCommandlineStart = false;
for (auto opt : opts)
{
if (opt == subcommand.commandlineOption)
{
foundCommandlineStart = true;
}
else if (foundCommandlineStart)
{
opt->clear();
}
// otherwise, this option preceeded the start of the commandline
}
// Concatenate all the strings starting with the first arg in the
// _commandline as the _real_ commandline.
const auto& firstCmdlineArg = _commandline.at(0);
auto foundFirstArg = false;
std::string fullCommandlineBuffer;
for (const auto& arg : _currentCommandline->Args())
{
if (arg == firstCmdlineArg)
{
foundFirstArg = true;
}
if (foundFirstArg)
{
if (arg.find(" ") != std::string::npos)
{
fullCommandlineBuffer += "\"";
fullCommandlineBuffer += arg;
fullCommandlineBuffer += "\"";
}
else
{
fullCommandlineBuffer += arg;
}
fullCommandlineBuffer += " ";
}
}
// Discard the space from the last arg we appended.
std::string_view realCommandline = fullCommandlineBuffer;
realCommandline = realCommandline.substr(0, static_cast<size_t>(fullCommandlineBuffer.size() - 1));
args->Commandline(winrt::to_hstring(realCommandline));
}
if (*subcommand.profileNameOption)
{
args->Profile(winrt::to_hstring(_profileName));
}
if (*subcommand.startingDirectoryOption)
{
args->StartingDirectory(winrt::to_hstring(_startingDirectory));
}
return *args;
}
// Method Description:
// - This function should return true if _no_ subcommands were parsed from the
// given commandline. In that case, we'll fall back to trying the commandline
// as a new tab command.
// Arguments:
// - <none>
// Return Value:
// - true if no sub commands were parsed.
bool AppCommandlineArgs::_noCommandsProvided()
{
return !(*_newTabCommand.subcommand ||
*_focusTabCommand ||
*_newPaneCommand.subcommand);
}
// Method Description:
// - Reset any state we might have accumulated back to its default values. Since
// we'll be re-using these members across the parsing of many commandlines, we
// need to make sure the state from one run doesn't pollute the following one.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::_resetStateToDefault()
{
_profileName.clear();
_startingDirectory.clear();
_commandline.clear();
_splitVertical = false;
_splitHorizontal = false;
_focusTabIndex = -1;
_focusNextTab = false;
_focusPrevTab = false;
}
// Function Description:
// - Builds a list of Commandline objects for the given argc,argv. Each
// Commandline represents a single command to parse. These commands can be
// seperated by ";", which indicates the start of the next commandline. If the
// user would like to provide ';' in the text of the commandline, they can
// escape it as "\;".
// Arguments:
// - args: an array of arguments to parse into Commandlines
// Return Value:
// - a list of Commandline objects, where each one represents a single
// commandline to parse.
std::vector<Commandline> AppCommandlineArgs::BuildCommands(winrt::array_view<const winrt::hstring>& args)
{
std::vector<Commandline> commands;
commands.emplace_back(Commandline{});
// For each arg in argv:
// Check the string for a delimiter.
// * If there isn't a delimiter, add the arg to the current commandline.
// * If there is a delimiter, split the string at that delimiter. Add the
// first part of the string to the current command, and start a new
// command with the second bit.
for (const auto& arg : args)
{
_addCommandsForArg(commands, { arg });
}
return commands;
}
// Function Description:
// - Builds a list of Commandline objects for the given argc,argv. Each
// Commandline represents a single command to parse. These commands can be
// seperated by ";", which indicates the start of the next commandline. If the
// user would like to provide ';' in the text of the commandline, they can
// escape it as "\;".
// Arguments:
// - argc: the number of arguments provided in argv
// - argv: a c-style array of wchar_t strings. These strings can include spaces in them.
// Return Value:
// - a list of Commandline objects, where each one represents a single
// commandline to parse.
std::vector<Commandline> AppCommandlineArgs::BuildCommands(const std::vector<const wchar_t*>& args)
{
std::vector<Commandline> commands;
// Initialize a first Commandline without a leading `wt.exe` argument. When
// we're run from the commandline, `wt.exe` (or whatever the exe's name is)
// will be the first argument passed to us
commands.resize(1);
// For each arg in argv:
// Check the string for a delimiter.
// * If there isn't a delimiter, add the arg to the current commandline.
// * If there is a delimiter, split the string at that delimiter. Add the
// first part of the string to the current command, ansd start a new
// command with the second bit.
for (const auto& arg : args)
{
_addCommandsForArg(commands, { arg });
}
return commands;
}
// Function Description:
// - Update and append Commandline objects for the given arg to the given list
// of commands. Each Commandline represents a single command to parse. These
// commands can be seperated by ";", which indicates the start of the next
// commandline. If the user would like to provide ';' in the text of the
// commandline, they can escape it as "\;".
// - As we parse arg, if it doesn't contain a delimiter in it, we'll add it to
// the last command in commands. Otherwise, we'll generate a new Commandline
// object for each command in arg.
// Arguments:
// - commands: a list of Commandline objects to modify and append to
// - arg: a single argument that should be parsed into args to append to the
// current command, or create more Commandlines
// Return Value:
// <none>
void AppCommandlineArgs::_addCommandsForArg(std::vector<Commandline>& commands, std::wstring_view arg)
{
std::wstring remaining{ arg };
std::wsmatch match;
// Keep looking for matches until we've found no unescaped delimiters,
// or we've hit the end of the string.
std::regex_search(remaining, match, AppCommandlineArgs::_commandDelimiterRegex);
do
{
if (match.empty())
{
// Easy case: no delimiter. Add it to the current command.
commands.back().AddArg(remaining);
break;
}
else
{
// Harder case: There was a match.
const bool matchedFirstChar = match.position(0) == 0;
// If the match was at the beginning of the string, then the
// next arg should be "", since there was no content before the
// delimiter. Otherwise, add one, since the regex will include
// the last character of the string before the delimiter.
const auto delimiterPosition = matchedFirstChar ? match.position(0) : match.position(0) + 1;
const auto nextArg = remaining.substr(0, delimiterPosition);
if (!nextArg.empty())
{
commands.back().AddArg(nextArg);
}
// Create a new commandline
commands.emplace_back(Commandline{});
// Initialize it with "wt.exe" as the first arg, as if that command
// was passed individually by the user on the commandline.
commands.back().AddArg(std::wstring{ AppCommandlineArgs::PlaceholderExeName });
// Look for the next match in the string, but updating our
// remaining to be the text after the match.
remaining = match.suffix().str();
std::regex_search(remaining, match, AppCommandlineArgs::_commandDelimiterRegex);
}
} while (!remaining.empty());
}
// Method Description:
// - Returns the deque of actions we've buffered as a result of parsing commands.
// Arguments:
// - <none>
// Return Value:
// - the deque of actions we've buffered as a result of parsing commands.
std::deque<winrt::TerminalApp::ActionAndArgs>& AppCommandlineArgs::GetStartupActions()
{
return _startupActions;
}
// Method Description:
// - Get the string of text that should be displayed to the user on exit. This
// is usually helpful for cases where the user entered some sort of invalid
// commandline. It's additionally also used when the user has requested the
// help text.
// Arguments:
// - <none>
// Return Value:
// - The help text, or an error message, generated from parsing the input
// provided by the user.
const std::string& AppCommandlineArgs::GetExitMessage()
{
return _exitMessage;
}
// Method Description:
// - Ensure that the first command in our list of actions is a NewTab action.
// This makes sure that if the user passes a commandline like "wt split-pane
// -H", we _first_ create a new tab, so there's always at least one tab.
// - If the first command in our queue of actions is a NewTab action, this does
// nothing.
// - This should only be called once - if the first NewTab action is popped from
// our _startupActions, calling this again will add another.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppCommandlineArgs::ValidateStartupCommands()
{
// If we parsed no commands, or the first command we've parsed is not a new
// tab action, prepend a new-tab command to the front of the list.
if (_startupActions.empty() ||
_startupActions.front().Action() != ShortcutAction::NewTab)
{
// Build the NewTab action from the values we've parsed on the commandline.
auto newTabAction = winrt::make_self<implementation::ActionAndArgs>();
newTabAction->Action(ShortcutAction::NewTab);
auto args = winrt::make_self<implementation::NewTabArgs>();
auto newTerminalArgs = winrt::make_self<implementation::NewTerminalArgs>();
args->TerminalArgs(*newTerminalArgs);
newTabAction->Args(*args);
_startupActions.push_front(*newTabAction);
}
}

View File

@@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#pragma once
#include "ActionAndArgs.h"
#include "Commandline.h"
#ifdef UNIT_TESTING
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class CommandlineTest;
};
#endif
namespace TerminalApp
{
class AppCommandlineArgs;
};
class TerminalApp::AppCommandlineArgs final
{
public:
static constexpr std::string_view NixHelpFlag{ "-?" };
static constexpr std::string_view WindowsHelpFlag{ "/?" };
static constexpr std::wstring_view PlaceholderExeName{ L"wt.exe" };
AppCommandlineArgs();
~AppCommandlineArgs() = default;
int ParseCommand(const Commandline& command);
static std::vector<Commandline> BuildCommands(const std::vector<const wchar_t*>& args);
static std::vector<Commandline> BuildCommands(winrt::array_view<const winrt::hstring>& args);
void ValidateStartupCommands();
std::deque<winrt::TerminalApp::ActionAndArgs>& GetStartupActions();
const std::string& GetExitMessage();
private:
static const std::wregex _commandDelimiterRegex;
CLI::App _app{ "wt - the Windows Terminal" };
// This is a helper struct to encapsulate all the options for a subcommand
// that produces a NewTerminalArgs.
struct NewTerminalSubcommand
{
CLI::App* subcommand;
CLI::Option* commandlineOption;
CLI::Option* profileNameOption;
CLI::Option* startingDirectoryOption;
};
// --- Subcommands ---
NewTerminalSubcommand _newTabCommand;
NewTerminalSubcommand _newPaneCommand;
CLI::App* _focusTabCommand;
// Are you adding a new sub-command? Make sure to update _noCommandsProvided!
CLI::Option* _horizontalOption;
CLI::Option* _verticalOption;
std::string _profileName;
std::string _startingDirectory;
// _commandline will receive the commandline as it's parsed by CLI11
std::vector<std::string> _commandline;
const Commandline* _currentCommandline{ nullptr };
bool _splitVertical{ false };
bool _splitHorizontal{ false };
int _focusTabIndex{ -1 };
bool _focusNextTab{ false };
bool _focusPrevTab{ false };
// Are you adding more args here? Make sure to reset them in _resetStateToDefault
std::deque<winrt::TerminalApp::ActionAndArgs> _startupActions;
std::string _exitMessage;
winrt::TerminalApp::NewTerminalArgs _getNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _addNewTerminalArgs(NewTerminalSubcommand& subcommand);
void _buildParser();
void _buildNewTabParser();
void _buildSplitPaneParser();
void _buildFocusTabParser();
bool _noCommandsProvided();
void _resetStateToDefault();
int _handleExit(const CLI::App& command, const CLI::Error& e);
static void _addCommandsForArg(std::vector<Commandline>& commands, std::wstring_view arg);
#ifdef UNIT_TESTING
friend class TerminalAppLocalTests::CommandlineTest;
#endif
};

View File

@@ -718,6 +718,24 @@ namespace winrt::TerminalApp::implementation
}
}
int32_t AppLogic::SetStartupCommandline(array_view<const winrt::hstring> actions)
{
if (_root)
{
return _root->SetStartupCommandline(actions);
}
return 0;
}
winrt::hstring AppLogic::EarlyExitMessage()
{
if (_root)
{
return _root->EarlyExitMessage();
}
return { L"" };
}
// -------------------------------- 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.

View File

@@ -10,15 +10,6 @@
#include "TerminalPage.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Microsoft.UI.Xaml.Controls.Primitives.h>
#include <winrt/Microsoft.UI.Xaml.XamlTypeInfo.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
namespace winrt::TerminalApp::implementation
{
struct AppLogic : AppLogicT<AppLogic>
@@ -33,6 +24,9 @@ namespace winrt::TerminalApp::implementation
void LoadSettings();
[[nodiscard]] std::shared_ptr<::TerminalApp::CascadiaSettings> GetSettings() const noexcept;
int32_t SetStartupCommandline(array_view<const winrt::hstring> actions);
winrt::hstring EarlyExitMessage();
Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi);
winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY);
winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme();

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
import "../TerminalPage.idl";
import "../ShortcutActionDispatch.idl";
namespace TerminalApp
{
@@ -11,8 +12,7 @@ namespace TerminalApp
MaximizedMode,
};
[default_interface] runtimeclass AppLogic
{
[default_interface] runtimeclass AppLogic {
AppLogic();
// For your own sanity, it's better to do setup outside the ctor.
@@ -25,6 +25,9 @@ namespace TerminalApp
Boolean IsUwp();
void RunAsUwp();
Int32 SetStartupCommandline(String[] commands);
String EarlyExitMessage { get; };
void LoadSettings();
Windows.UI.Xaml.UIElement GetRoot();

View File

@@ -0,0 +1,39 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "pch.h"
#include "Commandline.h"
using namespace TerminalApp;
size_t Commandline::Argc() const
{
return _args.size();
}
const std::vector<std::string>& Commandline::Args() const
{
return _args;
}
// Method Description:
// - Add the given arg to the list of args for this commandline. If the arg has
// an escaped delimiter ('\;') in it, we'll de-escape it, so the processed
// Commandline will have it as just a ';'.
// Arguments:
// - nextArg: The string to add as an arg to the commandline. This string may contain spaces.
// Return Value:
// - <none>
void Commandline::AddArg(const std::wstring& nextArg)
{
// Attempt to convert '\;' in the arg to just '\', removing the escaping
std::wstring modifiedArg{ nextArg };
size_t pos = modifiedArg.find(EscapedDelimiter, 0);
while (pos != std::string::npos)
{
modifiedArg.replace(pos, EscapedDelimiter.length(), Delimiter);
pos = modifiedArg.find(EscapedDelimiter, pos + Delimiter.length());
}
_args.emplace_back(winrt::to_string(modifiedArg));
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Module Name:
// - Commandline.h
//
// Abstract:
// - This is a helper class for representing a single commandline within the
// Terminal Application. Users can specify multiple commands on a single
// commandline invocation of the Terminal, and this class is used to represent
// a single individual command.
// - Args are added to this class as wide strings, because the args provided to
// the application are typically wide strings.
// - Args are retrieved from this class as a list of narrow-strings, because
// CLI11 (which we're using to parse the commandlines) requires narrow
// strings.
//
// Author:
// - Mike Griese (zadjii-msft) 09-Jan-2020
// fwdecl unittest classes
namespace TerminalAppLocalTests
{
class CommandlineTest;
};
namespace TerminalApp
{
class Commandline;
};
class TerminalApp::Commandline
{
public:
static constexpr std::wstring_view Delimiter{ L";" };
static constexpr std::wstring_view EscapedDelimiter{ L"\\;" };
void AddArg(const std::wstring& nextArg);
size_t Argc() const;
const std::vector<std::string>& Args() const;
private:
std::vector<std::string> _args;
friend class TerminalAppLocalTests::CommandlineTest;
};

View File

@@ -358,6 +358,25 @@ void Pane::Close()
_ClosedHandlers(nullptr, nullptr);
}
// Method Description:
// - Prepare this pane to be removed from the UI hierarchy by closing all controls
// and connections beneath it.
void Pane::Shutdown()
{
// Lock the create/close lock so that another operation won't concurrently
// modify our tree
std::unique_lock lock{ _createCloseLock };
if (_IsLeaf())
{
_control.Close();
}
else
{
_firstChild->Shutdown();
_secondChild->Shutdown();
}
}
// Method Description:
// - Get the root UIElement of this pane. There may be a single TermControl as a
// child, or an entire tree of grids and panes as children of this element.

View File

@@ -64,6 +64,7 @@ public:
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
void Shutdown();
void Close();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);

View File

@@ -240,7 +240,7 @@ bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType)
void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control)
{
auto [first, second] = _activePane->Split(splitType, profile, control);
_activePane = first;
_AttachEventHandlersToControl(control);
// Add a event handlers to the new panes' GotFocus event. When the pane
@@ -298,6 +298,13 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
_rootPane->NavigateFocus(direction);
}
// Method Description:
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
void Tab::Shutdown()
{
_rootPane->Shutdown();
}
// Method Description:
// - Closes the currently focused pane in this tab. If it's the last pane in
// this tab, our Closed event will be fired (at a later time) for anyone

View File

@@ -37,6 +37,7 @@ public:
winrt::hstring GetActiveTitle() const;
winrt::fire_and_forget SetTabText(const winrt::hstring text);
void Shutdown();
void ClosePane();
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);

View File

@@ -124,10 +124,57 @@ namespace winrt::TerminalApp::implementation
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
_CreateNewTabFlyout();
_UpdateTabWidthMode();
_OpenNewTab(nullptr);
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
// Actually start the terminal.
if (_appArgs.GetStartupActions().empty())
{
_OpenNewTab(nullptr);
}
else
{
_appArgs.ValidateStartupCommands();
// This will kick off a chain of events to perform each startup
// action. As each startup action is completed, the next will be
// fired.
_ProcessNextStartupAction();
}
}
// Method Description:
// - Process the next startup action in our list of startup actions. When
// that action is complete, fire the next (if there are any more).
// Arguments:
// - <none>
// Return Value:
// - <none>
fire_and_forget TerminalPage::_ProcessNextStartupAction()
{
// If there are no actions left, do nothing.
if (_appArgs.GetStartupActions().empty())
{
return;
}
// Get the next action to be processed
auto nextAction = _appArgs.GetStartupActions().front();
_appArgs.GetStartupActions().pop_front();
auto weakThis{ get_weak() };
// Handle it on the UI thread.
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
if (auto page{ weakThis.get() })
{
page->_actionDispatch->DoAction(nextAction);
// Kick off the next action to be handled (if necessary)
page->_ProcessNextStartupAction();
}
}
// Method Description:
@@ -441,6 +488,7 @@ namespace winrt::TerminalApp::implementation
// - settings: the TerminalSettings object to use to create the TerminalControl with.
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
{
const bool isFirstTab = _tabs.empty();
// Initialize the new tab
// Create a connection based on the values in our settings object.
@@ -494,12 +542,20 @@ namespace winrt::TerminalApp::implementation
}
});
// This is one way to set the tab's selected background color.
// tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?);
// This kicks off TabView::SelectionChanged, in response to which we'll attach the terminal's
// Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
// If this is the first tab, we don't need to kick off the event to get
// the tab's content added to the root of the page. just do it
// immediately.
if (isFirstTab)
{
_tabContent.Children().Clear();
_tabContent.Children().Append(newTab->GetRootElement());
}
else
{
// This kicks off TabView::SelectionChanged, in response to which
// we'll attach the terminal's Xaml control to the Xaml root.
_tabView.SelectedItem(tabViewItem);
}
}
// Method Description:
@@ -753,16 +809,20 @@ namespace winrt::TerminalApp::implementation
// - tabIndex: the index of the tab to be removed
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
{
// Removing the tab from the collection should destroy its control and disconnect its connection,
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
auto iterator = _tabs.begin() + tabIndex;
(*iterator)->Shutdown();
_tabs.erase(iterator);
_tabView.TabItems().RemoveAt(tabIndex);
// To close the window here, we need to close the hosting window.
if (_tabs.size() == 1)
if (_tabs.size() == 0)
{
_lastTabClosedHandlers(*this, nullptr);
}
// Removing the tab from the collection will destroy its control and disconnect its connection.
_tabs.erase(_tabs.begin() + tabIndex);
_tabView.TabItems().RemoveAt(tabIndex);
auto focusedTabIndex = _GetFocusedTabIndex();
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
{
@@ -864,7 +924,7 @@ namespace winrt::TerminalApp::implementation
winrt::Microsoft::Terminal::TerminalControl::TermControl TerminalPage::_GetActiveControl()
{
int focusedTabIndex = _GetFocusedTabIndex();
auto focusedTab = _tabs[focusedTabIndex];
auto focusedTab = _tabs.at(focusedTabIndex);
return focusedTab->GetActiveTerminalControl();
}
@@ -1427,6 +1487,72 @@ namespace winrt::TerminalApp::implementation
}
}
// Method Description:
// - Sets the initial commandline to process on startup, and attempts to
// parse it. Commands will be parsed into a list of ShortcutActions that
// will be processed on TerminalPage::Create().
// - This function will have no effective result after Create() is called.
// - This function returns 0, unless a there was a non-zero result from
// trying to parse one of the commands provided. In that case, no commands
// after the failing command will be parsed, and the non-zero code
// returned.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// Return Value:
// - the result of the first command who's parsing returned a non-zero code,
// or 0. (see TerminalPage::_ParseArgs)
int32_t TerminalPage::SetStartupCommandline(winrt::array_view<const hstring> args)
{
return _ParseArgs(args);
}
// Method Description:
// - Attempts to parse an array of commandline args into a list of
// commands to execute, and then parses these commands. As commands are
// succesfully parsed, they will generate ShortcutActions for us to be
// able to execute. If we fail to parse any commands, we'll return the
// error code from the failure to parse that command, and stop processing
// additional commands.
// Arguments:
// - args: an array of strings to process as a commandline. These args can contain spaces
// Return Value:
// - 0 if the commandline was successfully parsed
int TerminalPage::_ParseArgs(winrt::array_view<const hstring>& args)
{
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
for (auto& cmdBlob : commands)
{
// On one hand, it seems like we should be able to have one
// AppCommandlineArgs for parsing all of them, and collect the
// results one at a time.
//
// On the other hand, re-using a CLI::App seems to leave state from
// previous parsings around, so we could get mysterious behavior
// where one command affects the values of the next.
//
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
// > If that option is not given, CLI11 will not touch the initial
// > value. This allows you to set up defaults by simply setting
// > your value beforehand.
//
// So we pretty much need the to either manually reset the state
// each command, or build new ones.
const auto result = _appArgs.ParseCommand(cmdBlob);
// If this succeeded, result will be 0. Otherwise, the caller should
// exit(result), to exit the program.
if (result != 0)
{
return result;
}
}
// If all the args were successfully parsed, we'll have some commands
// built in _appArgs, which we'll use when the application starts up.
return 0;
}
// Method Description:
// - This is the method that App will call when the titlebar
// has been clicked. It dismisses any open flyouts.
@@ -1472,6 +1598,22 @@ namespace winrt::TerminalApp::implementation
_UpdateTabView();
}
// Method Description:
// - If there were any errors parsing the commandline that was used to
// initialize the terminal, this will return a string containing that
// message. If there were no errors, this message will be blank.
// - If the user requested help on any command (using --help), this will
// contain the help message.
// Arguments:
// - <none>
// Return Value:
// - the help text or error message for the providied commandline, if one
// exists, otherwise the empty string.
winrt::hstring TerminalPage::EarlyExitMessage()
{
return winrt::to_hstring(_appArgs.GetExitMessage());
}
// -------------------------------- 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.

View File

@@ -8,9 +8,9 @@
#include "CascadiaSettings.h"
#include "Profile.h"
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Microsoft.Terminal.TerminalControl.h>
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include "AppCommandlineArgs.h"
// fwdecl unittest classes
namespace TerminalAppLocalTests
@@ -39,6 +39,9 @@ namespace winrt::TerminalApp::implementation
void CloseWindow();
int32_t SetStartupCommandline(winrt::array_view<const hstring> args);
winrt::hstring EarlyExitMessage();
// -------------------------------- WinRT Events ---------------------------------
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(TitleChanged, _titleChangeHandlers, winrt::Windows::Foundation::IInspectable, winrt::hstring);
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(LastTabClosed, _lastTabClosedHandlers, winrt::Windows::Foundation::IInspectable, winrt::TerminalApp::LastTabClosedEventArgs);
@@ -70,6 +73,10 @@ namespace winrt::TerminalApp::implementation
winrt::com_ptr<ShortcutActionDispatch> _actionDispatch{ winrt::make_self<ShortcutActionDispatch>() };
::TerminalApp::AppCommandlineArgs _appArgs;
int _ParseArgs(winrt::array_view<const hstring>& args);
fire_and_forget _ProcessNextStartupAction();
void _ShowAboutDialog();
void _ShowCloseWarningDialog();

View File

@@ -10,6 +10,9 @@ namespace TerminalApp
{
TerminalPage();
Int32 SetStartupCommandline(String[] commands);
String EarlyExitMessage { get; };
event Windows.Foundation.TypedEventHandler<Object, String> TitleChanged;
event Windows.Foundation.TypedEventHandler<Object, LastTabClosedEventArgs> LastTabClosed;
event Windows.Foundation.TypedEventHandler<Object, Windows.UI.Xaml.UIElement> SetTitleBarContent;

View File

@@ -25,6 +25,15 @@
</PropertyGroup>
<Import Project="..\..\..\..\common.openconsole.props" Condition="'$(OpenConsoleDir)'==''" />
<Import Project="$(OpenConsoleDir)src\cppwinrt.build.pre.props" />
<ItemDefinitionGroup>
<ClCompile>
<!-- For CLI11: It uses dynamic_cast to cast types around, which depends
on being compiled with RTTI (/GR). -->
<RuntimeTypeInfo>true</RuntimeTypeInfo>
</ClCompile>
</ItemDefinitionGroup>
<!-- ========================= XAML files ======================== -->
<ItemGroup>
<!-- HERE BE DRAGONS:
@@ -53,6 +62,8 @@
<!-- ========================= Headers ======================== -->
<ItemGroup>
<ClInclude Include="../App.base.h" />
<ClInclude Include="../AppCommandlineArgs.h" />
<ClInclude Include="../Commandline.h" />
<ClInclude Include="../MinMaxCloseControl.h">
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
</ClInclude>
@@ -105,6 +116,8 @@
<!-- ========================= Cpp Files ======================== -->
<ItemGroup>
<ClCompile Include="../init.cpp" />
<ClCompile Include="../AppCommandlineArgs.cpp" />
<ClCompile Include="../Commandline.cpp" />
<ClCompile Include="../MinMaxCloseControl.cpp">
<DependentUpon>../MinMaxCloseControl.xaml</DependentUpon>
</ClCompile>
@@ -253,7 +266,7 @@
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories>..;$(OpenConsoleDir)\dep;$(OpenConsoleDir)\dep\jsoncpp\json;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
<!-- Manually disable unreachable code warning, because jconcpp has a ton of that. -->
<DisableSpecificWarnings>4702;%(DisableSpecificWarnings)</DisableSpecificWarnings>
</ClCompile>

View File

@@ -36,6 +36,7 @@
#include <winrt/Windows.UI.Xaml.Hosting.h>
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Documents.h"
#include <winrt/Windows.ApplicationModel.DataTransfer.h>
#include <winrt/Microsoft.Toolkit.Win32.UI.XamlHost.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
@@ -57,6 +58,13 @@ TRACELOGGING_DECLARE_PROVIDER(g_hTerminalAppProvider);
#include <json.h>
#include <shellapi.h>
#include <filesystem>
#include <winrt/Microsoft.Terminal.TerminalConnection.h>
#include <CLI11/CLI11.hpp>
// TODO:GH#4155 - This macro can be used to identify strings that need to
// be localized in the future, but aren't localized currently due to some build
// system restrictions, mainly due to breaking our unittests. All of these
// strings should eventually be moved to Resources.resw.
#define NEEDS_LOC(x) (x)

View File

@@ -59,23 +59,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// set the input scope to Text because this control is for any text.
_editContext.InputScope(Core::CoreTextInputScope::Text);
_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });
_textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler });
_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });
_selectionRequestedRevoker = _editContext.SelectionRequested(winrt::auto_revoke, { this, &TSFInputControl::_selectionRequestedHandler });
_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });
_focusRemovedRevoker = _editContext.FocusRemoved(winrt::auto_revoke, { this, &TSFInputControl::_focusRemovedHandler });
_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });
_textUpdatingRevoker = _editContext.TextUpdating(winrt::auto_revoke, { this, &TSFInputControl::_textUpdatingHandler });
_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });
_selectionUpdatingRevoker = _editContext.SelectionUpdating(winrt::auto_revoke, { this, &TSFInputControl::_selectionUpdatingHandler });
_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });
_formatUpdatingRevoker = _editContext.FormatUpdating(winrt::auto_revoke, { this, &TSFInputControl::_formatUpdatingHandler });
_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });
_layoutRequestedRevoker = _editContext.LayoutRequested(winrt::auto_revoke, { this, &TSFInputControl::_layoutRequestedHandler });
_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });
_compositionStartedRevoker = _editContext.CompositionStarted(winrt::auto_revoke, { this, &TSFInputControl::_compositionStartedHandler });
_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
_compositionCompletedRevoker = _editContext.CompositionCompleted(winrt::auto_revoke, { this, &TSFInputControl::_compositionCompletedHandler });
}
// Method Description:
// - Prepares this TSFInputControl to be removed from the UI hierarchy.
void TSFInputControl::Close()
{
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
// See GH#4159 for more info.
_layoutRequestedRevoker.revoke();
}
// Method Description:

View File

@@ -37,6 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void NotifyFocusEnter();
void NotifyFocusLeave();
void Close();
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
// -------------------------------- WinRT Events ---------------------------------
@@ -55,6 +57,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs const& args);
void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs const& args);
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextRequested_revoker _textRequestedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionRequested_revoker _selectionRequestedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::FocusRemoved_revoker _focusRemovedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextUpdating_revoker _textUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionUpdating_revoker _selectionUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::FormatUpdating_revoker _formatUpdatingRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::LayoutRequested_revoker _layoutRequestedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
Windows::UI::Xaml::Controls::Canvas _canvas;
Windows::UI::Xaml::Controls::TextBlock _textBlock;

View File

@@ -27,5 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
void NotifyFocusEnter();
void NotifyFocusLeave();
void Close();
}
}

View File

@@ -761,6 +761,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.Start();
_initializedTerminal = true;
_InitializedHandlers(*this, nullptr);
return true;
}
@@ -1734,6 +1735,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.TerminalOutput(_connectionOutputEventToken);
_connectionStateChangedRevoker.revoke();
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
if (auto localConnection{ std::exchange(_connection, nullptr) })
{
localConnection.Close();

View File

@@ -102,6 +102,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
DECLARE_EVENT_WITH_TYPED_EVENT_HANDLER(CopyToClipboard, _clipboardCopyHandlers, TerminalControl::TermControl, TerminalControl::CopyToClipboardEventArgs);
TYPED_EVENT(ConnectionStateChanged, TerminalControl::TermControl, IInspectable);
TYPED_EVENT(Initialized, TerminalControl::TermControl, Windows::UI::Xaml::RoutedEventArgs);
// clang-format on
private:

View File

@@ -33,6 +33,7 @@ namespace Microsoft.Terminal.TerminalControl
event Windows.Foundation.TypedEventHandler<TermControl, CopyToClipboardEventArgs> CopyToClipboard;
event Windows.Foundation.TypedEventHandler<TermControl, PasteFromClipboardEventArgs> PasteFromClipboard;
event Windows.Foundation.TypedEventHandler<TermControl, Windows.UI.Xaml.RoutedEventArgs> Initialized;
// This is an event handler forwarder for the underlying connection.
// We expose this and ConnectionState here so that it might eventually be data bound.
event Windows.Foundation.TypedEventHandler<TermControl, IInspectable> ConnectionStateChanged;

View File

@@ -5,6 +5,9 @@
#include "AppHost.h"
#include "../types/inc/Viewport.hpp"
#include "../types/inc/utils.hpp"
#include "../types/inc/User32Utils.hpp"
#include "resource.h"
using namespace winrt::Windows::UI;
using namespace winrt::Windows::UI::Composition;
@@ -23,6 +26,10 @@ AppHost::AppHost() noexcept :
_useNonClientArea = _logic.GetShowTabsInTitlebar();
// If there were commandline args to our process, try and process them here.
// Do this before AppLogic::Create, otherwise this will have no effect
_HandleCommandlineArgs();
if (_useNonClientArea)
{
_window = std::make_unique<NonClientIslandWindow>(_logic.GetRequestedTheme());
@@ -56,6 +63,55 @@ AppHost::~AppHost()
_app = nullptr;
}
// Method Description:
// - Retrieve any commandline args passed on the commandline, and pass them to
// the app logic for processing.
// - If the logic determined there's an error while processing that commandline,
// display a message box to the user with the text of the error, and exit.
// * We display a message box because we're a Win32 application (not a
// console app), and the shell has undoubtedly returned to the foreground
// of the console. Text emitted here might mix unexpectedly with output
// from the shell process.
// Arguments:
// - <none>
// Return Value:
// - <none>
void AppHost::_HandleCommandlineArgs()
{
if (auto commandline{ GetCommandLineW() })
{
int argc = 0;
// Get the argv, and turn them into a hstring array to pass to the app.
wil::unique_any<LPWSTR*, decltype(&::LocalFree), ::LocalFree> argv{ CommandLineToArgvW(commandline, &argc) };
if (argv)
{
std::vector<winrt::hstring> args;
for (auto& elem : wil::make_range(argv.get(), argc))
{
args.emplace_back(elem);
}
const auto result = _logic.SetStartupCommandline({ args });
const auto message = _logic.EarlyExitMessage();
if (!message.empty())
{
const auto displayHelp = result == 0;
const auto messageTitle = displayHelp ? IDS_HELP_DIALOG_TITLE : IDS_ERROR_DIALOG_TITLE;
const auto messageIcon = displayHelp ? MB_ICONWARNING : MB_ICONERROR;
// TODO:GH#4134: polish this dialog more, to make the text more
// like msiexec /?
MessageBoxW(nullptr,
message.data(),
GetStringResource(messageTitle).data(),
MB_OK | messageIcon);
ExitProcess(result);
}
}
}
}
// Method Description:
// - Initializes the XAML island, creates the terminal app, and sets the
// island's content to that of the terminal app's content. Also registers some

View File

@@ -25,6 +25,8 @@ private:
winrt::TerminalApp::App _app;
winrt::TerminalApp::AppLogic _logic;
void _HandleCommandlineArgs();
void _HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::TerminalApp::LaunchMode& launchMode);
void _UpdateTitleBarContent(const winrt::Windows::Foundation::IInspectable& sender,
const winrt::Windows::UI::Xaml::UIElement& arg);

View File

@@ -25,18 +25,18 @@ LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
// TEXTINCLUDE
//
1 TEXTINCLUDE
1 TEXTINCLUDE
BEGIN
"resource.h\0"
END
2 TEXTINCLUDE
2 TEXTINCLUDE
BEGIN
"#include ""winres.h""\r\n"
"\0"
END
3 TEXTINCLUDE
3 TEXTINCLUDE
BEGIN
"\r\n"
"\0"
@@ -63,7 +63,8 @@ IDI_APPICON ICON "..\\..\\..\\res\\terminal.ico"
STRINGTABLE
BEGIN
IDS_ERROR_DIALOG_TITLE "Error"
IDS_ERROR_ARCHITECTURE_FORMAT
IDS_HELP_DIALOG_TITLE "Help"
IDS_ERROR_ARCHITECTURE_FORMAT
"Windows Terminal is designed to run on your system's native architecture (%s).\nYou are currently using the %s version.\n\nPlease use the version of Windows Terminal that matches your system's native architecture."
IDS_X86_ARCHITECTURE "i386"
END

View File

@@ -4,6 +4,7 @@
#include "pch.h"
#include "AppHost.h"
#include "resource.h"
#include "../types/inc/User32Utils.hpp"
using namespace winrt;
using namespace winrt::Windows::UI;
@@ -20,30 +21,6 @@ TRACELOGGING_DEFINE_PROVIDER(
(0x56c06166, 0x2e2e, 0x5f4d, 0x7f, 0xf3, 0x74, 0xf4, 0xb7, 0x8c, 0x87, 0xd6),
TraceLoggingOptionMicrosoftTelemetry());
// Routine Description:
// - Retrieves the string resource from the current module with the given ID
// from the resources files. See resource.h and the .rc definitions for valid IDs.
// Arguments:
// - id - Resource ID
// Return Value:
// - String resource retrieved from that ID.
static std::wstring GetStringResource(const UINT id)
{
// Calling LoadStringW with a pointer-sized storage and no length will return a read-only pointer
// directly to the resource data instead of copying it immediately into a buffer.
LPWSTR readOnlyResource = nullptr;
const auto length = LoadStringW(wil::GetModuleInstanceHandle(),
id,
reinterpret_cast<LPWSTR>(&readOnlyResource),
0);
// However, the pointer and length given are NOT guaranteed to be zero-terminated
// and most uses of this data will probably want a zero-terminated string.
// So we're going to construct and return a std::wstring copy from the pointer/length
// since those are certainly zero-terminated.
return { readOnlyResource, gsl::narrow<size_t>(length) };
}
// Routine Description:
// - Takes an image architecture and locates a string resource that maps to that architecture.
// Arguments:

View File

@@ -63,3 +63,7 @@ Abstract:
TRACELOGGING_DECLARE_PROVIDER(g_hWindowsTerminalProvider);
#include <telemetry\ProjectTelemetry.h>
#include <TraceLoggingActivity.h>
// For commandline argument processing
#include <shellapi.h>
#include <processenv.h>

View File

@@ -4,6 +4,7 @@
//
#define IDI_APPICON 101
#define IDS_ERROR_DIALOG_TITLE 105
#define IDS_HELP_DIALOG_TITLE 106
#define IDS_ERROR_ARCHITECTURE_FORMAT 110
#define IDS_X86_ARCHITECTURE 111
#define IDS_AMD64_ARCHITECTURE 112

View File

@@ -1299,6 +1299,35 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
return STATUS_SUCCESS;
}
// Routine Description:
// - A private API call for changing the screen mode between normal and reverse.
// When in reverse screen mode, the background and foreground colors are switched.
// Parameters:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return value:
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate error code.
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode)
{
try
{
Globals& g = ServiceLocator::LocateGlobals();
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
gci.SetScreenReversed(reverseMode);
if (g.pRender)
{
g.pRender->TriggerRedrawAll();
}
return STATUS_SUCCESS;
}
catch (...)
{
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
}
}
// Routine Description:
// - A private API call for making the cursor visible or not. Does not modify
// blinking state.

View File

@@ -29,6 +29,8 @@ void DoSrvPrivateSetDefaultAttributes(SCREEN_INFORMATION& screenInfo, const bool
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode);
void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noexcept;
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable);

View File

@@ -345,6 +345,19 @@ bool ConhostInternalGetSet::PrivateSetKeypadMode(const bool fApplicationMode)
return NT_SUCCESS(DoSrvPrivateSetKeypadMode(fApplicationMode));
}
// Routine Description:
// - Connects the PrivateSetScreenMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateSetScreenMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on our public API surface.
// Arguments:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return Value:
// - true if successful (see DoSrvPrivateSetScreenMode). false otherwise.
bool ConhostInternalGetSet::PrivateSetScreenMode(const bool reverseMode)
{
return NT_SUCCESS(DoSrvPrivateSetScreenMode(reverseMode));
}
// Routine Description:
// - Connects the PrivateShowCursor call directly into our Driver Message servicing call inside Conhost.exe
// PrivateShowCursor is an internal-only "API" call that the vt commands can execute,

View File

@@ -93,6 +93,8 @@ public:
bool PrivateSetCursorKeysMode(const bool applicationMode) override;
bool PrivateSetKeypadMode(const bool applicationMode) override;
bool PrivateSetScreenMode(const bool reverseMode) override;
bool PrivateShowCursor(const bool show) noexcept override;
bool PrivateAllowCursorBlinking(const bool enable) override;

View File

@@ -53,6 +53,7 @@ Settings::Settings() :
_fUseWindowSizePixels(false),
_fAutoReturnOnNewline(true), // the historic Windows behavior defaults this to on.
_fRenderGridWorldwide(false), // historically grid lines were only rendered in DBCS codepages, so this is false by default unless otherwise specified.
_fScreenReversed(false),
// window size pixels initialized below
_fInterceptCopyPaste(0),
_DefaultForeground(INVALID_COLOR),
@@ -394,6 +395,15 @@ void Settings::SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed
}
}
bool Settings::IsScreenReversed() const
{
return _fScreenReversed;
}
void Settings::SetScreenReversed(const bool fScreenReversed)
{
_fScreenReversed = fScreenReversed;
}
bool Settings::GetFilterOnPaste() const
{
return _fFilterOnPaste;
@@ -942,7 +952,14 @@ COLORREF Settings::CalculateDefaultBackground() const noexcept
COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexcept
{
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
if (_fScreenReversed)
{
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
else
{
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
}
// Method Description:
@@ -955,7 +972,14 @@ COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexce
COLORREF Settings::LookupBackgroundColor(const TextAttribute& attr) const noexcept
{
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
if (_fScreenReversed)
{
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
else
{
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
}
}
bool Settings::GetCopyColor() const noexcept

View File

@@ -52,6 +52,9 @@ public:
bool IsGridRenderingAllowedWorldwide() const;
void SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed);
bool IsScreenReversed() const;
void SetScreenReversed(const bool fScreenReversed);
bool GetFilterOnPaste() const;
void SetFilterOnPaste(const bool fFilterOnPaste);
@@ -231,6 +234,7 @@ private:
DWORD _dwVirtTermLevel;
bool _fAutoReturnOnNewline;
bool _fRenderGridWorldwide;
bool _fScreenReversed;
bool _fUseDx;
bool _fCopyColor;

View File

@@ -182,6 +182,7 @@ class ScreenBufferTests
TEST_METHOD(ScrollLines256Colors);
TEST_METHOD(SetScreenMode);
TEST_METHOD(SetOriginMode);
TEST_METHOD(HardResetBuffer);
@@ -4501,6 +4502,34 @@ void ScreenBufferTests::ScrollLines256Colors()
}
}
void ScreenBufferTests::SetScreenMode()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();
const auto rgbForeground = RGB(12, 34, 56);
const auto rgbBackground = RGB(78, 90, 12);
const auto testAttr = TextAttribute{ rgbForeground, rgbBackground };
Log::Comment(L"By default the screen mode is normal.");
VERIFY_IS_FALSE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));
Log::Comment(L"When DECSCNM is set, background and foreground colors are switched.");
stateMachine.ProcessString(L"\x1B[?5h");
VERIFY_IS_TRUE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupBackgroundColor(testAttr));
Log::Comment(L"When DECSCNM is reset, the colors are normal again.");
stateMachine.ProcessString(L"\x1B[?5l");
VERIFY_IS_FALSE(gci.IsScreenReversed());
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));
}
void ScreenBufferTests::SetOriginMode()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();

View File

@@ -46,6 +46,7 @@
#include <functional>
#include <set>
#include <unordered_set>
#include <regex>
// WIL
#include <wil/Common.h>

View File

@@ -82,6 +82,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
{
DECCKM_CursorKeysMode = 1,
DECCOLM_SetNumberOfColumns = 3,
DECSCNM_ScreenMode = 5,
DECOM_OriginMode = 6,
ATT610_StartCursorBlink = 12,
DECTCEM_TextCursorEnableMode = 25,

View File

@@ -54,6 +54,7 @@ public:
virtual bool SetCursorKeysMode(const bool applicationMode) = 0; // DECCKM
virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
virtual bool EnableCursorBlinking(const bool enable) = 0; // ATT610
virtual bool SetScreenMode(const bool reverseMode) = 0; //DECSCNM
virtual bool SetOriginMode(const bool relativeMode) = 0; // DECOM
virtual bool SetTopBottomScrollingMargins(const size_t topMargin, const size_t bottomMargin) = 0; // DECSTBM
virtual bool WarningBell() = 0; // BEL

View File

@@ -933,6 +933,9 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
success = _DoDECCOLMHelper(enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns);
break;
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
success = SetScreenMode(enable);
break;
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
// The cursor is also moved to the new home position when the origin mode is set or reset.
success = SetOriginMode(enable) && CursorPosition(1, 1);
@@ -1079,6 +1082,18 @@ bool AdaptDispatch::DeleteLine(const size_t distance)
return _pConApi->DeleteLines(distance);
}
// Routine Description:
// - DECSCNM - Sets the screen mode to either normal or reverse.
// When in reverse screen mode, the background and foreground colors are switched.
// Arguments:
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetScreenMode(const bool reverseMode)
{
return _pConApi->PrivateSetScreenMode(reverseMode);
}
// Routine Description:
// - DECOM - Sets the cursor addressing origin mode to relative or absolute.
// When relative, line numbers start at top margin of the user-defined scrolling region.
@@ -1475,6 +1490,12 @@ bool AdaptDispatch::HardReset()
success = _EraseScrollback();
}
// Set the DECSCNM screen mode back to normal.
if (success)
{
success = SetScreenMode(false);
}
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
if (success)
{

View File

@@ -68,6 +68,7 @@ namespace Microsoft::Console::VirtualTerminal
bool SetCursorKeysMode(const bool applicationMode) override; // DECCKM
bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM
bool EnableCursorBlinking(const bool enable) override; // ATT610
bool SetScreenMode(const bool reverseMode) override; //DECSCNM
bool SetOriginMode(const bool relativeMode) noexcept override; // DECOM
bool SetTopBottomScrollingMargins(const size_t topMargin,
const size_t bottomMargin) override; // DECSTBM

View File

@@ -57,6 +57,8 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateSetCursorKeysMode(const bool applicationMode) = 0;
virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0;
virtual bool PrivateSetScreenMode(const bool reverseMode) = 0;
virtual bool PrivateShowCursor(const bool show) = 0;
virtual bool PrivateAllowCursorBlinking(const bool enable) = 0;

View File

@@ -48,6 +48,7 @@ public:
bool SetCursorKeysMode(const bool /*applicationMode*/) noexcept override { return false; } // DECCKM
bool SetKeypadMode(const bool /*applicationMode*/) noexcept override { return false; } // DECKPAM, DECKPNM
bool EnableCursorBlinking(const bool /*enable*/) noexcept override { return false; } // ATT610
bool SetScreenMode(const bool /*reverseMode*/) noexcept override { return false; } //DECSCNM
bool SetOriginMode(const bool /*relativeMode*/) noexcept override { return false; }; // DECOM
bool SetTopBottomScrollingMargins(const size_t /*topMargin*/, const size_t /*bottomMargin*/) noexcept override { return false; } // DECSTBM
bool WarningBell() noexcept override { return false; } // BEL

View File

@@ -162,6 +162,13 @@ public:
return _privateSetKeypadModeResult;
}
bool PrivateSetScreenMode(const bool /*reverseMode*/) override
{
Log::Comment(L"PrivateSetScreenMode MOCK called...");
return true;
}
bool PrivateShowCursor(const bool show) override
{
Log::Comment(L"PrivateShowCursor MOCK called...");

View File

@@ -678,6 +678,7 @@ public:
_isAltBuffer{ false },
_cursorKeysMode{ false },
_cursorBlinking{ true },
_isScreenModeReversed{ false },
_isOriginModeRelative{ false },
_warningBell{ false },
_carriageReturn{ false },
@@ -857,6 +858,9 @@ public:
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
fSuccess = SetColumns(static_cast<size_t>(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns));
break;
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
fSuccess = SetScreenMode(fEnable);
break;
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
// The cursor is also moved to the new home position when the origin mode is set or reset.
fSuccess = SetOriginMode(fEnable) && CursorPosition(1, 1);
@@ -920,6 +924,12 @@ public:
return true;
}
bool SetScreenMode(const bool reverseMode) noexcept override
{
_isScreenModeReversed = reverseMode;
return true;
}
bool SetOriginMode(const bool fRelativeMode) noexcept override
{
_isOriginModeRelative = fRelativeMode;
@@ -999,6 +1009,7 @@ public:
bool _isAltBuffer;
bool _cursorKeysMode;
bool _cursorBlinking;
bool _isScreenModeReversed;
bool _isOriginModeRelative;
bool _warningBell;
bool _carriageReturn;
@@ -1290,6 +1301,25 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}
TEST_METHOD(TestScreenMode)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));
mach.ProcessString(L"\x1b[?5h");
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);
pDispatch->ClearState();
pDispatch->_isScreenModeReversed = true;
mach.ProcessString(L"\x1b[?5l");
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);
pDispatch->ClearState();
}
TEST_METHOD(TestOriginMode)
{
auto dispatch = std::make_unique<StatefulDispatch>();

View File

@@ -0,0 +1,31 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
// Routine Description:
// - Retrieves the string resource from the current module with the given ID
// from the resources files. See resource.h and the .rc definitions for valid
// IDs.
// Arguments:
// - id - Resource ID
// Return Value:
// - String resource retrieved from that ID.
// NOTE: `__declspec(noinline) inline`: make one AND ONLY ONE copy of this
// function, and don't actually inline it
__declspec(noinline) inline std::wstring GetStringResource(const UINT id)
{
// Calling LoadStringW with a pointer-sized storage and no length will
// return a read-only pointer directly to the resource data instead of
// copying it immediately into a buffer.
LPWSTR readOnlyResource = nullptr;
const auto length = LoadStringW(wil::GetModuleInstanceHandle(),
id,
reinterpret_cast<LPWSTR>(&readOnlyResource),
0);
LOG_LAST_ERROR_IF(length == 0);
// However, the pointer and length given are NOT guaranteed to be
// zero-terminated and most uses of this data will probably want a
// zero-terminated string. So we're going to construct and return a
// std::wstring copy from the pointer/length since those are certainly
// zero-terminated.
return { readOnlyResource, gsl::narrow<size_t>(length) };
}