diff --git a/src/cascadia/TerminalApp/Pane.cpp b/src/cascadia/TerminalApp/Pane.cpp index eec3ffdc7f8..f3dde0fa2fa 100644 --- a/src/cascadia/TerminalApp/Pane.cpp +++ b/src/cascadia/TerminalApp/Pane.cpp @@ -2849,13 +2849,12 @@ void Pane::_AdvanceSnappedDimension(const bool widthOrHeight, LayoutSizeNode& si // Method Description: // - Get the absolute minimum size that this pane can be resized to and still -// have 1x1 character visible, in each of its children. If we're a leaf, we'll -// include the space needed for borders _within_ us. +// satisfy each child's MinimumSize. If we're a leaf, we'll include the +// space needed for borders _within_ us. // Arguments: // - // Return Value: -// - The minimum size that this pane can be resized to and still have a visible -// character. +// - The minimum size that this pane can be resized to and still fit its content. Size Pane::_GetMinSize() const { if (_IsLeaf()) diff --git a/src/cascadia/TerminalControl/ControlCore.cpp b/src/cascadia/TerminalControl/ControlCore.cpp index 87ef33229ae..8f9d56fa1f1 100644 --- a/src/cascadia/TerminalControl/ControlCore.cpp +++ b/src/cascadia/TerminalControl/ControlCore.cpp @@ -414,12 +414,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation LOG_IF_FAILED(_renderEngine->SetWindowSize({ viewInPixels.Width(), viewInPixels.Height() })); const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); - const auto width = vp.Width(); - const auto height = vp.Height(); + const til::size viewportSize{ Utils::ClampToShortMax(vp.Width(), MINIMUM_VISIBLE_CELLS), + Utils::ClampToShortMax(vp.Height(), MINIMUM_VISIBLE_CELLS) }; if (_connection) { - _connection.Resize(height, width); + _connection.Resize(viewportSize.height, viewportSize.width); } if (_owningHwnd != 0) @@ -430,10 +430,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation } } - // Override the default width and height to match the size of the swapChainPanel - const til::size viewportSize{ Utils::ClampToShortMax(width, 1), - Utils::ClampToShortMax(height, 1) }; - // TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1 _terminal->Create(viewportSize, Utils::ClampToShortMax(_settings.HistorySize(), 0), *_renderer); _terminal->UpdateSettings(_settings); @@ -1228,10 +1224,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation auto cx = gsl::narrow_cast(lrint(_panelWidth * _compositionScale)); auto cy = gsl::narrow_cast(lrint(_panelHeight * _compositionScale)); - // Don't actually resize so small that a single character wouldn't fit - // in either dimension. The buffer really doesn't like being size 0. - cx = std::max(cx, _actualFont.GetSize().width); - cy = std::max(cy, _actualFont.GetSize().height); + // Don't resize below the visible minimum. A 1-cell viewport can hang + // TextBuffer::Reflow on a wide glyph (GH#19996). The buffer also + // doesn't like being size 0. + const auto cell = _actualFont.GetSize(); + cx = std::max(cx, cell.width * MINIMUM_VISIBLE_CELLS); + cy = std::max(cy, cell.height * MINIMUM_VISIBLE_CELLS); // Convert our new dimensions to characters const auto viewInPixels = Viewport::FromDimensions({ 0, 0 }, { cx, cy }); @@ -1247,7 +1245,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation // If this function succeeds with S_FALSE, then the terminal didn't // actually change size. No need to notify the connection of this no-op. - const auto hr = _terminal->UserResize({ vp.Width(), vp.Height() }); + const auto cols = std::max(vp.Width(), MINIMUM_VISIBLE_CELLS); + const auto rows = std::max(vp.Height(), MINIMUM_VISIBLE_CELLS); + const auto hr = _terminal->UserResize({ cols, rows }); if (FAILED(hr) || hr == S_FALSE) { return; @@ -1255,7 +1255,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation if (_connection) { - _connection.Resize(vp.Height(), vp.Width()); + _connection.Resize(rows, cols); } // TermControl will call Search() once the OutputIdle even fires after 100ms. diff --git a/src/cascadia/TerminalControl/HwndTerminal.cpp b/src/cascadia/TerminalControl/HwndTerminal.cpp index 2c792e81c7e..6ad4b41caa0 100644 --- a/src/cascadia/TerminalControl/HwndTerminal.cpp +++ b/src/cascadia/TerminalControl/HwndTerminal.cpp @@ -434,10 +434,10 @@ HRESULT HwndTerminal::Refresh(const til::size windowSize, _Out_ til::size* dimen const auto viewInPixels = Viewport::FromDimensions({}, windowSize); const auto vp = _renderEngine->GetViewportInCharacters(viewInPixels); - // Guard against resizing the window to 0 columns/rows, which the text buffer classes don't really support. + // Guard against resizing below the visible minimum (GH#19996). auto size = vp.Dimensions(); - size.width = std::max(size.width, 1); - size.height = std::max(size.height, 1); + size.width = std::max(size.width, MINIMUM_VISIBLE_CELLS); + size.height = std::max(size.height, MINIMUM_VISIBLE_CELLS); // If this function succeeds with S_FALSE, then the terminal didn't // actually change size. No need to notify the connection of this diff --git a/src/cascadia/TerminalControl/TermControl.cpp b/src/cascadia/TerminalControl/TermControl.cpp index 1557b968660..498cd354fdc 100644 --- a/src/cascadia/TerminalControl/TermControl.cpp +++ b/src/cascadia/TerminalControl/TermControl.cpp @@ -4,6 +4,7 @@ #include "pch.h" #include "TermControl.h" +#include #include #include "TermControlAutomationPeer.h" @@ -2741,16 +2742,18 @@ namespace winrt::Microsoft::Terminal::Control::implementation int32_t commandlineRows) { // If the settings have negative or zero row or column counts, ignore those counts. + // Floor at MINIMUM_VISIBLE_CELLS so wt --size 1,1 / initialCols:1 cannot + // open a 1-cell viewport (GH#19996). // (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 = static_cast(std::max(commandlineCols > 0 ? commandlineCols : settings.InitialCols(), - 1)); + MINIMUM_VISIBLE_CELLS)); const auto rows = static_cast(std::max(commandlineRows > 0 ? commandlineRows : settings.InitialRows(), - 1)); + MINIMUM_VISIBLE_CELLS)); const winrt::Windows::Foundation::Size initialSize{ cols, rows }; @@ -2842,8 +2845,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation // - a size containing the requested dimensions in pixels. winrt::Windows::Foundation::Size TermControl::GetNewDimensions(const winrt::Windows::Foundation::Size& sizeInChars) { - const auto cols = ::base::saturated_cast(sizeInChars.Width); - const auto rows = ::base::saturated_cast(sizeInChars.Height); + const auto cols = std::max(::base::saturated_cast(sizeInChars.Width), MINIMUM_VISIBLE_CELLS); + const auto rows = std::max(::base::saturated_cast(sizeInChars.Height), MINIMUM_VISIBLE_CELLS); const auto fontSize = _core.FontSize(); const auto scrollState = _core.Settings().ScrollState(); const auto padding = _core.Settings().Padding(); @@ -2884,20 +2887,22 @@ namespace winrt::Microsoft::Terminal::Control::implementation // Method Description: // - Get the absolute minimum size that this control can be resized to and - // still have 1x1 character visible. This includes the space needed for + // still have 2x2 characters visible. This includes the space needed for // the scrollbar and the padding. + // 2x2 is the VT theoretical minimum (DECSTBM / DECSLRM). A 1-cell + // viewport can hang TextBuffer::Reflow on a wide glyph (GH#19996). // Arguments: // - // Return Value: // - The minimum size that this terminal control can be resized to and still - // have a visible character. + // have a usable character grid. winrt::Windows::Foundation::Size TermControl::MinimumSize() { if (_initializedTerminal) { const auto fontSize = _core.FontSizeInDips(); - auto width = fontSize.Width; - auto height = fontSize.Height; + auto width = fontSize.Width * MINIMUM_VISIBLE_CELLS; + auto height = fontSize.Height * MINIMUM_VISIBLE_CELLS; // Reserve additional space if scrollbar is intended to be visible if (_core.Settings().ScrollState() != ScrollbarState::Hidden) { diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 0746066b751..38874bc8c47 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -42,6 +42,8 @@ Terminal::Terminal(TestDummyMarker) : void Terminal::Create(til::size viewportSize, til::CoordType scrollbackLines, Renderer& renderer) { + viewportSize.width = std::max(viewportSize.width, MINIMUM_VISIBLE_CELLS); + viewportSize.height = std::max(viewportSize.height, MINIMUM_VISIBLE_CELLS); _mutableViewport = Viewport::FromDimensions({ 0, 0 }, viewportSize); _scrollbackLines = scrollbackLines; const til::size bufferSize{ viewportSize.width, @@ -75,8 +77,8 @@ void Terminal::HardResetWithoutErase() void Terminal::CreateFromSettings(ICoreSettings settings, Renderer& renderer) { - const til::size viewportSize{ Utils::ClampToShortMax(settings.InitialCols(), 1), - Utils::ClampToShortMax(settings.InitialRows(), 1) }; + const til::size viewportSize{ Utils::ClampToShortMax(settings.InitialCols(), MINIMUM_VISIBLE_CELLS), + Utils::ClampToShortMax(settings.InitialRows(), MINIMUM_VISIBLE_CELLS) }; // TODO:MSFT:20642297 - Support infinite scrollback here, if HistorySize is -1 Create(viewportSize, Utils::ClampToShortMax(settings.HistorySize(), 0), renderer); @@ -289,9 +291,14 @@ std::wstring_view Terminal::GetWorkingDirectory() noexcept // - S_OK if we successfully resized the terminal, S_FALSE if there was // nothing to do (the viewportSize is the same as our current size), or an // appropriate HRESULT for failing to resize. -[[nodiscard]] HRESULT Terminal::UserResize(const til::size viewportSize) noexcept +[[nodiscard]] HRESULT Terminal::UserResize(const til::size requestedSize) noexcept try { + const til::size viewportSize{ + std::max(requestedSize.width, MINIMUM_VISIBLE_CELLS), + std::max(requestedSize.height, MINIMUM_VISIBLE_CELLS) + }; + const auto oldDimensions = _GetMutableViewport().Dimensions(); if (viewportSize == oldDimensions) { diff --git a/src/cascadia/TerminalSettingsEditor/Launch.xaml b/src/cascadia/TerminalSettingsEditor/Launch.xaml index 22c7a161bd6..6acf972ba09 100644 --- a/src/cascadia/TerminalSettingsEditor/Launch.xaml +++ b/src/cascadia/TerminalSettingsEditor/Launch.xaml @@ -28,7 +28,7 @@ TargetType="muxc:NumberBox"> - +