-
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 DECCRA/DECFRA for ScrollConsoleScreenBuffer #17747
Conversation
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've given it, and the tests, a close read. I've also tested it with vim (set termguicolors | color kolor
) in Terminal (supports rectangular areas) and WezTerm (does not support rectangular areas!).
It's better than what we have, and I'm excited to get it out in Canary.
Microsoft::Console::VirtualTerminal::VtIo::FormatAttributes(buf, TextAttribute{ fillAttribute }); | ||
} | ||
|
||
if (copySourceViewport.IsValid() && copyTargetViewport.IsValid()) |
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.
If we bail out here, is there still something we could have done by manual-copy? e.g. if it's not representable with DECCRA, is there still a potential fallback?
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.
No, this just means that there was nothing to copy because either the source or target rects are completely out of bounds and so there's nothing to copy.
const auto h = std::max(0, sourceViewport.Height()); | ||
const auto a = static_cast<size_t>(w * h); | ||
if (a == 0) | ||
writer.BackupCursor(); |
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.
DECCRA and DECFRA are documented as not changing the active cursor position. We may be able to skip backing up the cursor in the DECxRA cases
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, backing up the cursor also backs up the active attributes. We use those in DECFRA. Got it.
src/host/getset.cpp
Outdated
{ | ||
return S_OK; | ||
// This calculates just the positive offsets caused by out-of-bounds source and target coordinates. | ||
// The source gets offset because it's outside the bounds of the buffer (= negative), |
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 might benefit from some ASCII art. I still don't understand 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.
If the source starts outside the buffer, we want to offset it by +source. if the target starts outside the clip region, we want to offset something by something.
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 surprised we can apply the same offset to both the source and target!
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 believe there's no good way to draw this. It's a logic that I came up with after noodling over the problem for a while.
Edit: Much better comment now.
|
||
const auto copyTargetViewport = Viewport::FromDimensions(target + offset, sourceViewport.Dimensions()).Clamp(clipViewport); | ||
const auto copySourceViewport = Viewport::FromDimensions(sourceViewport.Origin() + offset, copyTargetViewport.Dimensions()).Clamp(bufferSize); | ||
const auto fills = Viewport::Subtract(fillViewport, copyTargetViewport); |
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.
"SOME RECTS" strikes again
// The first parameter denotes the conformance level, so we skip it. | ||
parameters.subspan(1).for_each([&](auto p) { | ||
attributes.set(static_cast<DeviceAttribute>(p)); | ||
return 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.
In practice this probably won't matter, but strictly speaking we should be checking that the first parameter is 60+, otherwise we can't assume that we're getting a list of features here. When the first parameter is less than 60, the parameters that follow take on a different meaning.
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.
Should I check for >=60 or >=61? Documentation and comments that I found here imply that all terminals that support DA1 are at least 61... Edit: I used >=61 for 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.
Using >=61 is fine, but I don't think it would matter either way. In STD070 they document the range 61 to 69 as representing levels 1 to 9. But they also go on say that values 1 to 59 are device specific codes, and that the new encoding scheme applies to values 60 or greater. So it's a bit ambiguous, but it's highly unlikely we'll ever encounter a terminal with the value 60 either way.
And while we're getting into technicalities, I should mention that certain features are supposed to be implicit at higher levels. In particular level 4+ devices are supposed to support the rectangular editing operations, but won't advertise feature 28 explicitly. Unfortunately modern terminals tend to lie about their conformance level, so we can't trust that value, and we're better off assuming no support unless feature 28 is explicitly listed.
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.
12/14 I read closely, and getset.cpp looks... good enough. I'm out of time to give it a close enough review but i'm guessing we can ship this
// This assumes that InputStateMachineEngine is tightly coupled with VtInputThread and the rest of the ConPTY system. | ||
// On startup, ConPTY will send a DA1 request to get more information about the hosting terminal. | ||
// We catch it here and store the information for later retrieval. | ||
if (_deviceAttributes.load(std::memory_order_relaxed) == 0) |
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.
if we get this while we're _lookingForDSR
, but we don't get a DSR, should we disable looking for the cursor at that 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.
Oh! I forgot about that!
Thanks!
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 I wouldn't have been able to wrap my head around this without diagrams, thanks for that!
@@ -554,12 +557,224 @@ class ::Microsoft::Console::VirtualTerminal::VtIoTests | |||
actual = readOutput(); | |||
VERIFY_ARE_EQUAL(expected, actual); | |||
|
|||
// Copying from a partially out-of-bounds source to a partially out-of-bounds target, |
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 friggin love me some diagrams
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 tried to write a diagram but failed to make it readable & obvious since it requires 3 overlapping rectangles. Instead, I tried to improve the verbal description.
I'll merge for now since we're short on time.
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, I thought this was getset.cpp haha. Thank you in this case!
// DECCRA: Copy Rectangular Area | ||
fmt::format_to( | ||
std::back_inserter(buf), | ||
FMT_COMPILE(L"\x1b[{};{};{};{};;{};{}$v"), |
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.
CSI 9/11 Pts; Pls; Pbs; Prs; Pps; Ptd; Pld; Ppd $ v
area to be copied destination
The omitted one here is:
Pps is the number of the page where the rectangular area is located. Default: Pps = 1.
and that makes sense
This adds logic to get the DA1 report from the hosting terminal on
startup. We then use the information to figure out if it supports
rectangular area operations. If so, we can use DECCRA/DECFRA to
implement ScrollConsoleScreenBuffer.
This additionally changes
ScrollConsoleScreenBuffer
to alwaysforbid control characters as the fill character, even in conhost
(via
VtIo::SanitizeUCS2
). My hope is that this makes the APImore consistent and robust as it avoids another source for
invisible control characters in the text buffer.
Part of #17643
Validation Steps Performed