Skip to content

Commit 817df81

Browse files
Don-VitoDHowett
authored andcommitted
Fix combining wt args and "wt new-tab" args in implicit context (#8315)
Currently when implicit tab command is specified (i.e., we have parameters for new-tab, but don't have the explicit subcommand name) we fallback to parsing the entire arg list as new tab command. However, if we also have a launch profile (or anything else that might in the future belong to the upper scope) it is passed as a parameter to the new tab command, failing the parsing. The idea behind my solution is to run the parser as a prefix command - i.e., as long as we succeed to parse [options] / [subcommand] we will parse them (populating the fields like a launch mode), but once we will discover something unfamiliar, like profile, we will know that the prefix is over and will handle the remaining suffix as a new tab command. ## Validation Steps Performed * UT added * Manual run of different options Closes #7318 (cherry picked from commit 435e457)
1 parent a388adf commit 817df81

File tree

2 files changed

+74
-33
lines changed

2 files changed

+74
-33
lines changed

src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ namespace TerminalAppLocalTests
5858
TEST_METHOD(TestMultipleCommandExecuteCommandlineAction);
5959
TEST_METHOD(TestInvalidExecuteCommandlineAction);
6060
TEST_METHOD(TestLaunchMode);
61+
TEST_METHOD(TestLaunchModeWithNoCommand);
6162

6263
private:
6364
void _buildCommandlinesHelper(AppCommandlineArgs& appArgs,
@@ -1224,4 +1225,56 @@ namespace TerminalAppLocalTests
12241225
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedFocusMode);
12251226
}
12261227
}
1228+
1229+
void CommandlineTest::TestLaunchModeWithNoCommand()
1230+
{
1231+
{
1232+
Log::Comment(NoThrowString().Format(L"Pass a launch mode and profile"));
1233+
1234+
AppCommandlineArgs appArgs{};
1235+
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-M", L"--profile", L"cmd" };
1236+
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
1237+
1238+
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
1239+
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedMode);
1240+
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
1241+
1242+
auto actionAndArgs = appArgs._startupActions.at(0);
1243+
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
1244+
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
1245+
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
1246+
VERIFY_IS_NOT_NULL(myArgs);
1247+
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
1248+
VERIFY_IS_TRUE(myArgs.TerminalArgs().Commandline().empty());
1249+
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
1250+
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
1251+
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
1252+
VERIFY_IS_FALSE(myArgs.TerminalArgs().Profile().empty());
1253+
VERIFY_ARE_EQUAL(L"cmd", myArgs.TerminalArgs().Profile());
1254+
}
1255+
{
1256+
Log::Comment(NoThrowString().Format(L"Pass a launch mode and command line"));
1257+
1258+
AppCommandlineArgs appArgs{};
1259+
std::vector<const wchar_t*> rawCommands{ L"wt.exe", L"-M", L"powershell.exe" };
1260+
_buildCommandlinesHelper(appArgs, 1u, rawCommands);
1261+
1262+
VERIFY_IS_TRUE(appArgs.GetLaunchMode().has_value());
1263+
VERIFY_ARE_EQUAL(appArgs.GetLaunchMode().value(), LaunchMode::MaximizedMode);
1264+
VERIFY_ARE_EQUAL(1u, appArgs._startupActions.size());
1265+
1266+
auto actionAndArgs = appArgs._startupActions.at(0);
1267+
VERIFY_ARE_EQUAL(ShortcutAction::NewTab, actionAndArgs.Action());
1268+
VERIFY_IS_NOT_NULL(actionAndArgs.Args());
1269+
auto myArgs = actionAndArgs.Args().try_as<NewTabArgs>();
1270+
VERIFY_IS_NOT_NULL(myArgs);
1271+
VERIFY_IS_NOT_NULL(myArgs.TerminalArgs());
1272+
VERIFY_IS_FALSE(myArgs.TerminalArgs().Commandline().empty());
1273+
VERIFY_IS_TRUE(myArgs.TerminalArgs().StartingDirectory().empty());
1274+
VERIFY_IS_TRUE(myArgs.TerminalArgs().TabTitle().empty());
1275+
VERIFY_IS_TRUE(myArgs.TerminalArgs().ProfileIndex() == nullptr);
1276+
VERIFY_IS_TRUE(myArgs.TerminalArgs().Profile().empty());
1277+
VERIFY_ARE_EQUAL(L"powershell.exe", myArgs.TerminalArgs().Commandline());
1278+
}
1279+
}
12271280
}

