WSL Terminal Line Endings (the "exact wrap" bug) #4303

Closed
opened 2026-01-30 23:43:25 +00:00 by claunia · 6 comments
Owner

Originally created by @aeiplatform on GitHub (Oct 6, 2019).

Put into terminal and then increase the window size:
printf "%*s\n" "$(tput cols)" "" | tr " " "X"

Line endings acts like they disappeared if the string length matches exactly the window columns.
The terminal has different behavior for columns +- 1.
The behavior does not depend on the type of shell.

Originally created by @aeiplatform on GitHub (Oct 6, 2019). Put into terminal and then increase the window size: `printf "%*s\n" "$(tput cols)" "" | tr " " "X"` Line endings acts like they disappeared if the string length matches exactly the window columns. The terminal has different behavior for columns +- 1. The behavior does not depend on the type of shell.
claunia added the Product-ConhostArea-OutputIssue-BugIn-PRNeeds-Tag-FixPriority-2 labels 2026-01-30 23:43:25 +00:00
Author
Owner

@egmontkob commented on GitHub (Oct 6, 2019):

I discovered this yesterday and wanted to report today, LOL.

There's more to the story.

First, let's note that most shells redraw their prompt after a resize, potentially overwriting some of the previously printed Xs, or just making it harder to follow what's happening. So the given line that's exactly as wide as the screen should be followed by some other lines to clearly see what's going on (e.g. just tap on Enter once or twice to get new prompts).

Then, let's observe that if you have a shorter line than the width, e.g. shorter by one:

printf "%*s\n" "$(( $(tput cols) - 1 ))" "" | tr " " "Y"

(followed by some other lines), and then you narrow the window, this line wraps one step too early (e.g. in this case immediately as you shrink the window by 1), introducing an unnecessary empty line below (which in turn copy-pastes as tons of spaces).

Another thing I noticed is that double clicking stops the selection at the current physical row boundary, rather than selecting an entire word.

I haven't dug into the code yet, but I have a guts feeling that we might be seeing a conceptionally unusual and problematic handling (or rather: lack thereof) of newlines, rather than a simple off-by-one.

I have the feeling (and apologies if I'm wrong, I'm planning to explore the code soon) that it's not remembered whether a row ends in a newline or not; instead, the presence or absence of a trailing speical character ("unused space" or so), which occupies its own cell, is used for that. Or something similar.

