diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 8ab581594e0..89f27a1d77e 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -618,6 +618,11 @@ "type": "boolean", "default": false, "description": "This will override the profile's `elevate` setting." + }, + "keepOpen": { + "type": "boolean", + "default": false, + "description": "This will override the profile's `closeOnExit` setting to keep the tab/pane/window open." } }, "type": "object" diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp index f3c0efa463d..96c63fce272 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp @@ -717,6 +717,69 @@ namespace TerminalAppLocalTests VERIFY_IS_FALSE(terminalArgs.ColorScheme().empty()); VERIFY_ARE_EQUAL(expectedScheme, terminalArgs.ColorScheme()); } + { + // Test for existing --KeepOpen parameter + AppCommandlineArgs appArgs{}; + std::vector rawCommands{ L"wt.exe", subcommand, L"-p", L"PowerShell", L"--KeepOpen" }; + _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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(terminalArgs.Profile(), L"PowerShell"); + } + { + // Test for existing -O parameter (Short form of --keepOpen) + AppCommandlineArgs appArgs{}; + std::vector rawCommands{ L"wt.exe", subcommand, L"-p", L"PowerShell", L"-o" }; + _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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(terminalArgs.Profile(), L"PowerShell"); + } + { + // Test for NOT existing --keepOpen parameter + AppCommandlineArgs appArgs{}; + std::vector rawCommands{ L"wt.exe", subcommand, L"-p", L"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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_TRUE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_FALSE(terminalArgs.Profile().empty()); + VERIFY_IS_FALSE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(terminalArgs.Profile(), L"PowerShell"); + } } void CommandlineTest::ParseSplitPaneIntoArgs() @@ -1041,6 +1104,69 @@ namespace TerminalAppLocalTests VERIFY_IS_TRUE(terminalArgs.Profile().empty()); VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", terminalArgs.Commandline()); } + { + // Test for existing --KeepOpen parameter + AppCommandlineArgs appArgs{}; + std::vector rawCommands{ L"wt.exe", L"--KeepOpen", 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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", terminalArgs.Commandline()); + } + { + // Test for existing -O parameter (Short form of --keepOpen) + AppCommandlineArgs appArgs{}; + std::vector rawCommands{ L"wt.exe", L"-O", 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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_TRUE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", terminalArgs.Commandline()); + } + { + // Test for NOT existing --keepOpen parameter + AppCommandlineArgs appArgs{}; + std::vector 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(); + VERIFY_IS_NOT_NULL(myArgs); + auto terminalArgs{ myArgs.ContentArgs().try_as() }; + VERIFY_IS_NOT_NULL(terminalArgs); + VERIFY_IS_FALSE(terminalArgs.Commandline().empty()); + VERIFY_IS_NULL(terminalArgs.ProfileIndex()); + VERIFY_IS_TRUE(terminalArgs.Profile().empty()); + VERIFY_IS_FALSE(terminalArgs.KeepOpen()); + VERIFY_ARE_EQUAL(L"powershell.exe \"This is an arg with spaces\"", terminalArgs.Commandline()); + } } void CommandlineTest::ParseFocusTabArgs() diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index b7a6b581a05..fcf14fbab42 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -579,6 +579,8 @@ void AppCommandlineArgs::_addNewTerminalArgs(AppCommandlineArgs::NewTerminalSubc _inheritEnvironment, RS_A(L"CmdInheritEnvDesc")); + subcommand.keepOpenOption = subcommand.subcommand->add_flag("-o,--keepOpen", _keepOpenOption, RS_A(L"CmdKeepOpenDesc")); + // Using positionals_at_end allows us to support "wt new-tab -d wsl -d Ubuntu" // without CLI11 thinking that we've specified -d twice. // There's an alternate construction where we make all subcommands "prefix commands", @@ -685,6 +687,11 @@ NewTerminalArgs AppCommandlineArgs::_getNewTerminalArgs(AppCommandlineArgs::NewT } args.ReloadEnvironmentVariables(!inheritEnv); + if (*subcommand.keepOpenOption) + { + args.KeepOpen(_keepOpenOption); + } + return args; } @@ -731,6 +738,7 @@ void AppCommandlineArgs::_resetStateToDefault() _commandline.clear(); _suppressApplicationTitle = false; _appendCommandLineOption = false; + _keepOpenOption = false; _splitVertical = false; _splitHorizontal = false; diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 7eb5a34f93d..14fcad76f5f 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -70,6 +70,7 @@ class TerminalApp::AppCommandlineArgs final CLI::Option* colorSchemeOption; CLI::Option* appendCommandLineOption; CLI::Option* inheritEnvOption; + CLI::Option* keepOpenOption; }; struct NewPaneSubcommand : public NewTerminalSubcommand @@ -112,6 +113,8 @@ class TerminalApp::AppCommandlineArgs final std::vector _commandline; bool _appendCommandLineOption{ false }; + bool _keepOpenOption{ false }; + bool _splitVertical{ false }; bool _splitHorizontal{ false }; bool _splitDuplicate{ false }; diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 15b79c40ffc..4c0166e7f4b 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -898,4 +898,7 @@ Restart the active pane connection + + If set, the window/tab/pane will stay open after the command has exited. (It is he same as setting the profile termination behavior to never.) + diff --git a/src/cascadia/TerminalApp/TerminalPaneContent.cpp b/src/cascadia/TerminalApp/TerminalPaneContent.cpp index 5ecdb4bf560..64dd1544c8e 100644 --- a/src/cascadia/TerminalApp/TerminalPaneContent.cpp +++ b/src/cascadia/TerminalApp/TerminalPaneContent.cpp @@ -235,6 +235,18 @@ namespace winrt::TerminalApp::implementation co_return; } + // We try to read the setting from the command line arguments + if (const auto& settings{ _cache.TryLookup(_profile) }) + { + const auto closeMode = settings.DefaultSettings().CloseOnExit(); + + // If the command line argument "keepOpen" is set to keep the pane/tab/window open we stop further execution here. + if (closeMode == CloseOnExitMode::Never) + { + co_return; + } + } + if (_profile) { const auto mode = _profile.CloseOnExit(); diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.h b/src/cascadia/TerminalSettingsModel/ActionArgs.h index 3f0a922b6b7..34bb118d7d0 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.h +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.h @@ -369,6 +369,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation ACTION_ARG(winrt::hstring, ColorScheme); ACTION_ARG(Windows::Foundation::IReference, Elevate, nullptr); ACTION_ARG(Windows::Foundation::IReference, ReloadEnvironmentVariables, nullptr); + ACTION_ARG(bool, KeepOpen, false) ACTION_ARG(uint64_t, ContentId); static constexpr std::string_view CommandlineKey{ "commandline" }; @@ -383,6 +384,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation static constexpr std::string_view ColorSchemeKey{ "colorScheme" }; static constexpr std::string_view ElevateKey{ "elevate" }; static constexpr std::string_view ReloadEnvironmentVariablesKey{ "reloadEnvironmentVariables" }; + static constexpr std::string_view KeepOpenKey{ "keepOpen" }; static constexpr std::string_view ContentKey{ "__content" }; public: @@ -405,6 +407,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation otherAsUs->_ColorScheme == _ColorScheme && otherAsUs->_Elevate == _Elevate && otherAsUs->_ReloadEnvironmentVariables == _ReloadEnvironmentVariables && + otherAsUs->_KeepOpen == _KeepOpen && otherAsUs->_ContentId == _ContentId; } return false; @@ -424,6 +427,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation JsonUtils::GetValueForKey(json, ColorSchemeKey, args->_ColorScheme); JsonUtils::GetValueForKey(json, ElevateKey, args->_Elevate); JsonUtils::GetValueForKey(json, ReloadEnvironmentVariablesKey, args->_ReloadEnvironmentVariables); + JsonUtils::GetValueForKey(json, KeepOpenKey, args->_KeepOpen); JsonUtils::GetValueForKey(json, ContentKey, args->_ContentId); return *args; } @@ -446,6 +450,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation JsonUtils::SetValueForKey(json, ColorSchemeKey, args->_ColorScheme); JsonUtils::SetValueForKey(json, ElevateKey, args->_Elevate); JsonUtils::SetValueForKey(json, ReloadEnvironmentVariablesKey, args->_ReloadEnvironmentVariables); + JsonUtils::SetValueForKey(json, KeepOpenKey, args->_KeepOpen); JsonUtils::SetValueForKey(json, ContentKey, args->_ContentId); return json; } @@ -463,6 +468,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation copy->_ColorScheme = _ColorScheme; copy->_Elevate = _Elevate; copy->_ReloadEnvironmentVariables = _ReloadEnvironmentVariables; + copy->_KeepOpen = _KeepOpen; copy->_ContentId = _ContentId; return *copy; } @@ -484,6 +490,7 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation h.write(ColorScheme()); h.write(Elevate()); h.write(ReloadEnvironmentVariables()); + h.write(KeepOpen()); h.write(ContentId()); } }; diff --git a/src/cascadia/TerminalSettingsModel/ActionArgs.idl b/src/cascadia/TerminalSettingsModel/ActionArgs.idl index af03e807176..345e71b8973 100644 --- a/src/cascadia/TerminalSettingsModel/ActionArgs.idl +++ b/src/cascadia/TerminalSettingsModel/ActionArgs.idl @@ -146,6 +146,7 @@ namespace Microsoft.Terminal.Settings.Model String Profile; // Either a GUID or a profile's name if the GUID isn't a match Guid SessionId; Boolean AppendCommandLine; + Boolean KeepOpen; // We use IReference<> to treat some args as nullable where null means // "use the inherited value". See ProfileIndex, diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp index 29b9bb811fe..765e5597151 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.cpp @@ -195,6 +195,11 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation { defaultSettings.ReloadEnvironmentVariables(newTerminalArgs.ReloadEnvironmentVariables().Value()); } + + if (newTerminalArgs.KeepOpen()) + { + defaultSettings.CloseOnExit(CloseOnExitMode::Never); + } } return settingsPair; @@ -347,6 +352,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation _RepositionCursorWithMouse = profile.RepositionCursorWithMouse(); _ReloadEnvironmentVariables = profile.ReloadEnvironmentVariables(); + + _CloseOnExit = profile.CloseOnExit(); } // Method Description: diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.h b/src/cascadia/TerminalSettingsModel/TerminalSettings.h index 33658427f94..82b7b454979 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.h +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.h @@ -173,6 +173,8 @@ namespace winrt::Microsoft::Terminal::Settings::Model::implementation INHERITABLE_SETTING(Model::TerminalSettings, bool, ReloadEnvironmentVariables, true); + INHERITABLE_SETTING(Model::TerminalSettings, CloseOnExitMode, CloseOnExit, CloseOnExitMode::Automatic); + private: std::optional> _ColorTable; std::span _getColorTableImpl(); diff --git a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl index 32bf6111909..bce77a36a8e 100644 --- a/src/cascadia/TerminalSettingsModel/TerminalSettings.idl +++ b/src/cascadia/TerminalSettingsModel/TerminalSettings.idl @@ -47,5 +47,6 @@ namespace Microsoft.Terminal.Settings.Model Boolean Elevate; Boolean ReloadEnvironmentVariables; + CloseOnExitMode CloseOnExit; }; }