Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show a double width cursor for double width characters #5319

Merged
21 commits merged into from
Apr 15, 2020
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions src/buffer/out/textBuffer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1967,11 +1967,8 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
const std::optional<Viewport> lastCharacterViewport,
std::optional<std::reference_wrapper<PositionInformation>> 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
Expand Down Expand Up @@ -2197,8 +2194,5 @@ HRESULT TextBuffer::Reflow(TextBuffer& oldBuffer,
newCursor.SetSize(ulSize);
}

newCursor.EndDeferDrawing();
oldCursor.EndDeferDrawing();

return hr;
}
31 changes: 25 additions & 6 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ 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.
_buffer->GetCursor().StartDeferDrawing();

// First allocate a new text buffer to take the place of the current one.
std::unique_ptr<TextBuffer> newTextBuffer;
try
Expand All @@ -199,6 +202,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.
Expand All @@ -214,15 +219,25 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting
oldRows.visibleViewportTop = newVisibleTop;

const std::optional<short> oldViewStart{ oldViewportTop };
RETURN_IF_FAILED(TextBuffer::Reflow(*_buffer.get(),
*newTextBuffer.get(),
_mutableViewport,
{ oldRows }));
const HRESULT reflowHR = TextBuffer::Reflow(*_buffer.get(),
*newTextBuffer.get(),
_mutableViewport,
{ oldRows });

if (FAILED(reflowHR))
{
_buffer->GetCursor().EndDeferDrawing();
return reflowHR;
}

newViewportTop = oldRows.mutableViewportTop;
newVisibleTop = oldRows.visibleViewportTop;
}
CATCH_RETURN();
catch (...)
{
_buffer->GetCursor().EndDeferDrawing();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In screenInfo, you said...

// Only tell _textBuffer to EndDefer (and not newTextBuffer) because whether or not Reflow failed,
// _textBuffer will end up being the buffer that we use after returning.

Should we just CATCH_RETURN() here then?

Also, if we return on line 221, would we be fine?

Copy link
Contributor Author

@leonMSFT leonMSFT Apr 13, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I wanted to make sure _buffer needs to EndDefer no matter what before we get out of this function. If the try failed at all and entered the catch, I need to call EndDeferDrawing() before we return the exception. ScreenInfo seemed to be slightly different in that there wasn't a try-catch wrapping the Reflow call.

I actually did not notice the RETURN_IF_FAILED for Reflow... Does that just totally skip the catch block? If so, I need to figure out where to EndDefer for _buffer 🤔

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually did not notice the RETURN_IF_FAILED for Reflow... Does that just totally skip the catch block? If so, I need to figure out where to EndDefer for _buffer 🤔

From a glimpse at RETURN_IF_FAILED, yup. We'd return so the catch block never gets executed.

You could probably get around it with a wil::scope_exit though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I basically just separated out the RETURN_IF_FAILED call into its innards, but slapped an EndDefer call before it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd somewhat prefer a scope_exit if possible. I'm happy to merge this as-is if a scoped end-defer isn't easy, but i'm worried that we have three different "end defer" locations here, and they refer to multiple different text buffers. idk

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh crap I'm so sorry @carlos-zamora 😢 , I didn't refresh my tab this morning when I replied to this thread so I didn't see the scope_exit comment before pushing my wacky change! Both of you are totally right, it's a much better approach 😅

RETURN_CAUGHT_EXCEPTION();
}

// Conpty resizes a little oddly - if the height decreased, and there were
// blank lines at the bottom, those lines will get trimmed. If there's not
Expand Down Expand Up @@ -329,6 +344,9 @@ void Terminal::UpdateSettings(winrt::Microsoft::Terminal::Settings::ICoreSetting

_buffer.swap(newTextBuffer);

// Now that the new buffer was swapped to be Terminal's buffer, we can tell its cursor to start drawing again.
_buffer->GetCursor().EndDeferDrawing();

// GH#3494: Maintain scrollbar position during resize
// Make sure that we don't scroll past the mutableViewport at the bottom of the buffer
newVisibleTop = std::min(newVisibleTop, _mutableViewport.Top());
Expand Down Expand Up @@ -875,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();
leonMSFT marked this conversation as resolved.
Show resolved Hide resolved
_buffer->GetCursor().SetIsOn(isOn);
}

Expand Down
5 changes: 3 additions & 2 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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<Microsoft::Console::Render::RenderOverlay> GetOverlays() const noexcept override;
const bool IsGridLineDrawingAllowed() noexcept override;
Expand All @@ -171,7 +172,7 @@ class Microsoft::Terminal::Core::Terminal final :
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;

void SetCursorOn(const bool isOn) noexcept;
void SetCursorOn(const bool isOn);
bool IsCursorBlinkingAllowed() const noexcept;

#pragma region TextSelection
Expand Down
6 changes: 4 additions & 2 deletions src/cascadia/TerminalCore/terminalrenderdata.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<RenderOverlay> Terminal::GetOverlays() const noexcept
Expand Down
43 changes: 43 additions & 0 deletions src/cascadia/UnitTests_TerminalCore/TerminalApiTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
};

Expand Down Expand Up @@ -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());
}
8 changes: 8 additions & 0 deletions src/host/screenInfo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1403,6 +1403,10 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
// Save cursor's relative height versus the viewport
SHORT const sCursorHeightInViewportBefore = _textBuffer->GetCursor().GetPosition().Y - _viewport.Top();

// Tell the both the new and old buffer to not redraw its cursor as we reflow and swap them.
newTextBuffer->GetCursor().StartDeferDrawing();
_textBuffer->GetCursor().StartDeferDrawing();

HRESULT hr = TextBuffer::Reflow(*_textBuffer.get(), *newTextBuffer.get(), std::nullopt, std::nullopt);

if (SUCCEEDED(hr))
Expand All @@ -1417,6 +1421,10 @@ bool SCREEN_INFORMATION::IsMaximizedY() const
_textBuffer.swap(newTextBuffer);
}

// Only tell _textBuffer to EndDefer (and not newTextBuffer) because whether or not Reflow failed,
// _textBuffer will end up being the buffer that we use after returning.
_textBuffer->GetCursor().EndDeferDrawing();

return NTSTATUS_FROM_HRESULT(hr);
}

Expand Down
2 changes: 1 addition & 1 deletion src/renderer/inc/IRenderData.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down