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

Emit lines wrapped due to spaces at the end correctly #5294

Merged
12 commits merged into from
Apr 15, 2020

Conversation

zadjii-msft
Copy link
Member

@zadjii-msft zadjii-msft commented Apr 9, 2020

Summary of the Pull Request

When WSL vim prints the initial empty buffer (the one that's just a bunch of '~'s), it prints this by doing the following:

  • Print '~' followed by enough spaces to clear the line
  • Use CUP (^[[H) to move the cursor to the start of the next line
  • repeat until the buffer is full

When we'd get the line of "~ "... in conhost, we'd mark that line as wrapped.

Logically, it doesn't really make any sense that when we follow that up by moving the cursor, the line is wrapped. However, this is just how conhost is right now.
This wasn't ever a problem in just conhost before, because we really didn't care if lines in the alt buffer were "wrapped" or not. Plus, when vim would get resized, it would just reprint it's own buffer anyways. Nor was this a problem in conpty before this year (2020). We've only just recently added logic to conpty to try and preserve wrapped lines.

Initially, I tried fixing this by breaking the line manually when the cursor was moved. This seemed to work great, except for the win32 vim.exe. Vim.exe doesn't emit a newline or a CUP to get to the next line. It just goes for it and keeps printing. So there's no way for us to know the line broke, because they're essentially just printing one long line, assuming we'll automatically move the cursor.

So instead, I'm making sure to emit the proper number of spaces at the end of a line when the line is wrapped. We won't do any funny business in that scenario and try to optimize for them, we'll just print the spaces.

References

PR Checklist

Validation Steps Performed

  • Wrote a unittest first and foremost
  • Checked vtpipeterm to make sure vim still works
  • checked Terminal to make sure vim still works

@DHowett-MSFT
Copy link
Contributor

Can we be certain that this will only happen for a cursor move immediately after we deferred, and only from the line that deferred? Is there any other state transition that might set/unset deferred that we aren't aware of? Do we un-wrap when we get a \n during deferral?

@DHowett-MSFT
Copy link
Contributor

spaes
Third-person singular simple present indicative form of spae

spae (third-person singular simple present spaes, present participle spaeing, simple past and past participle spaed)
(Scotland) To divine; foretell

Copy link
Contributor

@DHowett-MSFT DHowett-MSFT left a comment

Choose a reason for hiding this comment

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

I'm comfortable with this, but I am concerned about the questions I raised earlier. 😄

@zadjii-msft
Copy link
Member Author

zadjii-msft commented Apr 9, 2020

Can we be certain that this will only happen for a cursor move immediately after we deferred, and only from the line that deferred?

No idea.

Is there any other state transition that might set/unset deferred that we aren't aware of?

Almost certainly. I don't really know.

Do we un-wrap when we get a \n during deferral?

I'd presume, but I don't know. Wouldn't be hard to modify this test to test that as well.

Copy link
Contributor

@DHowett-MSFT DHowett-MSFT left a comment

Choose a reason for hiding this comment

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

HPR VPR CUB CUD CUF CUU?
okay here’s a crazy one

DECSC
Move to a deferred position
DECRC

I’m done, you don’t need to test any of these specifically; I imagine the method you targeted for the fix is a concentrator for all these other methods :p

@DHowett-MSFT
Copy link
Contributor

I think CRLF is the more common name for CRNL

@zadjii-msft zadjii-msft added Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Product-Conhost For issues in the Console codebase labels Apr 10, 2020
@zadjii-msft
Copy link
Member Author

@msftbot make sure @miniksa signs off on this

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

ghost commented Apr 10, 2020

Hello @zadjii-msft!

Because you've given me some instructions on how to help merge this pull request, I'll be modifying my merge approach. Here's how I understand your requirements for merging this pull request:

  • I'll only merge this pull request if it's approved by @miniksa

If this doesn't seem right to you, you can tell me to cancel these instructions and use the auto-merge policy that has been configured for this repository. Try telling me "forget everything I just told you".

@miniksa
Copy link
Member

miniksa commented Apr 10, 2020

Hmmmm. This is a conundrum. So the way I thought of wrap in the first place was from a stream write. If you had nothing special happen as you passed the end of the line, it got marked as wrap. If literally any other operation happened, it stayed unmarked and wasn't a wrap.

Is what's currently implemented between PTY/Terminal that if you get into a deferred cursor newline (you've written enough that the cursor is just sitting there waiting off the right boundary for the next character) that you also set the wrap at the same time? Should we not be waiting to set the wrap status until one more character arrives and it comes off the deferred newline onto the next line?

My concern here is that if it is implemented the way I'm describing (entering the deferred newline state sets wrap) that there are an unknowable set of circumstances where it may still be broken. You probably caught the major ones here with the cursor movements, but you're right to fear a long tail.

If there's a strong reason why wrap marking happens on ENTER defer newline instead of on EXIT defer newline with the follow up character.... then I'm fine with this as I think you've caught the major scenarios the best you can and we'll keep improving over time. Just like everything else with our PTY.

However, if there's not a good reason... perhaps investigate the alternative wrap marking scenario instead?

@DHowett-MSFT
Copy link
Contributor

@zadjii-msft idly wondering if fixing Niksa's concerns also helps exact-wrap?

@zadjii-msft
Copy link
Member Author

If there's a strong reason why wrap marking happens on ENTER defer newline instead of on EXIT defer newline with the follow up character.... then I'm fine with this as I think you've caught the major scenarios the best you can and we'll keep improving over time. Just like everything else with our PTY.

However, if there's not a good reason... perhaps investigate the alternative wrap marking scenario instead?

Okay I don't have a good reason, so I'll investigate this tomorrow. This is a tad bit scary, since that's a bit more mucking with WriteCharsLegacy than I'd like. I believe that screenInfo.Write is the one that ends up marking the line as wrapped when we write to the last cell of the row, but I suppose it could check it when it's writing the next char of the following row.

@DHowett-MSFT
Copy link
Contributor

@miniksa is there a world where we merge this because it fixes the significant regression in PTY wrapping and continue the investigation tomorrow?

@miniksa
Copy link
Member

miniksa commented Apr 13, 2020

@miniksa is there a world where we merge this because it fixes the significant regression in PTY wrapping and continue the investigation tomorrow?

A world with a p0 follow on filed and linked, sure.

@DHowett-MSFT
Copy link
Contributor

vim.exe still doesn't work with this change.

image

it's also picked up a status line bug

image

also, when it exits (!) and reprints the entire screen, it reprints the entire screen wrapped now

image

same symptom in the debug tap: no newlines between ~[K ~[K ~[K lines

@DHowett-MSFT
Copy link
Contributor

DHowett-MSFT commented Apr 14, 2020

I managed to get a duplicate status line in vim over ssh:

image

9␛[K␍␊␣10␛[K␍␊␣11␛[K␍␊␣12␛[K␍␊␣13␛[K␍␊␣14␛[K␍␊␣15␛[K␍␊␣16␛[K␍␊␣17␛[K␍␊␣18␛[K␍␊␣
19␛[K␍␊␣20␛[K␍␊␣21␛[K␍␊␣22␛[K␍␊␣23␛[K␍␊␣24␛[K␍␊␣25␛[K␍␊␣26␛[K␍␊␣27␛[K␍␊␣28␛[K␍␊␣
29␛[K␍␊␣30␣␛[4m␛[m␛[K
␛[24m␛[1m␛[30m␛[107m[No␣Name][+]␣␣[unix/]␣[/home/dhowett]␣␣29,1␣␣␣␣␣␣␣␣␣␣␣100%
␛[97m␛[49m␍␊--␣INSERT␣--␛[m␛[K␛[46C␛[1m␛[30m␛[107m␛[29;40H30,␛[28;5H␛[?25h

@DHowett-MSFT
Copy link
Contributor

Does the pull request description here need updating before merge?

@miniksa's concern no longer applies as we're not changing the wrap state in the host.

@zadjii-msft
Copy link
Member Author

Does the pull request description here need updating before merge?

Certainly does, on it.

@zadjii-msft zadjii-msft changed the title Break lines when we move the cursor Emit lines wrapped due to spaces at the end correctly Apr 14, 2020
//
// GH#5291: DON'T remove spaces when the row wrapped. We might need those
// spaces to preserve the wrap state of this line, or the cursor position.
// For example, vim.exe uses "~ "... to clear the line, and then leaves
Copy link
Contributor

Choose a reason for hiding this comment

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

We used to used to do something like "80X 80C (delete 80, move 80 right)" or "K 80C", right? Would that still work here? After all, we want the spaces to appear on the output but the wrap state to be maintained

is the real problem that we're not putting the cursor at the end of the line, and the spaces make up for that? and suppressing the spaces would mean we move the cursor wrong?

Copy link
Contributor

@DHowett-MSFT DHowett-MSFT Apr 14, 2020

Choose a reason for hiding this comment

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

(I'm worried about the accidental deoptimization in not using ECH/EL, i guess.)

Copy link
Member Author

Choose a reason for hiding this comment

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

if we just did the ^[[80X^[[80C, then the line won't actually get wrapped. We need to emit an actual space there. It's a combo of both needing the spaces to trigger the wrapping logic, and needing the spaces to move the cursor.

Theoretically we could do ^[[79X^[[79C (with a space at the end), but that seems dirtier.

Copy link
Contributor

Choose a reason for hiding this comment

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

Fair enough.

Copy link
Member

Choose a reason for hiding this comment

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

I'm concerned about the efficiency of it, though. We realized a lot of gains recently simply by emitting fewer things between the PTY and the Terminal. It's a little dirtier, but it's 69 fewer bytes to send, parse, and react to. That doesn't sound like much for one line, but it could be easily magnified over multiple lines and redraws.

Copy link
Member Author

Choose a reason for hiding this comment

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

So here's my thought - the client app was already emitting all these spaces intentionally. This was actually an optimization that conpty was doing for them, and an optimization that we were doing wrong (in this scenario).

We'll still be sending the optimized sequences if the app does something like "Y                                          X", or if the app uses ^[[K to clear the line (like a sane application should)

Copy link
Member

Choose a reason for hiding this comment

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

Alright, then I'm fine with it. Thanks.

Comment on lines +1666 to +1670
// In GH#5291, we experimented with manually breaking the line on all cursor
// movements here. As we print lines into the buffer, we mark lines as
// wrapped when we print the last cell of the row, not the first cell of the
// subsequent row (the row the first line wrapped onto).
//
Copy link
Member

Choose a reason for hiding this comment

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

Nice warning to future us.

@ghost ghost merged commit dc43524 into master Apr 15, 2020
@ghost ghost deleted the dev/migrie/5291-vim-is-horrible branch April 15, 2020 15:52
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:

@ghost ghost mentioned this pull request Apr 22, 2020
@zadjii-msft zadjii-msft mentioned this pull request May 12, 2020
31 tasks
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-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) AutoMerge Marked for automatic merge by the bot when requirements are met Product-Conhost For issues in the Console codebase
Projects
None yet
Development

Successfully merging this pull request may close these issues.

wsl vim is horribly broken after #5181
3 participants