diff --git a/doc/cascadia/profiles.schema.json b/doc/cascadia/profiles.schema.json index 3df0f2d5604..0ca40b685bd 100644 --- a/doc/cascadia/profiles.schema.json +++ b/doc/cascadia/profiles.schema.json @@ -63,6 +63,7 @@ "openTabColorPicker", "renameTab", "commandPalette", + "wt", "unbound" ], "type": "string" @@ -280,6 +281,23 @@ } ] }, + "WtAction": { + "description": "Arguments corresponding to a wt Action", + "allOf": [ + { "$ref": "#/definitions/ShortcutAction" }, + { + "properties": { + "action": { "type": "string", "pattern": "wt" }, + "commandline": { + "type": "string", + "default": "", + "description": "a `wt` commandline to run in the current window" + } + } + } + ], + "required": [ "commandline" ] + }, "Keybinding": { "additionalProperties": false, "properties": { @@ -296,6 +314,7 @@ { "$ref": "#/definitions/SplitPaneAction" }, { "$ref": "#/definitions/OpenSettingsAction" }, { "$ref": "#/definitions/SetTabColorAction" }, + { "$ref": "#/definitions/WtAction" }, { "type": "null" } ] }, diff --git a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp index e9191a4f2f5..d9735e2ccd4 100644 --- a/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp +++ b/src/cascadia/LocalTests_TerminalApp/CommandlineTest.cpp @@ -4,7 +4,9 @@ #include "pch.h" #include +#include "../TerminalApp/TerminalPage.h" #include "../TerminalApp/AppCommandlineArgs.h" +#include "../TerminalApp/ActionArgs.h" using namespace WEX::Logging; using namespace WEX::Common; @@ -52,6 +54,10 @@ namespace TerminalAppLocalTests TEST_METHOD(CheckTypos); + TEST_METHOD(TestSimpleExecuteCommandlineAction); + TEST_METHOD(TestMultipleCommandExecuteCommandlineAction); + TEST_METHOD(TestInvalidExecuteCommandlineAction); + private: void _buildCommandlinesHelper(AppCommandlineArgs& appArgs, const size_t expectedSubcommands, @@ -1067,4 +1073,66 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(L"C:\\", myArgs.TerminalArgs().StartingDirectory()); } } + + void CommandlineTest::TestSimpleExecuteCommandlineAction() + { + auto args = winrt::make_self(); + args->Commandline(L"new-tab"); + auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args); + VERIFY_ARE_EQUAL(1u, actions.size()); + auto actionAndArgs = actions.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); + 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()); + } + + void CommandlineTest::TestMultipleCommandExecuteCommandlineAction() + { + auto args = winrt::make_self(); + args->Commandline(L"new-tab ; split-pane"); + auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args); + VERIFY_ARE_EQUAL(2u, actions.size()); + { + auto actionAndArgs = actions.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); + 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()); + } + { + auto actionAndArgs = actions.at(1); + VERIFY_ARE_EQUAL(ShortcutAction::SplitPane, actionAndArgs.Action()); + VERIFY_IS_NOT_NULL(actionAndArgs.Args()); + auto myArgs = actionAndArgs.Args().try_as(); + 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()); + } + } + + void CommandlineTest::TestInvalidExecuteCommandlineAction() + { + auto args = winrt::make_self(); + // -H and -V cannot be combined. + args->Commandline(L"split-pane -H -V"); + auto actions = implementation::TerminalPage::ConvertExecuteCommandlineToActions(*args); + VERIFY_ARE_EQUAL(0u, actions.size()); + } } diff --git a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp index ecd58743f56..cdfb08415f5 100644 --- a/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp +++ b/src/cascadia/LocalTests_TerminalApp/SettingsTests.cpp @@ -76,6 +76,8 @@ namespace TerminalAppLocalTests TEST_METHOD(ValidateKeybindingsWarnings); + TEST_METHOD(ValidateExecuteCommandlineWarning); + TEST_METHOD(ValidateLegacyGlobalsWarning); TEST_METHOD(TestTrailingCommas); @@ -2254,6 +2256,57 @@ namespace TerminalAppLocalTests VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3)); } + void SettingsTests::ValidateExecuteCommandlineWarning() + { + Log::Comment(L"This test is affected by GH#6949, so we're just skipping it for now."); + Log::Result(WEX::Logging::TestResults::Skipped); + return; + + // const std::string badSettings{ R"( + // { + // "defaultProfile": "{6239a42c-2222-49a3-80bd-e8fdd045185c}", + // "profiles": [ + // { + // "name" : "profile0", + // "guid": "{6239a42c-2222-49a3-80bd-e8fdd045185c}" + // }, + // { + // "name" : "profile1", + // "guid": "{6239a42c-3333-49a3-80bd-e8fdd045185c}" + // } + // ], + // "keybindings": [ + // { "name":null, "command": { "action": "wt" }, "keys": [ "ctrl+a" ] }, + // { "name":null, "command": { "action": "wt", "commandline":"" }, "keys": [ "ctrl+b" ] }, + // { "name":null, "command": { "action": "wt", "commandline":null }, "keys": [ "ctrl+c" ] } + // ] + // })" }; + + // const auto settingsObject = VerifyParseSucceeded(badSettings); + + // auto settings = CascadiaSettings::FromJson(settingsObject); + + // VERIFY_ARE_EQUAL(0u, settings->_globals._keybindings->_keyShortcuts.size()); + + // for (const auto& warning : settings->_globals._keybindingsWarnings) + // { + // Log::Comment(NoThrowString().Format( + // L"warning:%d", warning)); + // } + // VERIFY_ARE_EQUAL(3u, settings->_globals._keybindingsWarnings.size()); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(0)); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(1)); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_globals._keybindingsWarnings.at(2)); + + // settings->_ValidateKeybindings(); + + // VERIFY_ARE_EQUAL(4u, settings->_warnings.size()); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::AtLeastOneKeybindingWarning, settings->_warnings.at(0)); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(1)); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(2)); + // VERIFY_ARE_EQUAL(::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter, settings->_warnings.at(3)); + } + void SettingsTests::ValidateLegacyGlobalsWarning() { const std::string badSettings{ R"( diff --git a/src/cascadia/TerminalApp/ActionAndArgs.cpp b/src/cascadia/TerminalApp/ActionAndArgs.cpp index a5d98e89bd2..f28bfcca8a6 100644 --- a/src/cascadia/TerminalApp/ActionAndArgs.cpp +++ b/src/cascadia/TerminalApp/ActionAndArgs.cpp @@ -38,6 +38,7 @@ static constexpr std::string_view ToggleAlwaysOnTopKey{ "toggleAlwaysOnTop" }; static constexpr std::string_view SetTabColorKey{ "setTabColor" }; static constexpr std::string_view OpenTabColorPickerKey{ "openTabColorPicker" }; static constexpr std::string_view RenameTabKey{ "renameTab" }; +static constexpr std::string_view ExecuteCommandlineKey{ "wt" }; static constexpr std::string_view ToggleCommandPaletteKey{ "commandPalette" }; static constexpr std::string_view ActionKey{ "action" }; @@ -89,6 +90,7 @@ namespace winrt::TerminalApp::implementation { UnboundKey, ShortcutAction::Invalid }, { FindKey, ShortcutAction::Find }, { RenameTabKey, ShortcutAction::RenameTab }, + { ExecuteCommandlineKey, ShortcutAction::ExecuteCommandline }, { ToggleCommandPaletteKey, ShortcutAction::ToggleCommandPalette }, }; @@ -121,6 +123,8 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::RenameTab, winrt::TerminalApp::implementation::RenameTabArgs::FromJson }, + { ShortcutAction::ExecuteCommandline, winrt::TerminalApp::implementation::ExecuteCommandlineArgs::FromJson }, + { ShortcutAction::Invalid, nullptr }, }; @@ -268,6 +272,7 @@ namespace winrt::TerminalApp::implementation { ShortcutAction::SetTabColor, RS_(L"ResetTabColorCommandKey") }, { ShortcutAction::OpenTabColorPicker, RS_(L"OpenTabColorPickerCommandKey") }, { ShortcutAction::RenameTab, RS_(L"ResetTabNameCommandKey") }, + { ShortcutAction::ExecuteCommandline, RS_(L"ExecuteCommandlineCommandKey") }, { ShortcutAction::ToggleCommandPalette, RS_(L"ToggleCommandPaletteCommandKey") }, }; }(); diff --git a/src/cascadia/TerminalApp/ActionArgs.cpp b/src/cascadia/TerminalApp/ActionArgs.cpp index 012eacd7f22..a5a83203afa 100644 --- a/src/cascadia/TerminalApp/ActionArgs.cpp +++ b/src/cascadia/TerminalApp/ActionArgs.cpp @@ -17,6 +17,7 @@ #include "OpenSettingsArgs.g.cpp" #include "SetTabColorArgs.g.cpp" #include "RenameTabArgs.g.cpp" +#include "ExecuteCommandlineArgs.g.cpp" #include @@ -258,4 +259,17 @@ namespace winrt::TerminalApp::implementation return RS_(L"ResetTabNameCommandKey"); } + winrt::hstring ExecuteCommandlineArgs::GenerateName() const + { + // "Run commandline "{_Commandline}" in this window" + if (!_Commandline.empty()) + { + return winrt::hstring{ + fmt::format(std::wstring_view(RS_(L"ExecuteCommandlineCommandKey")), + _Commandline.c_str()) + }; + } + return L""; + } + } diff --git a/src/cascadia/TerminalApp/ActionArgs.h b/src/cascadia/TerminalApp/ActionArgs.h index 93e2b5baf3e..091774230d1 100644 --- a/src/cascadia/TerminalApp/ActionArgs.h +++ b/src/cascadia/TerminalApp/ActionArgs.h @@ -17,6 +17,7 @@ #include "OpenSettingsArgs.g.h" #include "SetTabColorArgs.g.h" #include "RenameTabArgs.g.h" +#include "ExecuteCommandlineArgs.g.h" #include "../../cascadia/inc/cppwinrt_utils.h" #include "Utils.h" @@ -387,6 +388,39 @@ namespace winrt::TerminalApp::implementation return { *args, {} }; } }; + + struct ExecuteCommandlineArgs : public ExecuteCommandlineArgsT + { + ExecuteCommandlineArgs() = default; + GETSET_PROPERTY(winrt::hstring, Commandline, L""); + + static constexpr std::string_view CommandlineKey{ "commandline" }; + + public: + hstring GenerateName() const; + + bool Equals(const IActionArgs& other) + { + auto otherAsUs = other.try_as(); + if (otherAsUs) + { + return otherAsUs->_Commandline == _Commandline; + } + return false; + }; + static FromJsonResult FromJson(const Json::Value& json) + { + // LOAD BEARING: Not using make_self here _will_ break you in the future! + auto args = winrt::make_self(); + JsonUtils::GetValueForKey(json, CommandlineKey, args->_Commandline); + if (args->_Commandline.empty()) + { + return { nullptr, { ::TerminalApp::SettingsLoadWarnings::MissingRequiredParameter } }; + } + return { *args, {} }; + } + }; + } namespace winrt::TerminalApp::factory_implementation diff --git a/src/cascadia/TerminalApp/ActionArgs.idl b/src/cascadia/TerminalApp/ActionArgs.idl index 36d5025bb4a..84e5588bf2d 100644 --- a/src/cascadia/TerminalApp/ActionArgs.idl +++ b/src/cascadia/TerminalApp/ActionArgs.idl @@ -115,4 +115,9 @@ namespace TerminalApp { String Title { get; }; }; + + [default_interface] runtimeclass ExecuteCommandlineArgs : IActionArgs + { + String Commandline; + }; } diff --git a/src/cascadia/TerminalApp/AppActionHandlers.cpp b/src/cascadia/TerminalApp/AppActionHandlers.cpp index 271513d4e58..63a2743d80b 100644 --- a/src/cascadia/TerminalApp/AppActionHandlers.cpp +++ b/src/cascadia/TerminalApp/AppActionHandlers.cpp @@ -11,6 +11,7 @@ using namespace winrt::Windows::ApplicationModel::DataTransfer; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Text; using namespace winrt::Windows::UI::Core; +using namespace winrt::Windows::Foundation::Collections; using namespace winrt::Windows::System; using namespace winrt::Microsoft::Terminal; using namespace winrt::Microsoft::Terminal::Settings; @@ -334,4 +335,20 @@ namespace winrt::TerminalApp::implementation } args.Handled(true); } + + void TerminalPage::_HandleExecuteCommandline(const IInspectable& /*sender*/, + const TerminalApp::ActionEventArgs& actionArgs) + { + if (const auto& realArgs = actionArgs.ActionArgs().try_as()) + { + auto actions = winrt::single_threaded_vector(std::move( + TerminalPage::ConvertExecuteCommandlineToActions(realArgs))); + + if (_startupActions.Size() != 0) + { + actionArgs.Handled(true); + _ProcessStartupActions(actions, false); + } + } + } } diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp index 753afb2fe6e..70e6df6940b 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.cpp +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.cpp @@ -599,7 +599,7 @@ void AppCommandlineArgs::_addCommandsForArg(std::vector& commands, // - // Return Value: // - the deque of actions we've buffered as a result of parsing commands. -std::deque& AppCommandlineArgs::GetStartupActions() +std::vector& AppCommandlineArgs::GetStartupActions() { return _startupActions; } @@ -658,7 +658,8 @@ void AppCommandlineArgs::ValidateStartupCommands() auto newTerminalArgs = winrt::make_self(); args->TerminalArgs(*newTerminalArgs); newTabAction->Args(*args); - _startupActions.push_front(*newTabAction); + // push the arg onto the front + _startupActions.insert(_startupActions.begin(), 1, *newTabAction); } } @@ -666,3 +667,52 @@ std::optional AppCommandlineArgs::GetLaunchMode( { return _launchMode; } + +// 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 +// successfully 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. +// - The first arg in args should be the program name "wt" (or some variant). It +// will be ignored during parsing. +// 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 AppCommandlineArgs::ParseArgs(winrt::array_view& 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 = 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; +} diff --git a/src/cascadia/TerminalApp/AppCommandlineArgs.h b/src/cascadia/TerminalApp/AppCommandlineArgs.h index 00c7f3cd904..a0d87ffc6b8 100644 --- a/src/cascadia/TerminalApp/AppCommandlineArgs.h +++ b/src/cascadia/TerminalApp/AppCommandlineArgs.h @@ -28,13 +28,15 @@ class TerminalApp::AppCommandlineArgs final AppCommandlineArgs(); ~AppCommandlineArgs() = default; + int ParseCommand(const Commandline& command); + int ParseArgs(winrt::array_view& args); static std::vector BuildCommands(const std::vector& args); static std::vector BuildCommands(winrt::array_view& args); void ValidateStartupCommands(); - std::deque& GetStartupActions(); + std::vector& GetStartupActions(); const std::string& GetExitMessage(); bool ShouldExitEarly() const noexcept; @@ -90,7 +92,7 @@ class TerminalApp::AppCommandlineArgs final std::optional _launchMode{ std::nullopt }; // Are you adding more args here? Make sure to reset them in _resetStateToDefault - std::deque _startupActions; + std::vector _startupActions; std::string _exitMessage; bool _shouldExitEarly{ false }; diff --git a/src/cascadia/TerminalApp/AppLogic.cpp b/src/cascadia/TerminalApp/AppLogic.cpp index e036bd9ee2c..5a6ec188703 100644 --- a/src/cascadia/TerminalApp/AppLogic.cpp +++ b/src/cascadia/TerminalApp/AppLogic.cpp @@ -474,7 +474,7 @@ namespace winrt::TerminalApp::implementation // - // Return Value: // - a point containing the requested dimensions in pixels. - winrt::Windows::Foundation::Point AppLogic::GetLaunchDimensions(uint32_t dpi) + winrt::Windows::Foundation::Size AppLogic::GetLaunchDimensions(uint32_t dpi) { if (!_loadedInitialSettings) { @@ -504,7 +504,7 @@ namespace winrt::TerminalApp::implementation // of the height calculation here. auto titlebar = TitlebarControl{ static_cast(0) }; titlebar.Measure({ SHRT_MAX, SHRT_MAX }); - proposedSize.Y += (titlebar.DesiredSize().Height) * scale; + proposedSize.Height += (titlebar.DesiredSize().Height) * scale; } else if (_settings->GlobalSettings().AlwaysShowTabs()) { @@ -519,7 +519,7 @@ namespace winrt::TerminalApp::implementation // For whatever reason, there's about 6px of unaccounted-for space // in the application. I couldn't tell you where these 6px are // coming from, but they need to be included in this math. - proposedSize.Y += (tabControl.DesiredSize().Height + 6) * scale; + proposedSize.Width += (tabControl.DesiredSize().Height + 6) * scale; } return proposedSize; @@ -974,7 +974,7 @@ namespace winrt::TerminalApp::implementation // or 0. (see AppLogic::_ParseArgs) int32_t AppLogic::SetStartupCommandline(array_view args) { - const auto result = _ParseArgs(args); + const auto result = _appArgs.ParseArgs(args); if (result == 0) { _appArgs.ValidateStartupCommands(); @@ -984,53 +984,6 @@ namespace winrt::TerminalApp::implementation return result; } - // 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 - // successfully 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 AppLogic::_ParseArgs(winrt::array_view& 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: // - If there were any errors parsing the commandline that was used to // initialize the terminal, this will return a string containing that diff --git a/src/cascadia/TerminalApp/AppLogic.h b/src/cascadia/TerminalApp/AppLogic.h index c879a6f1876..de2b225f81f 100644 --- a/src/cascadia/TerminalApp/AppLogic.h +++ b/src/cascadia/TerminalApp/AppLogic.h @@ -37,7 +37,7 @@ namespace winrt::TerminalApp::implementation bool Fullscreen() const; bool AlwaysOnTop() const; - Windows::Foundation::Point GetLaunchDimensions(uint32_t dpi); + Windows::Foundation::Size GetLaunchDimensions(uint32_t dpi); winrt::Windows::Foundation::Point GetLaunchInitialPositions(int32_t defaultInitialX, int32_t defaultInitialY); winrt::Windows::UI::Xaml::ElementTheme GetRequestedTheme(); LaunchMode GetLaunchMode(); diff --git a/src/cascadia/TerminalApp/AppLogic.idl b/src/cascadia/TerminalApp/AppLogic.idl index 071a5ce84cc..00006608ef1 100644 --- a/src/cascadia/TerminalApp/AppLogic.idl +++ b/src/cascadia/TerminalApp/AppLogic.idl @@ -45,7 +45,8 @@ namespace TerminalApp Boolean Fullscreen { get; }; Boolean AlwaysOnTop { get; }; - Windows.Foundation.Point GetLaunchDimensions(UInt32 dpi); + Windows.Foundation.Size GetLaunchDimensions(UInt32 dpi); + Windows.Foundation.Point GetLaunchInitialPositions(Int32 defaultInitialX, Int32 defaultInitialY); Windows.UI.Xaml.ElementTheme GetRequestedTheme(); LaunchMode GetLaunchMode(); diff --git a/src/cascadia/TerminalApp/GlobalAppSettings.cpp b/src/cascadia/TerminalApp/GlobalAppSettings.cpp index c214c04896d..dd3b17ae723 100644 --- a/src/cascadia/TerminalApp/GlobalAppSettings.cpp +++ b/src/cascadia/TerminalApp/GlobalAppSettings.cpp @@ -199,7 +199,6 @@ void GlobalAppSettings::LayerJson(const Json::Value& json) warnings = winrt::TerminalApp::implementation::Command::LayerJson(_commands, bindings); // It's possible that the user provided commands have some warnings // in them, similar to the keybindings. - _keybindingsWarnings.insert(_keybindingsWarnings.end(), warnings.begin(), warnings.end()); } }; parseBindings(LegacyKeybindingsKey); diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index b931755c1e3..f8e61ae8379 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -7,6 +7,7 @@ #include "CascadiaSettings.h" using namespace winrt::Windows::Foundation; +using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Core; @@ -921,6 +922,102 @@ bool Pane::CanSplit(SplitState splitType) return false; } +// Method Description: +// - This is a helper to determine if a given Pane can be split, but without +// using the ActualWidth() and ActualHeight() methods. This is used during +// processing of many "split-pane" commands, which could happen _before_ we've +// laid out a Pane for the first time. When this happens, the Pane's don't +// have an actual size yet. However, we'd still like to figure out if the pane +// could be split, once they're all laid out. +// - This method assumes that the Pane we're attempting to split is `target`, +// and this method should be called on the root of a tree of Panes. +// - We'll walk down the tree attempting to find `target`. As we traverse the +// tree, we'll reduce the size passed to each subsequent recursive call. The +// size passed to this method represents how much space this Pane _will_ have +// to use. +// * If this pane is a leaf, and it's the pane we're looking for, use the +// available space to calculate which direction to split in. +// * If this pane is _any other leaf_, then just return nullopt, to indicate +// that the `target` Pane is not down this branch. +// * If this pane is a parent, calculate how much space our children will be +// able to use, and recurse into them. +// Arguments: +// - target: The Pane we're attempting to split. +// - splitType: The direction we're attempting to split in. +// - availableSpace: The theoretical space that's available for this pane to be able to split. +// Return Value: +// - nullopt if `target` is not this pane or a child of this pane, otherwise +// true iff we could split this pane, given `availableSpace` +// Note: +// - This method is highly similar to Pane::PreCalculateAutoSplit +std::optional Pane::PreCalculateCanSplit(const std::shared_ptr target, + SplitState splitType, + const winrt::Windows::Foundation::Size availableSpace) const +{ + if (_IsLeaf()) + { + if (target.get() == this) + { + // If this pane is a leaf, and it's the pane we're looking for, use + // the available space to calculate which direction to split in. + const Size minSize = _GetMinSize(); + + if (splitType == SplitState::None) + { + return { false }; + } + + else if (splitType == SplitState::Vertical) + { + const auto widthMinusSeparator = availableSpace.Width - CombinedPaneBorderSize; + const auto newWidth = widthMinusSeparator * Half; + + return { newWidth > minSize.Width }; + } + + else if (splitType == SplitState::Horizontal) + { + const auto heightMinusSeparator = availableSpace.Height - CombinedPaneBorderSize; + const auto newHeight = heightMinusSeparator * Half; + + return { newHeight > minSize.Height }; + } + } + else + { + // If this pane is _any other leaf_, then just return nullopt, to + // indicate that the `target` Pane is not down this branch. + return std::nullopt; + } + } + else + { + // If this pane is a parent, calculate how much space our children will + // be able to use, and recurse into them. + + const bool isVerticalSplit = _splitState == SplitState::Vertical; + const float firstWidth = isVerticalSplit ? + (availableSpace.Width * _desiredSplitPosition) - PaneBorderSize : + availableSpace.Width; + const float secondWidth = isVerticalSplit ? + (availableSpace.Width - firstWidth) - PaneBorderSize : + availableSpace.Width; + const float firstHeight = !isVerticalSplit ? + (availableSpace.Height * _desiredSplitPosition) - PaneBorderSize : + availableSpace.Height; + const float secondHeight = !isVerticalSplit ? + (availableSpace.Height - firstHeight) - PaneBorderSize : + availableSpace.Height; + + const auto firstResult = _firstChild->PreCalculateCanSplit(target, splitType, { firstWidth, firstHeight }); + return firstResult.has_value() ? firstResult : _secondChild->PreCalculateCanSplit(target, splitType, { secondWidth, secondHeight }); + } + + // We should not possibly be getting here - both the above branches should + // return a value. + FAIL_FAST(); +} + // Method Description: // - Split the focused pane in our tree of panes, and place the given // TermControl into the newly created pane. If we're the focused pane, then diff --git a/src/cascadia/TerminalApp/Pane.h b/src/cascadia/TerminalApp/Pane.h index 7041db1f0cb..a9c7bd9cebb 100644 --- a/src/cascadia/TerminalApp/Pane.h +++ b/src/cascadia/TerminalApp/Pane.h @@ -64,7 +64,9 @@ class Pane : public std::enable_shared_from_this const winrt::Microsoft::Terminal::TerminalControl::TermControl& control); float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; std::optional PreCalculateAutoSplit(const std::shared_ptr target, const winrt::Windows::Foundation::Size parentSize) const; - + std::optional PreCalculateCanSplit(const std::shared_ptr target, + winrt::TerminalApp::SplitState splitType, + const winrt::Windows::Foundation::Size availableSpace) const; void Shutdown(); void Close(); diff --git a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw index 09283b55c6d..6ca22a39ff0 100644 --- a/src/cascadia/TerminalApp/Resources/en-US/Resources.resw +++ b/src/cascadia/TerminalApp/Resources/en-US/Resources.resw @@ -543,6 +543,10 @@ Reset tab title + + Run commandline "{0}" in this window + {0} will be replaced with user-defined commandline + Crimson diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp index a040106e24c..571b4589516 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.cpp @@ -194,6 +194,10 @@ namespace winrt::TerminalApp::implementation _RenameTabHandlers(*this, *eventArgs); break; } + case ShortcutAction::ExecuteCommandline: + { + _ExecuteCommandlineHandlers(*this, *eventArgs); + } default: return false; } diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.h b/src/cascadia/TerminalApp/ShortcutActionDispatch.h index 9b930db980d..9c1dff1575c 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.h +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.h @@ -54,6 +54,7 @@ namespace winrt::TerminalApp::implementation TYPED_EVENT(SetTabColor, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(OpenTabColorPicker, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); TYPED_EVENT(RenameTab, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); + TYPED_EVENT(ExecuteCommandline, TerminalApp::ShortcutActionDispatch, TerminalApp::ActionEventArgs); // clang-format on private: diff --git a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl index 2588c3258b2..8306c04fc46 100644 --- a/src/cascadia/TerminalApp/ShortcutActionDispatch.idl +++ b/src/cascadia/TerminalApp/ShortcutActionDispatch.idl @@ -35,10 +35,11 @@ namespace TerminalApp ToggleFocusMode, ToggleFullscreen, ToggleAlwaysOnTop, + OpenSettings, SetTabColor, OpenTabColorPicker, - OpenSettings, RenameTab, + ExecuteCommandline, ToggleCommandPalette }; @@ -84,5 +85,6 @@ namespace TerminalApp event Windows.Foundation.TypedEventHandler SetTabColor; event Windows.Foundation.TypedEventHandler OpenTabColorPicker; event Windows.Foundation.TypedEventHandler RenameTab; + event Windows.Foundation.TypedEventHandler ExecuteCommandline; } } diff --git a/src/cascadia/TerminalApp/Tab.cpp b/src/cascadia/TerminalApp/Tab.cpp index 0e9838ec69c..51e54be09ee 100644 --- a/src/cascadia/TerminalApp/Tab.cpp +++ b/src/cascadia/TerminalApp/Tab.cpp @@ -875,6 +875,10 @@ namespace winrt::TerminalApp::implementation return _rootPane->PreCalculateAutoSplit(_activePane, availableSpace).value_or(SplitState::Vertical); } + bool Tab::PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const + { + return _rootPane->PreCalculateCanSplit(_activePane, splitType, availableSpace).value_or(false); + } DEFINE_EVENT(Tab, ActivePaneChanged, _ActivePaneChangedHandlers, winrt::delegate<>); DEFINE_EVENT(Tab, ColorSelected, _colorSelected, winrt::delegate); DEFINE_EVENT(Tab, ColorCleared, _colorCleared, winrt::delegate<>); diff --git a/src/cascadia/TerminalApp/Tab.h b/src/cascadia/TerminalApp/Tab.h index be76c1b7912..a6752fe6bbe 100644 --- a/src/cascadia/TerminalApp/Tab.h +++ b/src/cascadia/TerminalApp/Tab.h @@ -40,6 +40,7 @@ namespace winrt::TerminalApp::implementation float CalcSnappedDimension(const bool widthOrHeight, const float dimension) const; SplitState PreCalculateAutoSplit(winrt::Windows::Foundation::Size rootSize) const; + bool PreCalculateCanSplit(SplitState splitType, winrt::Windows::Foundation::Size availableSpace) const; void ResizeContent(const winrt::Windows::Foundation::Size& newSize); void ResizePane(const winrt::TerminalApp::Direction& direction); diff --git a/src/cascadia/TerminalApp/TerminalPage.cpp b/src/cascadia/TerminalApp/TerminalPage.cpp index 6ed6d8e9716..81cfaf1735c 100644 --- a/src/cascadia/TerminalApp/TerminalPage.cpp +++ b/src/cascadia/TerminalApp/TerminalPage.cpp @@ -46,7 +46,8 @@ namespace winrt namespace winrt::TerminalApp::implementation { TerminalPage::TerminalPage() : - _tabs{ winrt::single_threaded_observable_vector() } + _tabs{ winrt::single_threaded_observable_vector() }, + _startupActions{ winrt::single_threaded_vector() } { InitializeComponent(); } @@ -223,7 +224,7 @@ namespace winrt::TerminalApp::implementation if (_startupState == StartupState::NotInitialized) { _startupState = StartupState::InStartup; - if (_startupActions.empty()) + if (_startupActions.Size() == 0) { _OpenNewTab(nullptr); @@ -231,22 +232,27 @@ namespace winrt::TerminalApp::implementation } else { - _ProcessStartupActions(); + _ProcessStartupActions(_startupActions, true); } } } // Method Description: - // - Process all the startup actions in our list of startup actions. We'll - // do this all at once here. + // - Process all the startup actions in the provided list of startup + // actions. We'll do this all at once here. // Arguments: - // - + // - actions: a winrt vector of actions to process. Note that this must NOT + // be an IVector&, because we need the collection to be accessible on the + // other side of the co_await. + // - initial: if true, we're parsing these args during startup, and we + // should fire an Initialized event. // Return Value: // - - winrt::fire_and_forget TerminalPage::_ProcessStartupActions() + winrt::fire_and_forget TerminalPage::_ProcessStartupActions(Windows::Foundation::Collections::IVector actions, + const bool initial) { // If there are no actions left, do nothing. - if (_startupActions.empty()) + if (actions.Size() == 0) { return; } @@ -256,11 +262,20 @@ namespace winrt::TerminalApp::implementation co_await winrt::resume_foreground(Dispatcher(), CoreDispatcherPriority::Normal); if (auto page{ weakThis.get() }) { - for (const auto& action : _startupActions) + for (const auto& action : actions) { - _actionDispatch->DoAction(action); + if (auto page{ weakThis.get() }) + { + _actionDispatch->DoAction(action); + } + else + { + return; + } } - + } + if (initial) + { _CompleteInitialization(); } } @@ -857,6 +872,7 @@ namespace winrt::TerminalApp::implementation _actionDispatch->SetTabColor({ this, &TerminalPage::_HandleSetTabColor }); _actionDispatch->OpenTabColorPicker({ this, &TerminalPage::_HandleOpenTabColorPicker }); _actionDispatch->RenameTab({ this, &TerminalPage::_HandleRenameTab }); + _actionDispatch->ExecuteCommandline({ this, &TerminalPage::_HandleExecuteCommandline }); } // Method Description: @@ -1364,19 +1380,20 @@ namespace winrt::TerminalApp::implementation const auto controlConnection = _CreateConnectionFromSettings(realGuid, controlSettings); - const auto canSplit = focusedTab->CanSplitPane(splitType); + const float contentWidth = ::base::saturated_cast(_tabContent.ActualWidth()); + const float contentHeight = ::base::saturated_cast(_tabContent.ActualHeight()); + const winrt::Windows::Foundation::Size availableSpace{ contentWidth, contentHeight }; - if (!canSplit && _startupState == StartupState::Initialized) + auto realSplitType = splitType; + if (realSplitType == SplitState::Automatic) { - return; + realSplitType = focusedTab->PreCalculateAutoSplit(availableSpace); } - auto realSplitType = splitType; - if (realSplitType == SplitState::Automatic && _startupState < StartupState::Initialized) + const auto canSplit = focusedTab->PreCalculateCanSplit(realSplitType, availableSpace); + if (!canSplit) { - float contentWidth = gsl::narrow_cast(_tabContent.ActualWidth()); - float contentHeight = gsl::narrow_cast(_tabContent.ActualHeight()); - realSplitType = focusedTab->PreCalculateAutoSplit({ contentWidth, contentHeight }); + return; } TermControl newControl{ controlSettings, controlConnection }; @@ -1930,9 +1947,13 @@ namespace winrt::TerminalApp::implementation // - actions: a list of Actions to process on startup. // Return Value: // - - void TerminalPage::SetStartupActions(std::deque& actions) + void TerminalPage::SetStartupActions(std::vector& actions) { - _startupActions = actions; + // The fastest way to copy all the actions out of the std::vector and + // put them into a winrt::IVector is by making a copy, then moving the + // copy into the winrt vector ctor. + auto listCopy = actions; + _startupActions = winrt::single_threaded_vector(std::move(listCopy)); } winrt::TerminalApp::IDialogPresenter TerminalPage::DialogPresenter() const @@ -2192,6 +2213,49 @@ namespace winrt::TerminalApp::implementation // TODO GH#3327: Look at what to do with the NC area when we have XAML theming } + // Function Description: + // - This is a helper method to get the commandline out of a + // ExecuteCommandline action, break it into subcommands, and attempt to + // parse it into actions. This is used by _HandleExecuteCommandline for + // processing commandlines in the current WT window. + // Arguments: + // - args: the ExecuteCommandlineArgs to synthesize a list of startup actions for. + // Return Value: + // - an empty list if we failed to parse, otherwise a list of actions to execute. + std::vector TerminalPage::ConvertExecuteCommandlineToActions(const TerminalApp::ExecuteCommandlineArgs& args) + { + if (!args || args.Commandline().empty()) + { + return {}; + } + // Convert the commandline into an array of args with + // CommandLineToArgvW, similar to how the app typically does when + // called from the commandline. + int argc = 0; + wil::unique_any argv{ CommandLineToArgvW(args.Commandline().c_str(), &argc) }; + if (argv) + { + std::vector args; + + // Make sure the first argument is wt.exe, because ParseArgs will + // always skip the program name. The particular value of this first + // string doesn't terribly matter. + args.emplace_back(L"wt.exe"); + for (auto& elem : wil::make_range(argv.get(), argc)) + { + args.emplace_back(elem); + } + winrt::array_view argsView{ args }; + + ::TerminalApp::AppCommandlineArgs appArgs; + if (appArgs.ParseArgs(argsView) == 0) + { + return appArgs.GetStartupActions(); + } + } + return {}; + } + void TerminalPage::_CommandPaletteClosed(const IInspectable& /*sender*/, const RoutedEventArgs& /*eventArgs*/) { diff --git a/src/cascadia/TerminalApp/TerminalPage.h b/src/cascadia/TerminalApp/TerminalPage.h index 2aa717cfacd..f78cfe86f23 100644 --- a/src/cascadia/TerminalApp/TerminalPage.h +++ b/src/cascadia/TerminalApp/TerminalPage.h @@ -56,7 +56,8 @@ namespace winrt::TerminalApp::implementation bool Fullscreen() const; bool AlwaysOnTop() const; - void SetStartupActions(std::deque& actions); + void SetStartupActions(std::vector& actions); + static std::vector ConvertExecuteCommandlineToActions(const TerminalApp::ExecuteCommandlineArgs& args); winrt::TerminalApp::IDialogPresenter DialogPresenter() const; void DialogPresenter(winrt::TerminalApp::IDialogPresenter dialogPresenter); @@ -106,8 +107,8 @@ namespace winrt::TerminalApp::implementation winrt::Windows::UI::Xaml::Controls::Grid::LayoutUpdated_revoker _layoutUpdatedRevoker; StartupState _startupState{ StartupState::NotInitialized }; - std::deque _startupActions; - winrt::fire_and_forget _ProcessStartupActions(); + Windows::Foundation::Collections::IVector _startupActions; + winrt::fire_and_forget _ProcessStartupActions(Windows::Foundation::Collections::IVector actions, const bool initial); void _ShowAboutDialog(); void _ShowCloseWarningDialog(); @@ -221,6 +222,7 @@ namespace winrt::TerminalApp::implementation void _HandleSetTabColor(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleOpenTabColorPicker(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleRenameTab(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); + void _HandleExecuteCommandline(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); void _HandleToggleCommandPalette(const IInspectable& sender, const TerminalApp::ActionEventArgs& args); // Make sure to hook new actions up in _RegisterActionCallbacks! #pragma endregion diff --git a/src/cascadia/TerminalApp/lib/pch.h b/src/cascadia/TerminalApp/lib/pch.h index 52a51a3bd53..4cb7a49f1e3 100644 --- a/src/cascadia/TerminalApp/lib/pch.h +++ b/src/cascadia/TerminalApp/lib/pch.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 9578166b14d..8838eb5df11 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -17,6 +17,7 @@ using namespace ::Microsoft::Console::Types; using namespace ::Microsoft::Terminal::Core; +using namespace winrt::Windows::Graphics::Display; using namespace winrt::Windows::UI::Xaml; using namespace winrt::Windows::UI::Xaml::Input; using namespace winrt::Windows::UI::Xaml::Automation::Peers; @@ -628,7 +629,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // Then, using the font, get the number of characters that can fit. // Resize our terminal connection to match that size, and initialize the terminal with that size. const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, windowSize); - THROW_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); + LOG_IF_FAILED(dxEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); // Update DxEngine's SelectionBackground dxEngine->SetSelectionBackground(_settings.SelectionBackground()); @@ -2274,28 +2275,65 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // as font size, scrollbar and other control scaling, etc. Make sure the // caller knows what monitor the control is about to appear on. // Return Value: - // - a point containing the requested dimensions in pixels. - winrt::Windows::Foundation::Point TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi) + // - a size containing the requested dimensions in pixels. + winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(IControlSettings const& settings, const uint32_t dpi) { + // If the settings have negative or zero row or column counts, ignore those counts. + // (The lower TerminalCore layer also has upper bounds as well, but at this layer + // we may eventually impose different ones depending on how many pixels we can address.) + const auto cols = ::base::saturated_cast(std::max(settings.InitialCols(), 1)); + const auto rows = ::base::saturated_cast(std::max(settings.InitialRows(), 1)); + + const winrt::Windows::Foundation::Size initialSize{ cols, rows }; + + return GetProposedDimensions(initialSize, + settings.FontSize(), + settings.FontWeight(), + settings.FontFace(), + settings.ScrollState(), + settings.Padding(), + dpi); + } + + // Function Description: + // - Determines how much space (in pixels) an app would need to reserve to + // create a control with the settings stored in the settings param. This + // accounts for things like the font size and face, the initialRows and + // initialCols, and scrollbar visibility. The returned sized is based upon + // the provided DPI value + // Arguments: + // - initialSizeInChars: The size to get the proposed dimensions for. + // - fontHeight: The font height to use to calculate the proposed size for. + // - fontWeight: The font weight to use to calculate the proposed size for. + // - fontFace: The font name to use to calculate the proposed size for. + // - scrollState: The ScrollbarState to use to calculate the proposed size for. + // - padding: The padding to use to calculate the proposed size for. + // - dpi: The DPI we should create the terminal at. This affects things such + // as font size, scrollbar and other control scaling, etc. Make sure the + // caller knows what monitor the control is about to appear on. + // Return Value: + // - a size containing the requested dimensions in pixels. + winrt::Windows::Foundation::Size TermControl::GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars, + const int32_t& fontHeight, + const winrt::Windows::UI::Text::FontWeight& fontWeight, + const winrt::hstring& fontFace, + const Microsoft::Terminal::Settings::ScrollbarState& scrollState, + const winrt::hstring& padding, + const uint32_t dpi) + { + const auto cols = ::base::saturated_cast(initialSizeInChars.Width); + const auto rows = ::base::saturated_cast(initialSizeInChars.Height); + // Initialize our font information. - const auto fontFace = settings.FontFace(); - const short fontHeight = gsl::narrow_cast(settings.FontSize()); - const auto fontWeight = settings.FontWeight(); // The font width doesn't terribly matter, we'll only be using the // height to look it up // The other params here also largely don't matter. // The family is only used to determine if the font is truetype or // not, but DX doesn't use that info at all. // The Codepage is additionally not actually used by the DX engine at all. - FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, fontHeight }, CP_UTF8, false }; + FontInfo actualFont = { fontFace, 0, fontWeight.Weight, { 0, gsl::narrow_cast(fontHeight) }, CP_UTF8, false }; FontInfoDesired desiredFont = { actualFont }; - // If the settings have negative or zero row or column counts, ignore those counts. - // (The lower TerminalCore layer also has upper bounds as well, but at this layer - // we may eventually impose different ones depending on how many pixels we can address.) - const auto cols = std::max(settings.InitialCols(), 1); - const auto rows = std::max(settings.InitialRows(), 1); - // Create a DX engine and initialize it with our font and DPI. We'll // then use it to measure how much space the requested rows and columns // will take up. @@ -2315,13 +2353,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation double width = cols * fontSize.X; // Reserve additional space if scrollbar is intended to be visible - if (settings.ScrollState() == ScrollbarState::Visible) + if (scrollState == ScrollbarState::Visible) { width += scrollbarSize; } double height = rows * fontSize.Y; - auto thickness = _ParseThicknessFromPadding(settings.Padding()); + auto thickness = _ParseThicknessFromPadding(padding); // GH#2061 - make sure to account for the size the padding _will be_ scaled to width += scale * (thickness.Left + thickness.Right); height += scale * (thickness.Top + thickness.Bottom); @@ -2354,21 +2392,41 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation // have a visible character. winrt::Windows::Foundation::Size TermControl::MinimumSize() { - const auto fontSize = _actualFont.GetSize(); - double width = fontSize.X; - double height = fontSize.Y; - // Reserve additional space if scrollbar is intended to be visible - if (_settings.ScrollState() == ScrollbarState::Visible) + if (_initializedTerminal) { - width += ScrollBar().ActualWidth(); - } + const auto fontSize = _actualFont.GetSize(); + double width = fontSize.X; + double height = fontSize.Y; + // Reserve additional space if scrollbar is intended to be visible + if (_settings.ScrollState() == ScrollbarState::Visible) + { + width += ScrollBar().ActualWidth(); + } - // Account for the size of any padding - const auto padding = GetPadding(); - width += padding.Left + padding.Right; - height += padding.Top + padding.Bottom; + // Account for the size of any padding + const auto padding = GetPadding(); + width += padding.Left + padding.Right; + height += padding.Top + padding.Bottom; - return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; + return { gsl::narrow_cast(width), gsl::narrow_cast(height) }; + } + else + { + // If the terminal hasn't been initialized yet, then the font size will + // have dimensions {1, fontSize.Y}, which can mess with consumers of + // this method. In that case, we'll need to pre-calculate the font + // width, before we actually have a renderer or swapchain. + const winrt::Windows::Foundation::Size minSize{ 1, 1 }; + const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel(); + const auto dpi = ::base::saturated_cast(USER_DEFAULT_SCREEN_DPI * scaleFactor); + return GetProposedDimensions(minSize, + _settings.FontSize(), + _settings.FontWeight(), + _settings.FontFace(), + _settings.ScrollState(), + _settings.Padding(), + dpi); + } } // Method Description: diff --git a/src/cascadia/TerminalControl/TermControl.h b/src/cascadia/TerminalControl/TermControl.h index 3879c4b3835..783ca85d967 100644 --- a/src/cascadia/TerminalControl/TermControl.h +++ b/src/cascadia/TerminalControl/TermControl.h @@ -100,7 +100,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation TerminalConnection::ConnectionState ConnectionState() const; - static Windows::Foundation::Point GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi); + static Windows::Foundation::Size GetProposedDimensions(Microsoft::Terminal::Settings::IControlSettings const& settings, const uint32_t dpi); + static Windows::Foundation::Size GetProposedDimensions(const winrt::Windows::Foundation::Size& initialSizeInChars, + const int32_t& fontSize, + const winrt::Windows::UI::Text::FontWeight& fontWeight, + const winrt::hstring& fontFace, + const Microsoft::Terminal::Settings::ScrollbarState& scrollState, + const winrt::hstring& padding, + const uint32_t dpi); // clang-format off // -------------------------------- WinRT Events --------------------------------- diff --git a/src/cascadia/TerminalControl/TermControl.idl b/src/cascadia/TerminalControl/TermControl.idl index 0d99a87b530..d5454e50578 100644 --- a/src/cascadia/TerminalControl/TermControl.idl +++ b/src/cascadia/TerminalControl/TermControl.idl @@ -35,7 +35,7 @@ namespace Microsoft.Terminal.TerminalControl TermControl(); TermControl(Microsoft.Terminal.Settings.IControlSettings settings, Microsoft.Terminal.TerminalConnection.ITerminalConnection connection); - static Windows.Foundation.Point GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi); + static Windows.Foundation.Size GetProposedDimensions(Microsoft.Terminal.Settings.IControlSettings settings, UInt32 dpi); void UpdateSettings(Microsoft.Terminal.Settings.IControlSettings newSettings); diff --git a/src/cascadia/WindowsTerminal/AppHost.cpp b/src/cascadia/WindowsTerminal/AppHost.cpp index b2c3c32a3b5..36ec74db1a7 100644 --- a/src/cascadia/WindowsTerminal/AppHost.cpp +++ b/src/cascadia/WindowsTerminal/AppHost.cpp @@ -286,9 +286,9 @@ void AppHost::_HandleCreateWindow(const HWND hwnd, RECT proposedRect, winrt::Ter auto initialSize = _logic.GetLaunchDimensions(dpix); const short islandWidth = Utils::ClampToShortMax( - static_cast(ceil(initialSize.X)), 1); + static_cast(ceil(initialSize.Width)), 1); const short islandHeight = Utils::ClampToShortMax( - static_cast(ceil(initialSize.Y)), 1); + static_cast(ceil(initialSize.Height)), 1); // Get the size of a window we'd need to host that client rect. This will // add the titlebar space.