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

Conversation

leonMSFT
Copy link
Contributor

Summary of the Pull Request

This PR will allow the cursor to be double width when on top of a double width character. This required changing IsCursorDoubleWidth to check whether the glyph the cursor's on top of is double width. This code is exactly the same as the original PR that addressed this issue in #2932. That one got reverted at some point due to the crashes related to it, but due to a combination of Terminal having come further since that PR and other changes to address use-after-frees, some of the crashes may/may not be relevant now. The ones that seemed to be relevant/repro-able, I attempt to address in this PR.

The IsCursorDoubleWidth check would fail during the TextBuffer::Reflow call inside of Terminal::UserResize occasionally, particularly when newCursor.EndDeferDrawing() is called. This is because when we tell the newCursor to EndDefer, the renderer will attempt to redraw the cursor. As part of this redraw, it'll ask if IsCursorDoubleWidth, and if the renderer managed to ask this before UserResize swapped out the old buffer with the new one from Reflow, the renderer will be asking the old buffer if its out-of-bounds cursor is double width. This was pretty easily repro'd using cmatrix -u0 and resizing the window like a madman.

As a solution, I've moved the Start/End DeferDrawing calls out of Reflow and into UserResize. This way, I can "clamp" the portion of the code where the newBuffer is getting created and reflowed and swapped into the Terminal buffer, and only allow the renderer to draw once the swap is done. This also means that ConHost's ResizeWithReflow needed to change slightly.

In addition, I've added a WriteLock to SetCursorOn. It was mentioned as a fix for a crash in #2965 (although I can't repro), and I also figured it would be good to try to emulate where ConHost locks with regards to Cursor operations, and this seemed to be one that we were missing.

PR Checklist

Validation Steps Performed

Manual validation that the cursor is indeed chonky, added a test case to check that we are correctly saying that the cursor is double width (not too sure if I put it in the right place). Also open to other test case ideas and thoughts on what else I should be careful for since I am quite nervous about what other crashes might occur.

@skyline75489
Copy link
Collaborator

A note about performance: when #2932 was born, the overall performace penalty is somewhat observable, because IsCursorDoubleWidth is called very frequently. Perhaps not like #5288 kinda frequency, but still there are many routines are using IsCursorDoubleWidth.

By the way, I can not repro the crash described in #2965, either.

Copy link
Member

@carlos-zamora carlos-zamora left a comment

Choose a reason for hiding this comment

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

Just a few questions, not blocking, but I'll sign off after I get a response. 😊

src/cascadia/TerminalCore/Terminal.cpp Show resolved Hide resolved
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 😅

@leonMSFT
Copy link
Contributor Author

@skyline75489 That's a good callout with the performance hit, the cursor redraws quite frequently. Maybe it would be better if I somehow saved the answer to IsCursorDoubleWidth as a boolean that's figured out ahead of time (not sure where yet though).

Copy link
Member

@zadjii-msft zadjii-msft left a comment

Choose a reason for hiding this comment

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

Maybe it would be better if I somehow saved the answer to IsCursorDoubleWidth as a boolean that's figured out ahead of time (not sure where yet though).

I'm not sure that's something that's totally cacheable. We might be able to stash that information as the cursor gets moved, but I'd think that the cursor is drawn fewer times than it's moved.

I think the perf hit is probably fine, considering this seems like it's the same implementation that conhost has in RenderData::IsCursorDoubleWidth. It might be a useful exercise though to have a perf trace taken and compare before and after this change though, to prove that nothing dramatic has regressed.

@zadjii-msft zadjii-msft added Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) Product-Terminal The new Windows Terminal. labels Apr 13, 2020
@skyline75489
Copy link
Collaborator

Yeah the caching isn't really a good idea. My point is that we should be vigilant against issues like #5288 . It would be more useful to focus on 'how to update cursor less frequently' than 'how to update cursor fast enough'. The doubled-width cursor may help us find more hot paths, for that matter.

@leonMSFT
Copy link
Contributor Author

After running the bigtext.txt test from Terminal Not Speed Enough, the time taken seems to be roughly in the same range (sometimes even faster but maybe because of many other changes since) when comparing Terminal versions 0.10.781 to my branch on release.

