diff --git a/src/buffer/out/textBuffer.cpp b/src/buffer/out/textBuffer.cpp index eaa41dc415a..1fbf1bbea1d 100644 --- a/src/buffer/out/textBuffer.cpp +++ b/src/buffer/out/textBuffer.cpp @@ -1967,11 +1967,8 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, const std::optional lastCharacterViewport, std::optional> positionInfo) { - Cursor& oldCursor = oldBuffer.GetCursor(); + const Cursor& oldCursor = oldBuffer.GetCursor(); Cursor& newCursor = newBuffer.GetCursor(); - // skip any drawing updates that might occur as we manipulate the new buffer - oldCursor.StartDeferDrawing(); - newCursor.StartDeferDrawing(); // We need to save the old cursor position so that we can // place the new cursor back on the equivalent character in @@ -2197,8 +2194,5 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer, newCursor.SetSize(ulSize); } - newCursor.EndDeferDrawing(); - oldCursor.EndDeferDrawing(); - return hr; } diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index 2da24a11d82..03764a13835 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -190,6 +190,11 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting // bottom in the new buffer as well. Track that case now. const bool originalOffsetWasZero = _scrollOffset == 0; + // skip any drawing updates that might occur until we swap _buffer with the new buffer or if we exit early. + _buffer->GetCursor().StartDeferDrawing(); + // we're capturing _buffer by reference here because when we exit, we want to EndDefer on the current active buffer. + auto endDefer = wil::scope_exit([&]() noexcept { _buffer->GetCursor().EndDeferDrawing(); }); + // First allocate a new text buffer to take the place of the current one. std::unique_ptr newTextBuffer; try @@ -199,6 +204,8 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting 0, // temporarily set size to 0 so it won't render. _buffer->GetRenderTarget()); + newTextBuffer->GetCursor().StartDeferDrawing(); + // Build a PositionInformation to track the position of both the top of // the mutable viewport and the top of the visible viewport in the new // buffer. @@ -886,8 +893,9 @@ CATCH_LOG() // Visible, then it will immediately become visible. // Arguments: // - isVisible: whether the cursor should be visible -void Terminal::SetCursorOn(const bool isOn) noexcept +void Terminal::SetCursorOn(const bool isOn) { + auto lock = LockForWriting(); _buffer->GetCursor().SetIsOn(isOn); } diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index ed77aec2f23..313f0f6b34e 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -11,6 +11,7 @@ #include "../../terminal/input/terminalInput.hpp" #include "../../types/inc/Viewport.hpp" +#include "../../types/inc/GlyphWidth.hpp" #include "../../types/IUiaData.h" #include "../../cascadia/terminalcore/ITerminalApi.hpp" #include "../../cascadia/terminalcore/ITerminalInput.hpp" @@ -147,7 +148,7 @@ class Microsoft::Terminal::Core::Terminal final : ULONG GetCursorPixelWidth() const noexcept override; CursorType GetCursorStyle() const noexcept override; COLORREF GetCursorColor() const noexcept override; - bool IsCursorDoubleWidth() const noexcept override; + bool IsCursorDoubleWidth() const override; bool IsScreenReversed() const noexcept override; const std::vector GetOverlays() const noexcept override; const bool IsGridLineDrawingAllowed() noexcept override; @@ -171,7 +172,7 @@ class Microsoft::Terminal::Core::Terminal final : void SetCursorPositionChangedCallback(std::function pfn) noexcept; void SetBackgroundCallback(std::function pfn) noexcept; - void SetCursorOn(const bool isOn) noexcept; + void SetCursorOn(const bool isOn); bool IsCursorBlinkingAllowed() const noexcept; #pragma region TextSelection diff --git a/src/cascadia/TerminalCore/terminalrenderdata.cpp b/src/cascadia/TerminalCore/terminalrenderdata.cpp index e49859cb374..01672a047d1 100644 --- a/src/cascadia/TerminalCore/terminalrenderdata.cpp +++ b/src/cascadia/TerminalCore/terminalrenderdata.cpp @@ -105,9 +105,11 @@ COLORREF Terminal::GetCursorColor() const noexcept return _buffer->GetCursor().GetColor(); } -bool Terminal::IsCursorDoubleWidth() const noexcept +bool Terminal::IsCursorDoubleWidth() const { - return false; + const auto position = _buffer->GetCursor().GetPosition(); + TextBufferTextIterator it(TextBufferCellIterator(*_buffer, position)); + return IsGlyphFullWidth(*it); } const std::vector Terminal::GetOverlays() const noexcept diff --git a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp index 3e715e44fd5..b236765bb43 100644 --- a/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp +++ b/src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp @@ -33,6 +33,7 @@ namespace TerminalCoreUnitTests // This test ensures that Terminal::_WriteBuffer doesn't get stuck when // PrintString() is called with more code units than the buffer width. TEST_METHOD(PrintStringOfSurrogatePairs); + TEST_METHOD(CheckDoubleWidthCursor); }; }; @@ -208,3 +209,45 @@ void TerminalApiTest::CursorVisibilityViaStateMachine() VERIFY_IS_FALSE(cursor.IsBlinkingAllowed()); VERIFY_IS_FALSE(cursor.IsVisible()); } + +void TerminalApiTest::CheckDoubleWidthCursor() +{ + DummyRenderTarget renderTarget; + Terminal term; + term.Create({ 100, 100 }, 0, renderTarget); + + auto& tbi = *(term._buffer); + auto& stateMachine = *(term._stateMachine); + auto& cursor = tbi.GetCursor(); + + // Lets stuff the buffer with single width characters, + // but leave the last two columns empty for double width. + std::wstring singleWidthText; + singleWidthText.reserve(98); + for (size_t i = 0; i < 98; ++i) + { + singleWidthText.append(L"A"); + } + stateMachine.ProcessString(singleWidthText); + VERIFY_IS_TRUE(cursor.GetPosition().X == 98); + + // Stuff two double width characters. + std::wstring doubleWidthText{ L"我愛" }; + stateMachine.ProcessString(doubleWidthText); + + // The last 'A' + term.SetCursorPosition(97, 0); + VERIFY_IS_FALSE(term.IsCursorDoubleWidth()); + + // This and the next CursorPos are taken up by '我‘ + term.SetCursorPosition(98, 0); + VERIFY_IS_TRUE(term.IsCursorDoubleWidth()); + term.SetCursorPosition(99, 0); + VERIFY_IS_TRUE(term.IsCursorDoubleWidth()); + + // This and the next CursorPos are taken up by ’愛‘ + term.SetCursorPosition(0, 1); + VERIFY_IS_TRUE(term.IsCursorDoubleWidth()); + term.SetCursorPosition(1, 1); + VERIFY_IS_TRUE(term.IsCursorDoubleWidth()); +} diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 10a490fb93c..75e16af1ade 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1403,6 +1403,12 @@ bool SCREEN_INFORMATION::IsMaximizedY() const // Save cursor's relative height versus the viewport SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top(); + // skip any drawing updates that might occur until we swap _textBuffer with the new buffer or we exit early. + newTextBuffer->GetCursor().StartDeferDrawing(); + _textBuffer->GetCursor().StartDeferDrawing(); + // we're capturing _textBuffer by reference here because when we exit, we want to EndDefer on the current active buffer. + auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); }); + HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt); if (SUCCEEDED(hr)) diff --git a/src/renderer/inc/IRenderData.hpp b/src/renderer/inc/IRenderData.hpp index 860537514ff..c504ae2dbb3 100644 --- a/src/renderer/inc/IRenderData.hpp +++ b/src/renderer/inc/IRenderData.hpp @@ -58,7 +58,7 @@ namespace Microsoft::Console::Render virtual CursorType GetCursorStyle() const noexcept = 0; virtual ULONG GetCursorPixelWidth() const noexcept = 0; virtual COLORREF GetCursorColor() const noexcept = 0; - virtual bool IsCursorDoubleWidth() const noexcept = 0; + virtual bool IsCursorDoubleWidth() const = 0; virtual bool IsScreenReversed() const noexcept = 0;