-
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
Use VT for COOKED_READ_DATA #17445
Use VT for COOKED_READ_DATA #17445
Conversation
This comment has been minimized.
This comment has been minimized.
1f4219f
to
4f35067
Compare
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.
Well holy shit
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.
Currently talking about a bug with Leonard - hold tight
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.
Tested, broke, fixed, tested, love it, ship it
Should be much better now! |
/azp run |
const auto cookedReadRestore = wil::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.
Curious, why don't we need this anymore?
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 RedrawAfterResize
call moved upwards in this file. The problem with screenInfo
is that it calls RedrawAfterResize
right after resizing the buffer but before it actually updated the _viewport
. This causes the VT parser to think that the addressable range is smaller/larger than it actually is which results in issues.
My solution was to delay the RedrawAfterResize
call until the inevitable call to _InternalSetViewportSize
later on.
} | ||
|
||
if (measureOnly) | ||
// Render the popups, if there are any. | ||
if (!_popups.empty()) |
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'm not really a CMD user so I'm not really familiar with how to get popups to appear. BUT if you tell me how to get popups to appear, I'd be happy to test this out manually to get some extra coverage :)
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.
There are 3 "question" popups with F2, F4, and F9, and there's one interactive popup via F7. When you're in the F7 prompt, you can press F9 to select one of the entries immediately. You can read about what the 4 popups are supposed to do here (they're in the order F2, F4, F9, F7): https://github.com/microsoft/terminal/blob/dev/lhecker/14000-vt-cooked-read/src/host/readDataCooked.hpp#L54-L73
I should mention the F-keys that open them in that 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.
Alright, I did a little bit of testing on the popups. Tested against v1.20.11781. Here's a short report of my findings:
F2
- The UI does look different between the two. I'm guessing that's expected (and I'm guessing it's ok for us to do this). Curious, why though? I'm guessing it's a limitation on switching over to just using VT?
- 🐛BUG:
- run
echo hello world
- F2->
w
- expected:
echo hello
- actual: the characters are copied over to the input buffer, but they aren't rendered
- run
- ✅ the input buffer is properly updated regardless of the initial content of the input buffer
- i.e. start with
abcdefghijklmnop
then useF2
--> input buffer is updated appropriately (data-wise)
- i.e. start with
F4
- ✅ input buffer properly updated
- test:
abcdefghijklmnopqrstuvwxyz
(cursor just after thef
) +F4
+x
-->abcdefxyz
- test:
F9
- ✅ correctly retrieves the right command in the history and throws it in the input buffer
- test: fill history with
echo 1
up toecho 5
+F9
+1
-->echo 2
(it's 0-indexed!)
- test: fill history with
F7
- Again, UI looks pretty different. This one is probably going to be the most noticeable one since it's widely used.
- ✅ given the new UI, if we don't have enough space at the bottom, we shift everything over
- ✅ if the retrieved command doesn't fit on the screen, we truncate it
Looks like there's just one bug (and it's probably fixed by a missed call to the renderer somewhere). Nice work!
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.
Curious, why though? I'm guessing it's a limitation on switching over to just using VT?
Yes, but also not quite. We could implement the same popup interface as we had before, but it always had the problem that it reads the buffer contents. When you dismiss that old popup UI, it needs to restore the text that was behind it after all. It's possible to make this Unicode correct, but it's also very annoying to do so.
However, there's another problem: When you restart a session ("press Enter to restart" in WT), it recreates a new ConPTY instance which does not have the previous buffer contents. When the popup reads the buffer contents it'll read whitespace and when you dismiss it, it'll destroy your previous buffer contents.
Both issues can be avoided by simply modernizing the popup UI a little, and by making sure that the "popup" is always below the prompt and never on top of it.
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 last 2 commits address the issue you found. You can pretty much ignore the "Fix buffer invalidation" one though. 😅 I've decided to go with a way simpler solution for now until we improve our grapheme cluster support even further at some point.
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.
Just on the subject of the popups restoring the text behind them, technically we should be able to achieve that now with VT pages, without having to read the buffer contents. The catch is that it's unlikely to work on most other terminals, so we'd still have to have a simpler fallback UI that doesn't rely on popups, and some form of feature detection at startup. I would have loved for us to do that, but I know that's asking a bit much.
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 really wouldn't mind adding something like that in the future! It sounds pretty cool and we could show off our more advanced VT features that way.
case PopupKind::CopyToChar: | ||
_popupDrawPrompt(popup, ID_CONSOLE_MSGCMDLINEF2); | ||
break; | ||
case PopupKind::CopyFromChar: | ||
_popupDrawPrompt(popup, ID_CONSOLE_MSGCMDLINEF4); | ||
break; |
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 no more CopyToChar
and CopyFromChar
?
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.
All of the drawing logic moved over to _redisplay()
. I did this because with VT we can't directly manipulate the buffer anymore, so we need to be smart and correctly calculate the positions of the text and the popups before we even write them out. That only works of course, if we have all of the necessary information at hand simultaneously within a single function.
|
||
// If the bottom of the window when adjusted would be | ||
// above the final line of valid text... | ||
if (srNewViewport.bottom + DeltaY < coordValidEnd.y) | ||
if (sBottomProposed < coordValidEnd.y) |
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 GetValidAreaBoundaries
will not have been updated to reflect the new prompt position at this point, so we can't use that anymore to determine the viewport position. As an alternative I'm now using the cursor position. That will work a little worse for very long prompts, but at least it works. We can improve this later on.
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.
Blocking for that one bug. Other than that, looks great! Nice work!
link: #17445 (comment)
@@ -1037,6 +1037,11 @@ void SCREEN_INFORMATION::_InternalSetViewportSize(const til::size* const pcoordS | |||
const auto DeltaY = pcoordSize->height - _viewport.Height(); | |||
const auto coordScreenBufferSize = GetBufferSize().Dimensions(); | |||
|
|||
if (DeltaX == 0 && DeltaY == 0) | |||
{ | |||
return; |
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 going to suppress some sort of detectable input event that a client app could have been depending on?
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 only thing this function does (apart from changing the viewport) is to make the current cursor visible if a cooked read is active. So, I'd say "no".
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.
Thanks for doing this 😊 Fantastic work!
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 did not get through all this, but I won't block over what I've seen so far
src/host/VtIo.cpp
Outdated
out += 2; | ||
} | ||
|
||
// 7 bytes (";97"). |
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.
7
? or 3?
src/host/readDataCooked.cpp
Outdated
// Hilarious bit-trickery that no one can read. But it works. Check it out in a debugger. | ||
// The idea is to use bits 1:31 as the value and bit 0 as a trigger to bit-flip the value. | ||
// A bit-flipped positive number is negative, but offset by 1, so we add 1. Fun! | ||
const auto offset = ((i / 2) ^ ((i & 1) - 1)) + 1; |
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.
|
||
// Do a brute force search for the best starting position that ends at the current cursor position. | ||
// The search is centered around `bestGuessColumn` with offsets 0, 1, -1, 2, -2, 3, -3, ... | ||
for (til::CoordType i = 0, attempts = 2 * size.width; i <= attempts; i++) |
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.
this seems... really wacky? Why can't we just layout the initialData once, then measure the total number of columns that all takes, then go back that many columns and start there? I don't think I get why we instead are trying a bunch of different starting places till one just works...?
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 right, I forgot to mention in the comment why we need to do that! I'll update it.
We need to do this, because of wide glyphs. When a wide glyph intersects the end of a row, we insert a padding space. We're given an "end position" and a string and we need to find its starting position. The problem is that multiple starting positions can result in the same end position depending on what wide glyphs intersect the end of their row where. Because of that it's not trivially possible to layout those lines backwards starting from the end position.
// | ||
// NOTE: Don't call _flushBuffer() after appending newlines to the buffer! See _handlePostCharInputLoop for more information. | ||
void COOKED_READ_DATA::_flushBuffer() | ||
til::point COOKED_READ_DATA::_getViewportCursorPosition() const noexcept |
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.
📝: this thru _setCursorPosition
are pretty verbatim moved from BufferState
at the top of the file
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.
Yea I feel comfortable with this merging while I'm OOF. I didn't read all of it, so I don't want to be the second, but what I have read is inoffensive
|
||
// FYI: This loop does not loop. It exists because goto is considered evil |
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.
well, it might loop once. which we can't know at the top of this, for the same "wide chars wrap weird" reason
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.
well, it might loop once.
To me it felt like writing a duff's device. Sure, it does wrap around once, but like...
It literally has a break;
at the tail end of the for-loop. 😅 For all intents and purposes, I basically wrote a goto
but I'm not calling it as such. I just didn't want to write another pragma suppress
.
} | ||
|
||
til::CoordType COOKED_READ_DATA::_getColumnAtRelativeCursorPosition(ptrdiff_t distance) const | ||
COOKED_READ_DATA::LayoutResult COOKED_READ_DATA::_layoutLine(std::wstring& output, const std::wstring_view& input, const size_t inputOffset, const til::CoordType columnBegin, const til::CoordType columnLimit) const |
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 didn't read this method yet
By rewriting
COOKED_READ_DATA
to use VT for its output we make itpossible to pass this VT output 1:1 straight to the hosting terminal
if we're running under ConPTY. This is also possible with the current
console APIs it uses, but it's somewhat janky. In particular the
usage of
ReadConsoleOutput
to backup/restore the popup contentscould be considered bad faith "rules for thee, not for me",
given that we're telling people to move away from those APIs.
The new implementation contains a bare bones "pager" to fit even
very long prompt contents into the VT viewport.
I fully expect this initial PR to not be entirely bug free, because
writing a proper pager with line wrapping is a little bit complex.
This PR takes some significant shortcuts by leveraging the fact
that the prompt line is always left-to-right and always a series
of fully filled lines followed by one potentially semi-full line.
This allows us to skip using a front/back-buffer for diffing the
contents between two redisplay calls.
Part of #14000
Validation Steps Performed
printf(" "); gets(buffer);
in C (or equivalent)from 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 ✅
Pressing tab produces an audible bing and prints no text ✅
exactly each character that is being typed ✅
only exactly each deleted character ✅