What most terminals do, and I believe is the right thing, is that they remember for each physical row whether it ended in an explicit newline or an overflow. This bit belongs to the row itself, rather than a particular cell thereof, and is obviously not directly visible (you could somehow display it in some debug mode). This information is used for multiple things:

  • Rewrapping on a resize
  • Selecting on double click (across physical wrap within a logical line, but not across newline characters)
  • Selecting entire logical lines on triple click
  • Automatically recognizing text patterns, e.g. URLs or user supplied regexps
  • Running the BiDi algorithm on logical lines as units (i.e. mapping them to Unicode Bidi Algorithm's notion of "paragraph")

Probably there's no official specification anywhere how exactly the line ending status needs to be set. It's mostly common sense, with the only nontrivial bit: a NL character does not set the line to end in an explicit newline, it leaves its state unchanged. My BiDi proposal also provides an overview of this, and then refines the behavior for some special cases.

I think that going with this traditional, well proven behavior of tracking newlines is the key to address multiple issues, e.g. rewrapping's dropping of a newline and off-by-one as reported here, proper spaces vs. newlines at copy-pasting (e.g. #2944), proper semantical behavior on double click, and to prepare for other cool features WT is about to have.

@egmontkob commented on GitHub (Oct 6, 2019): I discovered this yesterday and wanted to report today, LOL. There's more to the story. First, let's note that most shells redraw their prompt after a resize, potentially overwriting some of the previously printed `X`s, or just making it harder to follow what's happening. So the given line that's exactly as wide as the screen should be followed by some other lines to clearly see what's going on (e.g. just tap on Enter once or twice to get new prompts). Then, let's observe that if you have a shorter line than the width, e.g. shorter by one: printf "%*s\n" "$(( $(tput cols) - 1 ))" "" | tr " " "Y" (followed by some other lines), and then you narrow the window, this line wraps one step too early (e.g. in this case immediately as you shrink the window by 1), introducing an unnecessary empty line below (which in turn copy-pastes as tons of spaces). Another thing I noticed is that double clicking stops the selection at the current physical row boundary, rather than selecting an entire word. I haven't dug into the code yet, but I have a guts feeling that we might be seeing a conceptionally unusual and problematic handling (or rather: lack thereof) of newlines, rather than a simple off-by-one. I have the _feeling_ (and apologies if I'm wrong, I'm planning to explore the code soon) that it's not remembered whether a row ends in a newline or not; instead, the presence or absence of a trailing speical character ("unused space" or so), which occupies its own cell, is used for that. Or something similar. What most terminals do, and I believe is the right thing, is that they remember for each physical row whether it ended in an explicit newline or an overflow. This bit belongs to the row itself, rather than a particular cell thereof, and is obviously not directly visible (you could somehow display it in some debug mode). This information is used for multiple things: - Rewrapping on a resize - Selecting on double click (across physical wrap within a logical line, but not across newline characters) - Selecting entire logical lines on triple click - Automatically recognizing text patterns, e.g. URLs or user supplied regexps - Running the BiDi algorithm on logical lines as units (i.e. mapping them to Unicode Bidi Algorithm's notion of "paragraph") Probably there's no official specification anywhere how exactly the line ending status needs to be set. It's mostly common sense, with the only nontrivial bit: a NL character does _not_ set the line to end in an explicit newline, it leaves its state unchanged. My BiDi proposal also provides an [overview](https://terminal-wg.pages.freedesktop.org/bidi/recommendation/paragraphs.html) of this, and then [refines](https://terminal-wg.pages.freedesktop.org/bidi/recommendation/escape-sequences.html#how-these-escape-sequences-apply) the behavior for some special cases. I think that going with this traditional, well proven behavior of tracking newlines is the key to address multiple issues, e.g. rewrapping's dropping of a newline and off-by-one as reported here, proper spaces vs. newlines at copy-pasting (e.g. #2944), proper semantical behavior on double click, and to prepare for other cool features WT is about to have.
Author
Owner

@DHowett-MSFT commented on GitHub (Oct 6, 2019):

Just dropping in to say: yeah, we don't do wrapping correctly in Windows Terminal. Since it's a client of conhost, and conhost does wrapping somewhat okay, WT isn't yet enlightened here.

There's a lot of intricacies in the whole ConPTY layer -- it should almost certainly un-wrap wrapped lines before it sends them, and let the terminal emulator on the other end wrap them, but we can't do that until #780... WT won't even wrap right now (evidenced by #2588 #3094).

Curious thing about newlines in the traditional console is that using SetConsoleMode to turn off ENABLE_WRAP_AT_EOL_HANDLING and perhaps DISBALE_NEWLINE_AUTO_RETURN will impact the behavior of at-end and off-end line writing and wrapping.

Also of interest: #2089 (related to how we measure the righthand side of the screen).

@DHowett-MSFT commented on GitHub (Oct 6, 2019): Just dropping in to say: yeah, we don't do wrapping correctly _in Windows Terminal_. Since it's a client of conhost, and conhost does wrapping _somewhat okay_, WT isn't yet enlightened here. There's a lot of intricacies in the whole ConPTY layer -- it should almost certainly _un-wrap_ wrapped lines before it sends them, and let the terminal emulator on the other end wrap them, but we can't do that until #780... WT won't even _wrap_ right now (evidenced by #2588 #3094). Curious thing about newlines in the traditional console is that using `SetConsoleMode` to turn off `ENABLE_WRAP_AT_EOL_HANDLING` and perhaps `DISBALE_NEWLINE_AUTO_RETURN` will impact the behavior of at-end and off-end line writing and wrapping. Also of interest: #2089 (related to how we measure the righthand side of the screen).
Author
Owner

@aeiplatform commented on GitHub (Oct 10, 2019):

For pseudo terminal (PTY) such as screen pseudo terminal slave (PTS) works properly.
This unexpected behavior occurs only for the default teletypewriter (TTY).

@aeiplatform commented on GitHub (Oct 10, 2019): For pseudo terminal (_PTY_) such as **screen** pseudo terminal slave (_PTS_) works properly. This unexpected behavior occurs only for the default teletypewriter (_TTY_).
Author
Owner

@DHowett-MSFT commented on GitHub (Oct 29, 2019):

Incidentally, this is probably the same root cause as the issue with powershell where when you type off the edge of the screen it wraps one line early but the cursor jumps to the wrong place, and then when you backspace over it it destroys the known universe.

@DHowett-MSFT commented on GitHub (Oct 29, 2019): Incidentally, this is probably the same root cause as the issue with powershell where when you type off the edge of the screen it wraps one line early but the cursor jumps to the wrong place, and then when you backspace over it it destroys the known universe.
Author
Owner

@aeiplatform commented on GitHub (Jan 30, 2020):

On a new Windows build 18363.592 this issue got even weirder after increasing size of the terminal. Per every additional column of the terminal triggers duplication of the next line.
When the size is reduced, the terminal cuts off the original text, but after resizing again the terminal to the original size text is replaced by duplication of the next line.
So if you shrink the terminal, you will lose all the exceeding text.

I tried even explicit escaping, but without any differences.
printf "%*s\\n" "$(tput cols)" "" | tr " " "X"

@aeiplatform commented on GitHub (Jan 30, 2020): On a new Windows build 18363.592 this issue got even weirder after increasing size of the terminal. Per every additional column of the terminal triggers duplication of the next line. When the size is reduced, the terminal cuts off the original text, but after resizing again the terminal to the original size text is replaced by duplication of the next line. So if you shrink the terminal, you will lose all the exceeding text. I tried even explicit escaping, but without any differences. `printf "%*s\\n" "$(tput cols)" "" | tr " " "X"`
Author
Owner

@zadjii-msft commented on GitHub (Jan 30, 2020):

Woah okay this is pretty bad not just in Terminal, but in conhost too:

image

At first I thought this might be related to the disaster that is #4200/#405/#3490/#4354 but this might be lower than even all of them

@zadjii-msft commented on GitHub (Jan 30, 2020): Woah okay this is pretty bad _not just in Terminal_, but in conhost too: ![image](https://user-images.githubusercontent.com/18356694/73461721-3f77b880-4340-11ea-8415-36afc86a3504.png) At first I thought this might be related to the disaster that is #4200/#405/#3490/#4354 but this might be lower than even all of them
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#4303