Fix the input anchor column when the buffer is narrowed and then widened - #5191
Open
lulu-loopp wants to merge 1 commit into
Open
Fix the input anchor column when the buffer is narrowed and then widened#5191lulu-loopp wants to merge 1 commit into
lulu-loopp wants to merge 1 commit into
Conversation
When the buffer width changes, 'RecomputeInitialCoords' recovers the
column of the edit anchor with
_initialX %= _console.BufferWidth;
'_initialX' is the anchor's column at the width that was in effect
before the resize, so it is already the prompt's cell width reduced
modulo that width. Reducing it a second time gives the right answer the
first time the buffer is narrowed past the prompt, but it discards how
many physical lines the prompt spans, and that is never recovered: a
36-cell prompt narrowed to a width of 35 leaves '_initialX' at 1, and
widening back to 100 computes 1 % 100 == 1. Every subsequent render of
a non-empty input is then written one column into the prompt,
overwriting it, and the text drawn at the narrow width is left behind on
the screen.
Keep the width-independent quantity instead. '_initialPromptCells' is
captured wherever the anchor is captured and is never modified
afterwards, and '_initialX' is derived from it on every buffer width
change. Narrowing behaves exactly as before, and widening now restores
the anchor, including across several successive resizes.
This does not cover a prompt that was already wider than the buffer when
'ReadLine' was entered. 'CursorLeft' is the only observation available
in that case and it is already reduced, so the prompt's width cannot be
recovered without re-invoking the user's prompt function. That case
behaves as it did before.
Related to PowerShell#3637
3 tasks
Author
|
@microsoft-github-policy-service agree |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
PR Summary
When the terminal is made narrower than the prompt and then wider again, PSReadLine
draws the input on top of the prompt and leaves the text it drew at the narrow width
behind on the screen. The edit anchor is never restored for the rest of that
ReadLinecall, so every subsequent keystroke re-renders in the wrong column.This changes
RecomputeInitialCoordsto derive the anchor's column from awidth-independent quantity instead of reducing the column in place.
Repro
Get-ChildItem -Recurse -Filter *.rs | Select-Object FullName-- and do not press Enter.The input wraps onto the following rows and is rendered correctly.
Expected: the input is rendered starting at column 36, right after the prompt.
Actual: the input is rendered starting at column 1, over the prompt:
Screen after step 4, on 2.4.5:
and with this change:
Rows 2 and 3 are what was drawn while the window was narrow. They are not cleaned up
in either case -- the prompt on row 1 is what this change is about.
Root cause
RecomputeInitialCoordsrecovers the anchor column after a buffer width change witha single statement, in both of its branches:
Render.cs#L1261(isTextBufferUnchanged: true)Render.cs#L1297(isTextBufferUnchanged: false)_initialXis the anchor's column at the width that was in effect before theresize, so it already is the prompt's cell width reduced modulo that width.
Reducing it a second time is correct the first time the buffer is narrowed past the
prompt, but it discards the quotient, and the quotient is the only record of how many
physical lines the prompt spans. Once it is gone the prompt's true width cannot be
reconstructed:
_initialXbefore_initialXafter36 % 35= 11 % 100= 1The statement dates back to b2979d1 ("Fix rendering after buffer resize", 2017) and is
present unchanged in every release from 2.0.0 through 2.4.5.
The fix
Keep the width-independent quantity.
_initialPromptCellsis the cell width of theprompt's last logical line, measured from column 0 of the physical line where that
logical line starts. It is captured wherever the anchor is captured -- input
initialization in
ReadLine.cs,InvokePrompt, and the two prompt-reprint recoverypaths in
Render.cs-- and is never modified afterwards._initialXis then derivedfrom it on every buffer width change:
While the prompt fits in the buffer,
_initialPromptCells == _initialXand the newexpression is the identity, so nothing changes for the common case.
Behavior boundaries
equal by definition, and for later ones the new expression is what the old one was
trying to compute.
(100 -> 35 -> 60 -> 25 -> 100 was measured).
ReadLinewas entered is notcovered.
CursorLeftis the only observation available at that point and it isalready reduced, so the prompt's width is not recoverable without re-invoking the
user's
promptfunction. That case behaves exactly as it did before this change;see "Remaining work" below.
Verification
Unit test
RecomputeInitialCoords_ShouldRecoverInitialXWhenBufferGetsWiderintest/ResizingTest.cswalks a 36-cell prompt through the buffer widths100, 35, 60, 25, 100 and checks the initial column after each change. It fails on the
current code at the third width (
initial column is 1 but should be 36) and passeswith this change. It only checks the column: recovering the row relies on the terminal
having reflowed the screen buffer, which
TestConsoledoes not do, so a resizableconsole fixture would not make that half meaningful.
The full suite passes on this branch (
dotnet test, net8.0). Note that most of thesuite is
SkippableFactgated on the active keyboard layout and skipped on mymachine; CI covers those.
Against a real terminal
The unit test cannot show that PSReadLine then renders where it says it will, so the
change was also measured end to end: a ConPTY pseudoconsole is driven by a probe that
writes keys, resizes the pseudoconsole, and parses the emitted
CUPsequences to readback the column PSReadLine actually renders at. Prompt = 36 cells, input = 60
characters, on Windows 11 26200. The "patched" module in the tables is this change
applied on top of the v2.4.5 tag, so that it could be loaded next to the shipped
module for a like-for-like comparison; the code is the same as on this branch.
Each case starts at the first width listed, resizes as listed, and types one character
at each width.
ReadLinestartedWindows PowerShell 5.1 with 2.4.5 side-loaded measures the same as the two baseline
columns above. For the first case, 2.4.5 restores the anchor to column 1 instead of
36 and the patched build restores it to 36. The last case is the one described under
"Behavior boundaries".
Editing regression matrix
Fourteen resize-then-edit scenarios (type, repeated type,
Escape,Backspace,Home,Homethen type,LeftArrowthen type,UpArrow, multi-line input; eachnarrowing and widening; short and medium prompts) were run against official 2.4.5 and
against the patched build. The two transcripts -- render columns, cursor moves, and
final screen contents -- are identical byte for byte, including the three
scenarios that already drifted before this change (
LeftArrowthen type, and the twomulti-line cases). Those three are separate pre-existing problems and are untouched
here.
Relationship to #3074
#3074 (f46f15d, first released in
v2.2.0-beta5) rewrote
RecomputeInitialCoordsaround the render data, and fixed thecases where the text buffer had changed across the resize. It carried this statement
forward unchanged -- it only reformatted
_initialX = _initialX % ...to_initialX %= ...and duplicated it into the newelsebranch -- so the anchor column has hadthe same defect before and after that work. That is consistent with 2.0.0 and 2.4.5
measuring identically in the table above.
Remaining work
The uncovered case (prompt already wrapped when
ReadLinestarts) needs the prompt'swidth from a source other than
CursorLeft. The prompt string is available inInvokePromptand in the prompt-reprint recovery paths, so_initialPromptCellscould be measured directly there; for the normal entry path it would require either
invoking the
promptfunction again or reading the screen buffer, neither of whichseemed appropriate to fold into this fix. Happy to follow up if you would like it
handled here.
Notes
Found while building a terminal on Windows, where this was reproducible against the
inbox PSReadLine 2.0.0 as well as current 2.4.5.
PR Checklist
the VS Code integrated terminal take, on both Windows PowerShell 5.1 and pwsh
7.6.4. I have not measured on macOS or Linux; the change is arithmetic on the
buffer width and has no platform-specific part.
Related to #3637
Microsoft Reviewers: Open in CodeFlow