CATCH_RETURN();
catch (...)
{
_buffer->GetCursor().EndDeferDrawing();
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

@@ -190,6 +190,10 @@ 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();
auto endDefer = wil::scope_exit([&]() noexcept { _buffer->GetCursor().EndDeferDrawing(); });
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: might be worth a comment that we're capturing _buffer by reference because either way, when we exit, we want to undefer the current active one

@@ -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();

newTextBuffer->GetCursor().StartDeferDrawing();
_textBuffer->GetCursor().StartDeferDrawing();
auto endDefer = wil::scope_exit([&]() noexcept { _textBuffer->GetCursor().EndDeferDrawing(); });
Copy link
Contributor

Choose a reason for hiding this comment

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

same comment as above :)

@DHowett-MSFT
Copy link
Contributor

mr static

🤣 your commit messages kill me

@leonMSFT
Copy link
Contributor Author

mr static

🤣 your commit messages kill me

they rule with a firm hand 😢

@skyline75489
Copy link
Collaborator

Yeah I totally feel you. I was once bickering with Mr. Static. And I got burned in a way that I’ve never experienced before. I learned a valuable lesson: do not mess with Miss. Noexcept unless you absolutely need to. She looks harmless and gorgeous but in fact she’s very dangerous.

@leonMSFT
Copy link
Contributor Author

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@leonMSFT leonMSFT added the AutoMerge Marked for automatic merge by the bot when requirements are met label Apr 15, 2020
@ghost
Copy link

ghost commented Apr 15, 2020

Hello @leonMSFT!

Because this pull request has the AutoMerge label, I will be glad to assist with helping to merge this pull request once all check-in policies pass.

p.s. you can customize the way I help with merging this pull request, such as holding this pull request until a specific person approves. Simply @mention me (@msftbot) and give me an instruction to get started! Learn more here.

@ghost ghost merged commit fe3f528 into master Apr 15, 2020
@ghost ghost deleted the dev/lelian/bigcursor branch April 15, 2020 19:23
DHowett-MSFT pushed a commit that referenced this pull request Apr 21, 2020
Improve wide glyph support in UIA (GH-4946)
Add enhanced key support for ConPty (GH-5021)
Set DxRenderer non-text alias mode (GH-5149)
Reduce CursorChanged Events for Accessibility (GH-5196)
Add more object ID tracing for Accessibility (GH-5215)
Add SS3 cursor key encoding to ConPty (GH-5383)
UIA: Prevent crash from invalid UTR endpoint comparison (GH-5399)
Make CodepointWidthDetector::GetWidth faster (CC-3727)
add til::math, use it for float conversions to point, size (GH-5150)
Add support for renderer backoff, don't FAIL_FAST on 3x failures, add UI (GH-5353)
Fix a deadlock and a bounding rects issue in UIA (GH-5385)
Don't duplicate spaces from potentially-wrapped EOL-deferred lines (GH-5398)
Reimplement the VT tab stop functionality (CC-5173)
Clamp parameter values to a maximum of 32767. (CC-5200)
Prevent the cursor type being reset when changing the visibility (CC-5251)
Make RIS switch back to the main buffer (CC-5248)
Add support for the DSR-OS operating status report (CC-5300)
Update the virtual bottom location if the cursor moves below it (CC-5317)
ci: run spell check in CI, fix remaining issues (CC-4799) (CC-5352)
Set Cascadia Code as default font (GH-5121)
Show a double width cursor for double width characters (GH-5319)
Delegate all character input to the character event handler (CC-4192)
Update til::bitmap to use dynamic_bitset<> + libpopcnt (GH-5092)
Merged PR 4465022: [Git2Git] Merged PR 4464559: Console: Ingest OSS changes up to e055079
Correct scrolling invalidation region for tmux in pty w/ bitmap (GH-5122)
Render row-by-row instead of invalidating entire screen (GH-5185)
Make conechokey use ReadConsoleInputW by default (GH-5148)
Manually pass mouse wheel messages to TermControls (GH-5131)
This fixes C-M-space for WSL but not for Win32, but I'm not sure there's a problem in Win32 quite yet. (GH-5208)
Fix copying wrapped lines by implementing better scrolling (GH-5181)
Emit lines wrapped due to spaces at the end correctly (GH-5294)
Remove unneeded whitespace (CC-5162)
@ghost
Copy link

ghost commented Apr 22, 2020

🎉Windows Terminal Preview v0.11.1121.0 has been released which incorporates this pull request.:tada:

Handy links:

This pull request was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) AutoMerge Marked for automatic merge by the bot when requirements are met Product-Terminal The new Windows Terminal.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request - filledBox cursor should cover the whole unicode character
5 participants