src/cascadia/TerminalApp/AppCommandlineArgs.cpp

Lines changed: 21 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -69,50 +69,29 @@ int AppCommandlineArgs::ParseCommand(const Commandline& command)
6969
{
7070
throw CLI::CallForHelp();
7171
}
72-
// Clear the parser's internal state
73-
_app.clear();
7472

75-
// attempt to parse the commandline
73+
// attempt to parse the commandline prefix of the form [options][subcommand]
7674
_app.parse(args);
75+
auto remainingParams = _app.remaining_size();
7776

7877
// If we parsed the commandline, and _no_ subcommands were provided, try
79-
// parsing again as a "new-tab" command.
80-
78+
// parse the remaining suffix as a "new-tab" command.
8179
if (_noCommandsProvided())
8280
{
83-
_newTabCommand.subcommand->clear();
8481
_newTabCommand.subcommand->parse(args);
82+
remainingParams = _newTabCommand.subcommand->remaining_size();
83+
}
84+
85+
// if after parsing the prefix and (optionally) the implicit tab subcommand
86+
// we still have unparsed parameters we need to fail
87+
if (remainingParams > 0)
88+
{
89+
throw CLI::ExtrasError(args);
8590
}
86-
}
87-
catch (const CLI::CallForHelp& e)
88-
{
89-
return _handleExit(_app, e);
9091
}
9192
catch (const CLI::ParseError& e)
9293
{
93-
// If we parsed the commandline, and _no_ subcommands were provided, try
94-
// parsing again as a "new-tab" command.
95-
if (_noCommandsProvided())
96-
{
97-
try
98-
{
99-
// CLI11 mutated the original vector the first time it tried to
100-
// parse the args. Reconstruct it the way CLI11 wants here.
101-
// "See above for why it's begin() + 1"
102-
std::vector<std::string> args{ command.Args().begin() + 1, command.Args().end() };
103-
std::reverse(args.begin(), args.end());
104-
_newTabCommand.subcommand->clear();
105-
_newTabCommand.subcommand->parse(args);
106-
}
107-
catch (const CLI::ParseError& e)
108-
{
109-
return _handleExit(*_newTabCommand.subcommand, e);
110-
}
111-
}
112-
else
113-
{
114-
return _handleExit(_app, e);
115-
}
94+
return _handleExit(_app, e);
11695
}
11796
return 0;
11897
}
@@ -157,6 +136,15 @@ int AppCommandlineArgs::_handleExit(const CLI::App& command, const CLI::Error& e
157136
// - <none>
158137
void AppCommandlineArgs::_buildParser()
159138
{
139+
// We define or parser as a prefix command, to support "implicit new tab subcommand" scenario.
140+
// In this scenario we will try to parse the prefix that contains parameters like launch mode,
141+
// but will not encounter an explicit command.
142+
// Instead we will encounter an argument that doesn't belong to the prefix indicating the prefix is over.
143+
// Then we will try to parse the remaining arguments as a new tab subcommand.
144+
// E.g., for "wt.exe -M -d c:/", we will use -M for the launch mode, but once we will encounter -d
145+
// we will know that the prefix is over and try to handle the suffix as a new tab subcommand
146+
_app.prefix_command();
147+
160148
// -v,--version: Displays version info
161149
auto versionCallback = [this](int64_t /*count*/) {
162150
// Set our message to display the application name and the current version.

0 commit comments

Comments
 (0)