Add support for commandline args to wt.exe (#4023)

## Summary of the Pull Request

Adds support for commandline arguments to the Windows Terminal, in accordance with the spec in #3495

## References

* Original issue: #607
* Original spec: #3495

## PR Checklist
* [x] Closes #607
* [x] I work here
* [x] Tests added/passed
* [ ] We should probably add some docs on these commands
* [x] The spec (#3495) needs to be merged first!

## Detailed Description of the Pull Request / Additional comments

🛑 **STOP** 🛑 - have you read #3495 yet? If you haven't, go do that now.

This PR adds support for three initial sub-commands to the `wt.exe` application:
* `new-tab`: Used to create a new tab.
* `split-pane`: Used to create a new split.
* `focus-tab`: Moves focus to another tab.

These commands are largely POC to prove that the commandlines work. They're not totally finished, but they work well enough. Follow up work items will be filed to track adding support for additional parameters and subcommands

Important scenarios added:
* `wt -d .`: Open a new wt instance in the current working directory #878
* `wt -p <profile name>`: Create a wt instance running the given profile, to unblock  #576, #1357, #2339
* `wt ; new-tab ; split-pane -V`: Launch the terminal with multiple tabs, splits, to unblock #756 

## Validation Steps Performed

* Ran tests
* Played with it a bunch
This commit is contained in:
Mike Griese 2020-01-27 09:34:12 -06:00 committed by msftbot[bot]
parent f0e6037570
commit 830c22b73e
30 changed files with 10089 additions and 53 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

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

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

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:
@ -868,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();
}
@ -1431,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.
@ -1476,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

@ -761,6 +761,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
_connection.Start();
_initializedTerminal = true;
_InitializedHandlers(*this, nullptr);
return true;
}

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

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

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