-
Notifications
You must be signed in to change notification settings - Fork 8.3k
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
Rewrite COOKED_READ_DATA #15783
Rewrite COOKED_READ_DATA #15783
Conversation
7e79ab5
to
dfc0896
Compare
|
31f7961
to
aa4fdda
Compare
78fd2f4
to
20415e8
Compare
004546d
to
b47784d
Compare
The latest commit (or rather rebase) should address all the issues we found. 🙂 |
This comment has been minimized.
This comment has been minimized.
// MSFT:19976291 Don't re-show the commandline here. We need to wait for | ||
// the viewport to also get resized before we can re-show the commandline. | ||
// ProcessResizeWindow will call commandline.Show() for us. | ||
_textBuffer->GetCursor().SetIsVisible(savedCursorVisibility); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason the old code has these commandLine.Hide();
and Show()
calls everywhere is because of this one exception here where it explicitly doesn't Show()
it again immediately.
But I read into MSFT:19976291 (summary: COOKED_READ_DATA
could crash conhost when you resize the window quickly & randomly) and then tried to test it with my reimplementation and it doesn't seem to reproduce. I don't think the issue can happen anymore, because the EraseBeforeResize()
and RedrawAfterResize()
implementation is a bit more robust now.
As such all of these are gone and moved a couple lines below in this diff into SCREEN_INFORMATION::ResizeScreenBuffer
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
leaving notes as I go. This might take a while.
const auto pCommandLine = &CommandLine::Instance(); | ||
|
||
pCommandLine->Hide(FALSE); | ||
|
||
LOG_IF_FAILED(ScreenInfo.ResizeScreenBuffer(coordBuffer, TRUE)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note to self: does resizing redraw the prompt appropriately? I'd be shocked if it didn't
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
answer: yes, this is in SCREEN_INFORMATION::ResizeScreenBuffer
in a scope_exit
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); | ||
if (gci.HasPendingCookedRead()) | ||
{ | ||
gci.CookedReadData().RedrawAfterResize(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: could we just do this in the same scope_exit as the above one (endResize
)?
src/buffer/out/textBuffer.cpp
Outdated
// Pretend as if `position` is a regular cursor in the TextBuffer. | ||
// This function will then pretend as if you pressed the left/right arrow | ||
// keys `distance` amount of times (negative = left, positive = right). | ||
til::point TextBuffer::NavigateCursor(til::point position, til::CoordType distance) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh lmao so it's like the opposite of the GetCellDistance
thing I did
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep exactly! 😅 I use it to implement the whacky initialData
argument for cooked read.
// or a write routine. Both of these callers grab the current console | ||
// lock. | ||
|
||
// MSFT:13994975 This is REALLY weird. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this scary comment no longer relevant? I remember this as one of the wackiest bits of COOKED_READ we ever debugged.
Seems like it's okay because we're not stashing _pdwNumBytes
anywhere... though, are we ever writing to pNumBytes
now?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this is relevant anymore. The existing cooked read instance now owns the popups and will directly feed them with input.
auto sourceCopy = sourceText; | ||
|
||
// Trim trailing \r\n off of sourceCopy if it has one. | ||
s_TrimTrailingCrLf(sourceCopy); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will the source text no longer have trailing crlf's?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I moved the removal of trailing newlines into the cooked read class. Technically we can also continue to remove the CRLF in alias.cpp - it doesn't really matter. I removed this code, because cooked read also depends on the newlines being removed and so this was redundant.
|
||
if (_ctrlWakeupMask != 0 && wch < L' ' && (_ctrlWakeupMask & (1 << wch))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
okay this went to COOKED_READ_DATA::_handleChar
|
||
if (wch != UNICODE_BACKSPACE || _bufPtr != _backupLimit) | ||
if (hasPopup) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
note to self: This is about where I was 🔖
// GH#1856 - make sure to hide the commandline _before_ we execute | ||
// the resize, and the re-display it after the resize. If we leave | ||
// it displayed, we'll crash during the resize when we try to figure | ||
// out if the bounds of the old commandline fit within the new | ||
// window (it might not). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
neat! this is no longer a concern?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, see here: #15783 (comment)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You know what? I reviewed roughly the whole thing and only came up with 6 comments. I'm ready to sign off, but I have a couple comments first.
Why are these broken? You clear the commandline before resizing, and redraw it completely at the new size. Where does |
This code doesn't work: terminal/src/buffer/out/textBuffer.cpp Lines 2654 to 2684 in e10b7e4
#15701 fixes it. |
Psh that PR's only a ~800 LOC delta. That's easy compared to the last couple here |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I accept that we are going to break something, but that breaking something is necessary to improve [[gestures broadly at everything]]
Now you just need to not conflict yourself! :D |
src/host/_stream.cpp
Outdated
@@ -206,7 +206,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t | |||
// Otherwise handling backspacing tabs/whitespace can turn up complex and bug-prone. | |||
assert(!interactive); | |||
auto pos = cursor.GetPosition(); | |||
pos.x = textBuffer.GetRowByOffset(pos.y).NavigateToPrevious(pos.x); | |||
pos.x = textBuffer.GetMutableRowByOffset(pos.y).NavigateToPrevious(pos.x); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why did this one require mutability?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Huh, yeah, good point.
A late change in #16105 wrapped `_buffer` into a class to better track its dirty state, but I failed to notice that in this one instance we intentionally manipulated `_buffer` without marking it as dirty. This fixes the issue by adding a call to `MarkAsClean()`. This changeset also adds the test instructions from #15783 as a document to this repository. I've extended the list with two bugs we've found in the implementation since then. ## Validation Steps Performed * In cmd.exe, with an empty prompt in an empty directory: Pressing tab produces an audible bing and prints no text ✅
A late change in #16105 wrapped `_buffer` into a class to better track its dirty state, but I failed to notice that in this one instance we intentionally manipulated `_buffer` without marking it as dirty. This fixes the issue by adding a call to `MarkAsClean()`. This changeset also adds the test instructions from #15783 as a document to this repository. I've extended the list with two bugs we've found in the implementation since then. ## Validation Steps Performed * In cmd.exe, with an empty prompt in an empty directory: Pressing tab produces an audible bing and prints no text ✅ (cherry picked from commit 7a8dd90) Service-Card-Id: 91033502 Service-Version: 1.19
A late change in microsoft#16105 wrapped `_buffer` into a class to better track its dirty state, but I failed to notice that in this one instance we intentionally manipulated `_buffer` without marking it as dirty. This fixes the issue by adding a call to `MarkAsClean()`. This changeset also adds the test instructions from microsoft#15783 as a document to this repository. I've extended the list with two bugs we've found in the implementation since then. ## Validation Steps Performed * In cmd.exe, with an empty prompt in an empty directory: Pressing tab produces an audible bing and prints no text ✅
This massive refactoring has two goals:
COOKED_READ_DATA
's inner workingsUnfortunately, over time, knowledge about its exact operation was lost.
While the new code is still complex it reduces the amount of code by 4x
which will make preserving knowledge hopefully significantly easier.
The new implementation is simpler and slower than the old one in a way,
because every time the input line is modified it's rewritten to the text
buffer from scratch. This however massively simplifies the underlying
algorithm and the amount of state that needs to be tracked and results
in a significant reduction in code size. It also makes it more robust,
because there's less code now that can be incorrect.
This "optimization laziness" can be afforded due the recent >10x
improvements to
TextBuffer
's text ingestion performance.For short inputs (<1000 characters) I still expect this implementation
to outperform the conhost from the past.
It has received one optimization already however: While reading text
from the
InputBuffer
we'll now defer writing into theTextBuffer
until we've stopped reading. This improves the overhead of pasting text
from O(n^2) to O(n), which is immediately noticeable for inputs >100kB.
Resizing the text buffer still ends up corrupting the input line
however, which unfortunately cannot be fixed in
COOKED_READ_DATA
.The issue occurs due to bugs in
TextBuffer::Reflow
itself, as itmisplaces the cursor if the prompt is on the last line of the buffer.
Closes #1377
Closes #1503
Closes #4628
Closes #4975
Closes #5033
Closes #8008
This commit is required to fix #797
Validation Steps Performed
Broken due to
TextBuffer::Reflow
bugsBroken due to
TextBuffer::Reflow
bugsfrom the previous command ✅
until that character into the current buffer (acts identical
to F3, but with automatic character search) ✅
starting at the current cursor position,
for as many characters as possible ✅
is longer than the previous command ✅
first instance of a given char (but not including it) ✅
the current buffer up until the current cursor position ✅