mirror of
https://github.com/microsoft/terminal.git
synced 2026-05-18 01:39:55 +00:00
Merge remote-tracking branch 'origin/master' into dev/migrie/b/3490-resizing-but-no-invalidateall-ing
This commit is contained in:
7821
dep/CLI11/CLI11.hpp
Normal file
7821
dep/CLI11/CLI11.hpp
Normal file
File diff suppressed because it is too large
Load Diff
5
dep/CLI11/README.md
Normal file
5
dep/CLI11/README.md
Normal 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)
|
||||
|
||||
@@ -291,7 +291,7 @@
|
||||
"description": "The number of rows to scroll at a time with the mouse wheel. This will override the system setting if the value is not zero or 'system'.",
|
||||
"maximum": 999,
|
||||
"minimum": 0,
|
||||
"type": "integer"
|
||||
"type": ["integer", "string"]
|
||||
},
|
||||
"keybindings": {
|
||||
"description": "Properties are specific to each custom key binding.",
|
||||
|
||||
156
doc/user-docs/UsingCommandlineArguments.md
Normal file
156
doc/user-docs/UsingCommandlineArguments.md
Normal 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
|
||||
991
src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
Normal file
991
src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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. -->
|
||||
|
||||
@@ -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>
|
||||
|
||||
609
src/cascadia/TerminalApp/AppCommandlineArgs.cpp
Normal file
609
src/cascadia/TerminalApp/AppCommandlineArgs.cpp
Normal 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);
|
||||
}
|
||||
}
|
||||
97
src/cascadia/TerminalApp/AppCommandlineArgs.h
Normal file
97
src/cascadia/TerminalApp/AppCommandlineArgs.h
Normal 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
|
||||
};
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
39
src/cascadia/TerminalApp/Commandline.cpp
Normal file
39
src/cascadia/TerminalApp/Commandline.cpp
Normal 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));
|
||||
}
|
||||
46
src/cascadia/TerminalApp/Commandline.h
Normal file
46
src/cascadia/TerminalApp/Commandline.h
Normal 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;
|
||||
};
|
||||
@@ -358,6 +358,25 @@ void Pane::Close()
|
||||
_ClosedHandlers(nullptr, nullptr);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prepare this pane to be removed from the UI hierarchy by closing all controls
|
||||
// and connections beneath it.
|
||||
void Pane::Shutdown()
|
||||
{
|
||||
// Lock the create/close lock so that another operation won't concurrently
|
||||
// modify our tree
|
||||
std::unique_lock lock{ _createCloseLock };
|
||||
if (_IsLeaf())
|
||||
{
|
||||
_control.Close();
|
||||
}
|
||||
else
|
||||
{
|
||||
_firstChild->Shutdown();
|
||||
_secondChild->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Get the root UIElement of this pane. There may be a single TermControl as a
|
||||
// child, or an entire tree of grids and panes as children of this element.
|
||||
|
||||
@@ -64,6 +64,7 @@ public:
|
||||
const winrt::Microsoft::Terminal::TerminalControl::TermControl& control);
|
||||
float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const;
|
||||
|
||||
void Shutdown();
|
||||
void Close();
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
|
||||
@@ -240,7 +240,7 @@ bool Tab::CanSplitPane(winrt::TerminalApp::SplitState splitType)
|
||||
void Tab::SplitPane(winrt::TerminalApp::SplitState splitType, const GUID& profile, TermControl& control)
|
||||
{
|
||||
auto [first, second] = _activePane->Split(splitType, profile, control);
|
||||
|
||||
_activePane = first;
|
||||
_AttachEventHandlersToControl(control);
|
||||
|
||||
// Add a event handlers to the new panes' GotFocus event. When the pane
|
||||
@@ -298,6 +298,13 @@ void Tab::NavigateFocus(const winrt::TerminalApp::Direction& direction)
|
||||
_rootPane->NavigateFocus(direction);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prepares this tab for being removed from the UI hierarchy by shutting down all active connections.
|
||||
void Tab::Shutdown()
|
||||
{
|
||||
_rootPane->Shutdown();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Closes the currently focused pane in this tab. If it's the last pane in
|
||||
// this tab, our Closed event will be fired (at a later time) for anyone
|
||||
|
||||
@@ -37,6 +37,7 @@ public:
|
||||
winrt::hstring GetActiveTitle() const;
|
||||
winrt::fire_and_forget SetTabText(const winrt::hstring text);
|
||||
|
||||
void Shutdown();
|
||||
void ClosePane();
|
||||
|
||||
WINRT_CALLBACK(Closed, winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>);
|
||||
|
||||
@@ -124,10 +124,57 @@ namespace winrt::TerminalApp::implementation
|
||||
_tabView.TabItemsChanged({ this, &TerminalPage::_OnTabItemsChanged });
|
||||
|
||||
_CreateNewTabFlyout();
|
||||
|
||||
_UpdateTabWidthMode();
|
||||
_OpenNewTab(nullptr);
|
||||
|
||||
_tabContent.SizeChanged({ this, &TerminalPage::_OnContentSizeChanged });
|
||||
|
||||
// Actually start the terminal.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
_OpenNewTab(nullptr);
|
||||
}
|
||||
else
|
||||
{
|
||||
_appArgs.ValidateStartupCommands();
|
||||
|
||||
// This will kick off a chain of events to perform each startup
|
||||
// action. As each startup action is completed, the next will be
|
||||
// fired.
|
||||
_ProcessNextStartupAction();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Process the next startup action in our list of startup actions. When
|
||||
// that action is complete, fire the next (if there are any more).
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - <none>
|
||||
fire_and_forget TerminalPage::_ProcessNextStartupAction()
|
||||
{
|
||||
// If there are no actions left, do nothing.
|
||||
if (_appArgs.GetStartupActions().empty())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the next action to be processed
|
||||
auto nextAction = _appArgs.GetStartupActions().front();
|
||||
_appArgs.GetStartupActions().pop_front();
|
||||
|
||||
auto weakThis{ get_weak() };
|
||||
|
||||
// Handle it on the UI thread.
|
||||
co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Low);
|
||||
if (auto page{ weakThis.get() })
|
||||
{
|
||||
page->_actionDispatch->DoAction(nextAction);
|
||||
|
||||
// Kick off the next action to be handled (if necessary)
|
||||
page->_ProcessNextStartupAction();
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -441,6 +488,7 @@ namespace winrt::TerminalApp::implementation
|
||||
// - settings: the TerminalSettings object to use to create the TerminalControl with.
|
||||
void TerminalPage::_CreateNewTabFromSettings(GUID profileGuid, TerminalSettings settings)
|
||||
{
|
||||
const bool isFirstTab = _tabs.empty();
|
||||
// Initialize the new tab
|
||||
|
||||
// Create a connection based on the values in our settings object.
|
||||
@@ -494,12 +542,20 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
});
|
||||
|
||||
// This is one way to set the tab's selected background color.
|
||||
// tabViewItem.Resources().Insert(winrt::box_value(L"TabViewItemHeaderBackgroundSelected"), a Brush?);
|
||||
|
||||
// This kicks off TabView::SelectionChanged, in response to which we'll attach the terminal's
|
||||
// Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
// If this is the first tab, we don't need to kick off the event to get
|
||||
// the tab's content added to the root of the page. just do it
|
||||
// immediately.
|
||||
if (isFirstTab)
|
||||
{
|
||||
_tabContent.Children().Clear();
|
||||
_tabContent.Children().Append(newTab->GetRootElement());
|
||||
}
|
||||
else
|
||||
{
|
||||
// This kicks off TabView::SelectionChanged, in response to which
|
||||
// we'll attach the terminal's Xaml control to the Xaml root.
|
||||
_tabView.SelectedItem(tabViewItem);
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -753,16 +809,20 @@ namespace winrt::TerminalApp::implementation
|
||||
// - tabIndex: the index of the tab to be removed
|
||||
void TerminalPage::_RemoveTabViewItemByIndex(uint32_t tabIndex)
|
||||
{
|
||||
// Removing the tab from the collection should destroy its control and disconnect its connection,
|
||||
// but it doesn't always do so. The UI tree may still be holding the control and preventing its destruction.
|
||||
auto iterator = _tabs.begin() + tabIndex;
|
||||
(*iterator)->Shutdown();
|
||||
|
||||
_tabs.erase(iterator);
|
||||
_tabView.TabItems().RemoveAt(tabIndex);
|
||||
|
||||
// To close the window here, we need to close the hosting window.
|
||||
if (_tabs.size() == 1)
|
||||
if (_tabs.size() == 0)
|
||||
{
|
||||
_lastTabClosedHandlers(*this, nullptr);
|
||||
}
|
||||
|
||||
// Removing the tab from the collection will destroy its control and disconnect its connection.
|
||||
_tabs.erase(_tabs.begin() + tabIndex);
|
||||
_tabView.TabItems().RemoveAt(tabIndex);
|
||||
|
||||
auto focusedTabIndex = _GetFocusedTabIndex();
|
||||
if (gsl::narrow_cast<int>(tabIndex) == focusedTabIndex)
|
||||
{
|
||||
@@ -864,7 +924,7 @@ namespace winrt::TerminalApp::implementation
|
||||
winrt::Microsoft::Terminal::TerminalControl::TermControl TerminalPage::_GetActiveControl()
|
||||
{
|
||||
int focusedTabIndex = _GetFocusedTabIndex();
|
||||
auto focusedTab = _tabs[focusedTabIndex];
|
||||
auto focusedTab = _tabs.at(focusedTabIndex);
|
||||
return focusedTab->GetActiveTerminalControl();
|
||||
}
|
||||
|
||||
@@ -1427,6 +1487,72 @@ namespace winrt::TerminalApp::implementation
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Sets the initial commandline to process on startup, and attempts to
|
||||
// parse it. Commands will be parsed into a list of ShortcutActions that
|
||||
// will be processed on TerminalPage::Create().
|
||||
// - This function will have no effective result after Create() is called.
|
||||
// - This function returns 0, unless a there was a non-zero result from
|
||||
// trying to parse one of the commands provided. In that case, no commands
|
||||
// after the failing command will be parsed, and the non-zero code
|
||||
// returned.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - the result of the first command who's parsing returned a non-zero code,
|
||||
// or 0. (see TerminalPage::_ParseArgs)
|
||||
int32_t TerminalPage::SetStartupCommandline(winrt::array_view<const hstring> args)
|
||||
{
|
||||
return _ParseArgs(args);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Attempts to parse an array of commandline args into a list of
|
||||
// commands to execute, and then parses these commands. As commands are
|
||||
// succesfully parsed, they will generate ShortcutActions for us to be
|
||||
// able to execute. If we fail to parse any commands, we'll return the
|
||||
// error code from the failure to parse that command, and stop processing
|
||||
// additional commands.
|
||||
// Arguments:
|
||||
// - args: an array of strings to process as a commandline. These args can contain spaces
|
||||
// Return Value:
|
||||
// - 0 if the commandline was successfully parsed
|
||||
int TerminalPage::_ParseArgs(winrt::array_view<const hstring>& args)
|
||||
{
|
||||
auto commands = ::TerminalApp::AppCommandlineArgs::BuildCommands(args);
|
||||
|
||||
for (auto& cmdBlob : commands)
|
||||
{
|
||||
// On one hand, it seems like we should be able to have one
|
||||
// AppCommandlineArgs for parsing all of them, and collect the
|
||||
// results one at a time.
|
||||
//
|
||||
// On the other hand, re-using a CLI::App seems to leave state from
|
||||
// previous parsings around, so we could get mysterious behavior
|
||||
// where one command affects the values of the next.
|
||||
//
|
||||
// From https://cliutils.github.io/CLI11/book/chapters/options.html:
|
||||
// > If that option is not given, CLI11 will not touch the initial
|
||||
// > value. This allows you to set up defaults by simply setting
|
||||
// > your value beforehand.
|
||||
//
|
||||
// So we pretty much need the to either manually reset the state
|
||||
// each command, or build new ones.
|
||||
const auto result = _appArgs.ParseCommand(cmdBlob);
|
||||
|
||||
// If this succeeded, result will be 0. Otherwise, the caller should
|
||||
// exit(result), to exit the program.
|
||||
if (result != 0)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// If all the args were successfully parsed, we'll have some commands
|
||||
// built in _appArgs, which we'll use when the application starts up.
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - This is the method that App will call when the titlebar
|
||||
// has been clicked. It dismisses any open flyouts.
|
||||
@@ -1472,6 +1598,22 @@ namespace winrt::TerminalApp::implementation
|
||||
_UpdateTabView();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - If there were any errors parsing the commandline that was used to
|
||||
// initialize the terminal, this will return a string containing that
|
||||
// message. If there were no errors, this message will be blank.
|
||||
// - If the user requested help on any command (using --help), this will
|
||||
// contain the help message.
|
||||
// Arguments:
|
||||
// - <none>
|
||||
// Return Value:
|
||||
// - the help text or error message for the providied commandline, if one
|
||||
// exists, otherwise the empty string.
|
||||
winrt::hstring TerminalPage::EarlyExitMessage()
|
||||
{
|
||||
return winrt::to_hstring(_appArgs.GetExitMessage());
|
||||
}
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
// Winrt events need a method for adding a callback to the event and removing the callback.
|
||||
// These macros will define them both for you.
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -59,23 +59,32 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
// set the input scope to Text because this control is for any text.
|
||||
_editContext.InputScope(Core::CoreTextInputScope::Text);
|
||||
|
||||
_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });
|
||||
_textRequestedRevoker = _editContext.TextRequested(winrt::auto_revoke, { this, &TSFInputControl::_textRequestedHandler });
|
||||
|
||||
_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });
|
||||
_selectionRequestedRevoker = _editContext.SelectionRequested(winrt::auto_revoke, { this, &TSFInputControl::_selectionRequestedHandler });
|
||||
|
||||
_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });
|
||||
_focusRemovedRevoker = _editContext.FocusRemoved(winrt::auto_revoke, { this, &TSFInputControl::_focusRemovedHandler });
|
||||
|
||||
_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });
|
||||
_textUpdatingRevoker = _editContext.TextUpdating(winrt::auto_revoke, { this, &TSFInputControl::_textUpdatingHandler });
|
||||
|
||||
_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });
|
||||
_selectionUpdatingRevoker = _editContext.SelectionUpdating(winrt::auto_revoke, { this, &TSFInputControl::_selectionUpdatingHandler });
|
||||
|
||||
_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });
|
||||
_formatUpdatingRevoker = _editContext.FormatUpdating(winrt::auto_revoke, { this, &TSFInputControl::_formatUpdatingHandler });
|
||||
|
||||
_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });
|
||||
_layoutRequestedRevoker = _editContext.LayoutRequested(winrt::auto_revoke, { this, &TSFInputControl::_layoutRequestedHandler });
|
||||
|
||||
_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });
|
||||
_compositionStartedRevoker = _editContext.CompositionStarted(winrt::auto_revoke, { this, &TSFInputControl::_compositionStartedHandler });
|
||||
|
||||
_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
|
||||
_compositionCompletedRevoker = _editContext.CompositionCompleted(winrt::auto_revoke, { this, &TSFInputControl::_compositionCompletedHandler });
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Prepares this TSFInputControl to be removed from the UI hierarchy.
|
||||
void TSFInputControl::Close()
|
||||
{
|
||||
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
|
||||
// See GH#4159 for more info.
|
||||
_layoutRequestedRevoker.revoke();
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void NotifyFocusEnter();
|
||||
void NotifyFocusLeave();
|
||||
|
||||
void Close();
|
||||
|
||||
static void OnCompositionChanged(Windows::UI::Xaml::DependencyObject const&, Windows::UI::Xaml::DependencyPropertyChangedEventArgs const&);
|
||||
|
||||
// -------------------------------- WinRT Events ---------------------------------
|
||||
@@ -55,6 +57,16 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
void _textUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextTextUpdatingEventArgs const& args);
|
||||
void _formatUpdatingHandler(winrt::Windows::UI::Text::Core::CoreTextEditContext sender, winrt::Windows::UI::Text::Core::CoreTextFormatUpdatingEventArgs const& args);
|
||||
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextRequested_revoker _textRequestedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionRequested_revoker _selectionRequestedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::FocusRemoved_revoker _focusRemovedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::TextUpdating_revoker _textUpdatingRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::SelectionUpdating_revoker _selectionUpdatingRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::FormatUpdating_revoker _formatUpdatingRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::LayoutRequested_revoker _layoutRequestedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionStarted_revoker _compositionStartedRevoker;
|
||||
winrt::Windows::UI::Text::Core::CoreTextEditContext::CompositionCompleted_revoker _compositionCompletedRevoker;
|
||||
|
||||
Windows::UI::Xaml::Controls::Canvas _canvas;
|
||||
Windows::UI::Xaml::Controls::TextBlock _textBlock;
|
||||
|
||||
|
||||
@@ -27,5 +27,7 @@ namespace Microsoft.Terminal.TerminalControl
|
||||
|
||||
void NotifyFocusEnter();
|
||||
void NotifyFocusLeave();
|
||||
|
||||
void Close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -761,6 +761,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
|
||||
_connection.Start();
|
||||
_initializedTerminal = true;
|
||||
_InitializedHandlers(*this, nullptr);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1734,6 +1735,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
|
||||
_connection.TerminalOutput(_connectionOutputEventToken);
|
||||
_connectionStateChangedRevoker.revoke();
|
||||
|
||||
_tsfInputControl.Close(); // Disconnect the TSF input control so it doesn't receive EditContext events.
|
||||
|
||||
if (auto localConnection{ std::exchange(_connection, nullptr) })
|
||||
{
|
||||
localConnection.Close();
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1299,6 +1299,35 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - A private API call for changing the screen mode between normal and reverse.
|
||||
// When in reverse screen mode, the background and foreground colors are switched.
|
||||
// Parameters:
|
||||
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
|
||||
// Return value:
|
||||
// - STATUS_SUCCESS if handled successfully. Otherwise, an appropriate error code.
|
||||
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode)
|
||||
{
|
||||
try
|
||||
{
|
||||
Globals& g = ServiceLocator::LocateGlobals();
|
||||
CONSOLE_INFORMATION& gci = g.getConsoleInformation();
|
||||
|
||||
gci.SetScreenReversed(reverseMode);
|
||||
|
||||
if (g.pRender)
|
||||
{
|
||||
g.pRender->TriggerRedrawAll();
|
||||
}
|
||||
|
||||
return STATUS_SUCCESS;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
return NTSTATUS_FROM_HRESULT(wil::ResultFromCaughtException());
|
||||
}
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - A private API call for making the cursor visible or not. Does not modify
|
||||
// blinking state.
|
||||
|
||||
@@ -29,6 +29,8 @@ void DoSrvPrivateSetDefaultAttributes(SCREEN_INFORMATION& screenInfo, const bool
|
||||
[[nodiscard]] NTSTATUS DoSrvPrivateSetCursorKeysMode(_In_ bool fApplicationMode);
|
||||
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode);
|
||||
|
||||
[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode);
|
||||
|
||||
void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noexcept;
|
||||
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable);
|
||||
|
||||
|
||||
@@ -345,6 +345,19 @@ bool ConhostInternalGetSet::PrivateSetKeypadMode(const bool fApplicationMode)
|
||||
return NT_SUCCESS(DoSrvPrivateSetKeypadMode(fApplicationMode));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Connects the PrivateSetScreenMode call directly into our Driver Message servicing call inside Conhost.exe
|
||||
// PrivateSetScreenMode is an internal-only "API" call that the vt commands can execute,
|
||||
// but it is not represented as a function call on our public API surface.
|
||||
// Arguments:
|
||||
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
|
||||
// Return Value:
|
||||
// - true if successful (see DoSrvPrivateSetScreenMode). false otherwise.
|
||||
bool ConhostInternalGetSet::PrivateSetScreenMode(const bool reverseMode)
|
||||
{
|
||||
return NT_SUCCESS(DoSrvPrivateSetScreenMode(reverseMode));
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - Connects the PrivateShowCursor call directly into our Driver Message servicing call inside Conhost.exe
|
||||
// PrivateShowCursor is an internal-only "API" call that the vt commands can execute,
|
||||
|
||||
@@ -93,6 +93,8 @@ public:
|
||||
bool PrivateSetCursorKeysMode(const bool applicationMode) override;
|
||||
bool PrivateSetKeypadMode(const bool applicationMode) override;
|
||||
|
||||
bool PrivateSetScreenMode(const bool reverseMode) override;
|
||||
|
||||
bool PrivateShowCursor(const bool show) noexcept override;
|
||||
bool PrivateAllowCursorBlinking(const bool enable) override;
|
||||
|
||||
|
||||
@@ -53,6 +53,7 @@ Settings::Settings() :
|
||||
_fUseWindowSizePixels(false),
|
||||
_fAutoReturnOnNewline(true), // the historic Windows behavior defaults this to on.
|
||||
_fRenderGridWorldwide(false), // historically grid lines were only rendered in DBCS codepages, so this is false by default unless otherwise specified.
|
||||
_fScreenReversed(false),
|
||||
// window size pixels initialized below
|
||||
_fInterceptCopyPaste(0),
|
||||
_DefaultForeground(INVALID_COLOR),
|
||||
@@ -394,6 +395,15 @@ void Settings::SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::IsScreenReversed() const
|
||||
{
|
||||
return _fScreenReversed;
|
||||
}
|
||||
void Settings::SetScreenReversed(const bool fScreenReversed)
|
||||
{
|
||||
_fScreenReversed = fScreenReversed;
|
||||
}
|
||||
|
||||
bool Settings::GetFilterOnPaste() const
|
||||
{
|
||||
return _fFilterOnPaste;
|
||||
@@ -942,7 +952,14 @@ COLORREF Settings::CalculateDefaultBackground() const noexcept
|
||||
COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexcept
|
||||
{
|
||||
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
|
||||
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
if (_fScreenReversed)
|
||||
{
|
||||
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
}
|
||||
else
|
||||
{
|
||||
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
}
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
@@ -955,7 +972,14 @@ COLORREF Settings::LookupForegroundColor(const TextAttribute& attr) const noexce
|
||||
COLORREF Settings::LookupBackgroundColor(const TextAttribute& attr) const noexcept
|
||||
{
|
||||
const auto tableView = std::basic_string_view<COLORREF>(&GetColorTable()[0], GetColorTableSize());
|
||||
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
if (_fScreenReversed)
|
||||
{
|
||||
return attr.CalculateRgbForeground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
}
|
||||
else
|
||||
{
|
||||
return attr.CalculateRgbBackground(tableView, CalculateDefaultForeground(), CalculateDefaultBackground());
|
||||
}
|
||||
}
|
||||
|
||||
bool Settings::GetCopyColor() const noexcept
|
||||
|
||||
@@ -52,6 +52,9 @@ public:
|
||||
bool IsGridRenderingAllowedWorldwide() const;
|
||||
void SetGridRenderingAllowedWorldwide(const bool fGridRenderingAllowed);
|
||||
|
||||
bool IsScreenReversed() const;
|
||||
void SetScreenReversed(const bool fScreenReversed);
|
||||
|
||||
bool GetFilterOnPaste() const;
|
||||
void SetFilterOnPaste(const bool fFilterOnPaste);
|
||||
|
||||
@@ -231,6 +234,7 @@ private:
|
||||
DWORD _dwVirtTermLevel;
|
||||
bool _fAutoReturnOnNewline;
|
||||
bool _fRenderGridWorldwide;
|
||||
bool _fScreenReversed;
|
||||
bool _fUseDx;
|
||||
bool _fCopyColor;
|
||||
|
||||
|
||||
@@ -182,6 +182,7 @@ class ScreenBufferTests
|
||||
|
||||
TEST_METHOD(ScrollLines256Colors);
|
||||
|
||||
TEST_METHOD(SetScreenMode);
|
||||
TEST_METHOD(SetOriginMode);
|
||||
|
||||
TEST_METHOD(HardResetBuffer);
|
||||
@@ -4501,6 +4502,34 @@ void ScreenBufferTests::ScrollLines256Colors()
|
||||
}
|
||||
}
|
||||
|
||||
void ScreenBufferTests::SetScreenMode()
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
auto& si = gci.GetActiveOutputBuffer();
|
||||
auto& stateMachine = si.GetStateMachine();
|
||||
|
||||
const auto rgbForeground = RGB(12, 34, 56);
|
||||
const auto rgbBackground = RGB(78, 90, 12);
|
||||
const auto testAttr = TextAttribute{ rgbForeground, rgbBackground };
|
||||
|
||||
Log::Comment(L"By default the screen mode is normal.");
|
||||
VERIFY_IS_FALSE(gci.IsScreenReversed());
|
||||
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
|
||||
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));
|
||||
|
||||
Log::Comment(L"When DECSCNM is set, background and foreground colors are switched.");
|
||||
stateMachine.ProcessString(L"\x1B[?5h");
|
||||
VERIFY_IS_TRUE(gci.IsScreenReversed());
|
||||
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupForegroundColor(testAttr));
|
||||
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupBackgroundColor(testAttr));
|
||||
|
||||
Log::Comment(L"When DECSCNM is reset, the colors are normal again.");
|
||||
stateMachine.ProcessString(L"\x1B[?5l");
|
||||
VERIFY_IS_FALSE(gci.IsScreenReversed());
|
||||
VERIFY_ARE_EQUAL(rgbForeground, gci.LookupForegroundColor(testAttr));
|
||||
VERIFY_ARE_EQUAL(rgbBackground, gci.LookupBackgroundColor(testAttr));
|
||||
}
|
||||
|
||||
void ScreenBufferTests::SetOriginMode()
|
||||
{
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
|
||||
@@ -46,6 +46,7 @@
|
||||
#include <functional>
|
||||
#include <set>
|
||||
#include <unordered_set>
|
||||
#include <regex>
|
||||
|
||||
// WIL
|
||||
#include <wil/Common.h>
|
||||
|
||||
@@ -82,6 +82,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
|
||||
{
|
||||
DECCKM_CursorKeysMode = 1,
|
||||
DECCOLM_SetNumberOfColumns = 3,
|
||||
DECSCNM_ScreenMode = 5,
|
||||
DECOM_OriginMode = 6,
|
||||
ATT610_StartCursorBlink = 12,
|
||||
DECTCEM_TextCursorEnableMode = 25,
|
||||
|
||||
@@ -54,6 +54,7 @@ public:
|
||||
virtual bool SetCursorKeysMode(const bool applicationMode) = 0; // DECCKM
|
||||
virtual bool SetKeypadMode(const bool applicationMode) = 0; // DECKPAM, DECKPNM
|
||||
virtual bool EnableCursorBlinking(const bool enable) = 0; // ATT610
|
||||
virtual bool SetScreenMode(const bool reverseMode) = 0; //DECSCNM
|
||||
virtual bool SetOriginMode(const bool relativeMode) = 0; // DECOM
|
||||
virtual bool SetTopBottomScrollingMargins(const size_t topMargin, const size_t bottomMargin) = 0; // DECSTBM
|
||||
virtual bool WarningBell() = 0; // BEL
|
||||
|
||||
@@ -933,6 +933,9 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar
|
||||
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
|
||||
success = _DoDECCOLMHelper(enable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
|
||||
success = SetScreenMode(enable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
|
||||
// The cursor is also moved to the new home position when the origin mode is set or reset.
|
||||
success = SetOriginMode(enable) && CursorPosition(1, 1);
|
||||
@@ -1079,6 +1082,18 @@ bool AdaptDispatch::DeleteLine(const size_t distance)
|
||||
return _pConApi->DeleteLines(distance);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECSCNM - Sets the screen mode to either normal or reverse.
|
||||
// When in reverse screen mode, the background and foreground colors are switched.
|
||||
// Arguments:
|
||||
// - reverseMode - set to true to enable reverse screen mode, false for normal mode.
|
||||
// Return Value:
|
||||
// - True if handled successfully. False otherwise.
|
||||
bool AdaptDispatch::SetScreenMode(const bool reverseMode)
|
||||
{
|
||||
return _pConApi->PrivateSetScreenMode(reverseMode);
|
||||
}
|
||||
|
||||
// Routine Description:
|
||||
// - DECOM - Sets the cursor addressing origin mode to relative or absolute.
|
||||
// When relative, line numbers start at top margin of the user-defined scrolling region.
|
||||
@@ -1475,6 +1490,12 @@ bool AdaptDispatch::HardReset()
|
||||
success = _EraseScrollback();
|
||||
}
|
||||
|
||||
// Set the DECSCNM screen mode back to normal.
|
||||
if (success)
|
||||
{
|
||||
success = SetScreenMode(false);
|
||||
}
|
||||
|
||||
// Cursor to 1,1 - the Soft Reset guarantees this is absolute
|
||||
if (success)
|
||||
{
|
||||
|
||||
@@ -68,6 +68,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool SetCursorKeysMode(const bool applicationMode) override; // DECCKM
|
||||
bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM
|
||||
bool EnableCursorBlinking(const bool enable) override; // ATT610
|
||||
bool SetScreenMode(const bool reverseMode) override; //DECSCNM
|
||||
bool SetOriginMode(const bool relativeMode) noexcept override; // DECOM
|
||||
bool SetTopBottomScrollingMargins(const size_t topMargin,
|
||||
const size_t bottomMargin) override; // DECSTBM
|
||||
|
||||
@@ -57,6 +57,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
virtual bool PrivateSetCursorKeysMode(const bool applicationMode) = 0;
|
||||
virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0;
|
||||
|
||||
virtual bool PrivateSetScreenMode(const bool reverseMode) = 0;
|
||||
|
||||
virtual bool PrivateShowCursor(const bool show) = 0;
|
||||
virtual bool PrivateAllowCursorBlinking(const bool enable) = 0;
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@ public:
|
||||
bool SetCursorKeysMode(const bool /*applicationMode*/) noexcept override { return false; } // DECCKM
|
||||
bool SetKeypadMode(const bool /*applicationMode*/) noexcept override { return false; } // DECKPAM, DECKPNM
|
||||
bool EnableCursorBlinking(const bool /*enable*/) noexcept override { return false; } // ATT610
|
||||
bool SetScreenMode(const bool /*reverseMode*/) noexcept override { return false; } //DECSCNM
|
||||
bool SetOriginMode(const bool /*relativeMode*/) noexcept override { return false; }; // DECOM
|
||||
bool SetTopBottomScrollingMargins(const size_t /*topMargin*/, const size_t /*bottomMargin*/) noexcept override { return false; } // DECSTBM
|
||||
bool WarningBell() noexcept override { return false; } // BEL
|
||||
|
||||
@@ -162,6 +162,13 @@ public:
|
||||
return _privateSetKeypadModeResult;
|
||||
}
|
||||
|
||||
bool PrivateSetScreenMode(const bool /*reverseMode*/) override
|
||||
{
|
||||
Log::Comment(L"PrivateSetScreenMode MOCK called...");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool PrivateShowCursor(const bool show) override
|
||||
{
|
||||
Log::Comment(L"PrivateShowCursor MOCK called...");
|
||||
|
||||
@@ -678,6 +678,7 @@ public:
|
||||
_isAltBuffer{ false },
|
||||
_cursorKeysMode{ false },
|
||||
_cursorBlinking{ true },
|
||||
_isScreenModeReversed{ false },
|
||||
_isOriginModeRelative{ false },
|
||||
_warningBell{ false },
|
||||
_carriageReturn{ false },
|
||||
@@ -857,6 +858,9 @@ public:
|
||||
case DispatchTypes::PrivateModeParams::DECCOLM_SetNumberOfColumns:
|
||||
fSuccess = SetColumns(static_cast<size_t>(fEnable ? DispatchTypes::s_sDECCOLMSetColumns : DispatchTypes::s_sDECCOLMResetColumns));
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECSCNM_ScreenMode:
|
||||
fSuccess = SetScreenMode(fEnable);
|
||||
break;
|
||||
case DispatchTypes::PrivateModeParams::DECOM_OriginMode:
|
||||
// The cursor is also moved to the new home position when the origin mode is set or reset.
|
||||
fSuccess = SetOriginMode(fEnable) && CursorPosition(1, 1);
|
||||
@@ -920,6 +924,12 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetScreenMode(const bool reverseMode) noexcept override
|
||||
{
|
||||
_isScreenModeReversed = reverseMode;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetOriginMode(const bool fRelativeMode) noexcept override
|
||||
{
|
||||
_isOriginModeRelative = fRelativeMode;
|
||||
@@ -999,6 +1009,7 @@ public:
|
||||
bool _isAltBuffer;
|
||||
bool _cursorKeysMode;
|
||||
bool _cursorBlinking;
|
||||
bool _isScreenModeReversed;
|
||||
bool _isOriginModeRelative;
|
||||
bool _warningBell;
|
||||
bool _carriageReturn;
|
||||
@@ -1290,6 +1301,25 @@ class StateMachineExternalTest final
|
||||
pDispatch->ClearState();
|
||||
}
|
||||
|
||||
TEST_METHOD(TestScreenMode)
|
||||
{
|
||||
auto dispatch = std::make_unique<StatefulDispatch>();
|
||||
auto pDispatch = dispatch.get();
|
||||
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
|
||||
StateMachine mach(std::move(engine));
|
||||
|
||||
mach.ProcessString(L"\x1b[?5h");
|
||||
VERIFY_IS_TRUE(pDispatch->_isScreenModeReversed);
|
||||
|
||||
pDispatch->ClearState();
|
||||
pDispatch->_isScreenModeReversed = true;
|
||||
|
||||
mach.ProcessString(L"\x1b[?5l");
|
||||
VERIFY_IS_FALSE(pDispatch->_isScreenModeReversed);
|
||||
|
||||
pDispatch->ClearState();
|
||||
}
|
||||
|
||||
TEST_METHOD(TestOriginMode)
|
||||
{
|
||||
auto dispatch = std::make_unique<StatefulDispatch>();
|
||||
|
||||
31
src/types/inc/User32Utils.hpp
Normal file
31
src/types/inc/User32Utils.hpp
Normal 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) };
|
||||
}
|
||||
Reference in New Issue
Block a user