-
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
Enable using the alt buffer in the Terminal #12561
Conversation
Resizing is surely boned but this is 1000% percent better than nothing at all.
This comment was marked as resolved.
This comment was marked as resolved.
_⚠️ targets #12561⚠️ _ "Alternate scroll mode" is a neat little mode where the app wants mouse wheel events to come through as arrow keypresses instead, when in the alternate buffer. Now that we've got support for the alt buffer in the Terminal, we can support this as well. * [x] Closes #3321 * [x] I work here * [ ] Tests would be nice Tested manually with ```bash printf "\e[?1007h" ; man ps ```
/cc @j4james for opinions since he's intimately familiar with these codepaths :) |
Does TermControl's scroll bar automatically do the right thing here? I'm pleasantly surprised to not see anything in TC (whole project!) changing :) |
sure does, we send a scroll position changed event, and it just works |
I'm a bit concerned about how this PR will interact with #12358 (would try building a branch with both integrated to test, but am having trouble building). Do we know what text will be sent as a notification when the alt buffer is exited? (for whatever it's worth, NVDA's current diffing approach sees the entire contents of the regular buffer as new, so reports it all, which it shouldn't). |
At the moment, no. Ideally... nothing would get sent? not sure that makes sense either. The complicating factor here is that ConPTY might just be repainting the whole screen when we switch buffers. Obviously not ideal but IMO not something that we should hold the whole fix over at this time. Lemme verify that (once I get my branch building again 😛) Money sequences are between the red input. Form that one, it's clearer that conpty is re-emitting the buffer contents. Drat. So it'll get re-echoed, which sure isn't worse than it is today. @DHowett - EDITed to add screen reader alt text. |
…f that we're supposed to do when entering/exiting the alt buffer
// any scrollback, so we just need to resize it and presto, we're done. | ||
if (_inAltBuffer()) | ||
{ | ||
_altBuffer->GetCursor().StartDeferDrawing(); |
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 isn't needed is it? Just move the endDefer
down to where you call StartDeferDrawing()
?
Viewport Terminal::_GetMutableViewport() const noexcept | ||
{ | ||
return _mutableViewport; | ||
return _inAltBuffer() ? Viewport::FromDimensions(_mutableViewport.Dimensions()) : | ||
_mutableViewport; | ||
} | ||
|
||
short Terminal::GetBufferHeight() const noexcept | ||
{ | ||
return _mutableViewport.BottomExclusive(); | ||
return _GetMutableViewport().BottomExclusive(); | ||
} | ||
|
||
// ViewStartIndex is also the length of the scrollback | ||
int Terminal::ViewStartIndex() const noexcept | ||
{ | ||
return _mutableViewport.Top(); | ||
return _inAltBuffer() ? 0 : _mutableViewport.Top(); | ||
} | ||
|
||
int Terminal::ViewEndIndex() const noexcept | ||
{ | ||
return _mutableViewport.BottomInclusive(); | ||
return _inAltBuffer() ? _mutableViewport.Height() - 1 : _mutableViewport.BottomInclusive(); | ||
} | ||
|
||
// _VisibleStartIndex is the first visible line of the buffer | ||
int Terminal::_VisibleStartIndex() const noexcept | ||
{ | ||
return std::max(0, ViewStartIndex() - _scrollOffset); | ||
return _inAltBuffer() ? ViewStartIndex() : | ||
std::max(0, ViewStartIndex() - _scrollOffset); | ||
} | ||
|
||
int Terminal::_VisibleEndIndex() const noexcept | ||
{ | ||
return std::max(0, ViewEndIndex() - _scrollOffset); | ||
return _inAltBuffer() ? ViewEndIndex() : | ||
std::max(0, ViewEndIndex() - _scrollOffset); | ||
} |
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 these changes are necessary. I believe it'd be better to leave the code as it is, because it'd better to get rid of the Viewport
class altogether which is easier with the previous code.
Also since these methods might be used for clamping values, I believe that this might introduce bugs more easily.
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 am guessing they are necessary because of _scrollOffset
... which makes me wonder how we account for the scroll offset being per-buffer but global state
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.
We could use a getter for _scrollOffset
which returns 0 for the alt buffer. 🤔
cursorSize, | ||
true, | ||
_mainBuffer->GetRenderer()); | ||
_mainBuffer->SetAsActiveBuffer(false); |
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.
Unrelated to this PR, I just realized that distributing the active state as a boolean across 2 instances, makes it possible to exceptions to get our app in an invalid state were both or none of the TextBuffers are active.
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.
37/37 but I have some questions, plus the selection-related followup from Teams
HRESULT VtEngine::SwitchScreenBuffer(const bool useAltBuffer) noexcept | ||
{ | ||
RETURN_IF_FAILED(_SwitchScreenBuffer(useAltBuffer)); | ||
RETURN_IF_FAILED(_Flush()); |
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.
order of operations -- interesting. Can this cause torn state?
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.
Hmm. So when we switch to the alt buffer, we first
- flush out the current frame
- emit this sequence, and flush again.
so I think this is basically atomic, and then we return back out and continue processing more output.
// - S_OK if we succeeded, else an appropriate HRESULT for failing to allocate or write. | ||
[[nodiscard]] HRESULT VtEngine::_SwitchScreenBuffer(const bool useAltBuffer) noexcept | ||
{ | ||
return _Write(useAltBuffer ? "\x1b[?1049h" : "\x1b[?1049l"); |
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 is technically only valid for the xterm-derived engines; wintelnet might B E E F
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.
True but I doubt that users of telnet.exe hit anything using the alt buffer...
|
||
void ConptyRoundtripTests::AltBufferToAltBufferTest() | ||
{ | ||
Log::Comment(L"When we request the alt buffer when we're already in the alt buffer, we should still clear it out and replace 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.
technically only true for DECSET 1049; other modes leave it dirty (might be interesting to consider)
auto row = savedCursorState.Row; | ||
const auto col = savedCursorState.Column; | ||
|
||
// The saved coordinates are always absolute, so we need reset the origin mode temporarily. |
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.
it doesn't look like we are doing anything with the origin mode
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 mean, terminal-side doesn't have that implemented at all, iirc. I just kinda assumed in the long view of "merging adaptdispatch and termdispatch" we'd fix it in post.
Viewport Terminal::_GetMutableViewport() const noexcept | ||
{ | ||
return _mutableViewport; | ||
return _inAltBuffer() ? Viewport::FromDimensions(_mutableViewport.Dimensions()) : | ||
_mutableViewport; | ||
} | ||
|
||
short Terminal::GetBufferHeight() const noexcept | ||
{ | ||
return _mutableViewport.BottomExclusive(); | ||
return _GetMutableViewport().BottomExclusive(); | ||
} | ||
|
||
// ViewStartIndex is also the length of the scrollback | ||
int Terminal::ViewStartIndex() const noexcept | ||
{ | ||
return _mutableViewport.Top(); | ||
return _inAltBuffer() ? 0 : _mutableViewport.Top(); | ||
} | ||
|
||
int Terminal::ViewEndIndex() const noexcept | ||
{ | ||
return _mutableViewport.BottomInclusive(); | ||
return _inAltBuffer() ? _mutableViewport.Height() - 1 : _mutableViewport.BottomInclusive(); | ||
} | ||
|
||
// _VisibleStartIndex is the first visible line of the buffer | ||
int Terminal::_VisibleStartIndex() const noexcept | ||
{ | ||
return std::max(0, ViewStartIndex() - _scrollOffset); | ||
return _inAltBuffer() ? ViewStartIndex() : | ||
std::max(0, ViewStartIndex() - _scrollOffset); | ||
} | ||
|
||
int Terminal::_VisibleEndIndex() const noexcept | ||
{ | ||
return std::max(0, ViewEndIndex() - _scrollOffset); | ||
return _inAltBuffer() ? ViewEndIndex() : | ||
std::max(0, ViewEndIndex() - _scrollOffset); | ||
} |
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 am guessing they are necessary because of _scrollOffset
... which makes me wonder how we account for the scroll offset being per-buffer but global state
_mainBuffer->SetAsActiveBuffer(true); | ||
|
||
// destroy the alt buffer | ||
_altBuffer = nullptr; | ||
|
||
// update all the hyperlinks on the screen | ||
UpdatePatternsUnderLock(); | ||
|
||
// Update scrollbars | ||
_NotifyScrollEvent(); | ||
|
||
// redraw the screen | ||
try | ||
{ | ||
_activeBuffer().TriggerRedrawAll(); | ||
} | ||
CATCH_LOG(); |
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 like it should be a helper for switching buffers, but I understand if that is not tenable
Thanks a lot @DHowett for this !! It was probably the most annoying Terminal bug in my day to day use. Please tell me this is a backport candidate for the 1.12 series :D (stuck on 1909...) |
@zadjii-msft deserves all the credit :) Unfortunately, a backport isn't in the cards just yet; the 1.13 and 1.14 arcs have taken some changes to how we handle and dispatch VT sequences, so it probably wouldn't just slot in . . . and I'm a bit worried about maintaining our stable release as we backport features into it 😄 |
I understand. Thanks @zadjii-msft for this :) By curisority, if I clone the code and build it myself form |
That's an excellent question. I can't recall which feature it was that we added which will break on 19H1... If you edit the |
Thanks. I'll try it. |
I think it might just work. The quirks around opacity that we're hoping will be fixed in Vb aren't removed in |
@phil-blain FYI You may want to wait until #12719 merges - there's a fix somewhere in there for some weird bugs around resizing the alt buffer that are present in this PR. I was hoping to merge them a little closer together, still waiting for CI to spin on that one. |
Sorry to bother you guys again - do you happen to know off-hand how much disk space does the build need ? I tried building in GitHub actions which has 14 GB of disk space and it seems it is not enough... |
OK thanks for confirming :) local VM it is then! |
🎉 Handy links: |
This PR allows the Terminal to actually use the alt buffer
appropriately. Currently, we just render the alt buffer state into the
main buffer and that is wild. It means things like
vim
will let theuser scroll up to see the previous history (which it shouldn't).
Very first thing this PR does: updates the
{Trigger|Invalidate}Circling
methods to instead be{Trigger|Invalidate}Flush(bool circling)
. We need this so that when anapp requests the alt buffer in conpty, we can immediately flush the
frame before asking the Terminal side to switch to the other buffer. The
Circling
methods was a great place to do this, but we don't actuallywant to set the circled flag in VtRenderer when that happens just for a
flush.
The Terminal's implementation is a little different than conhost's.
Conhost's implementation grew organically, so I had it straight up
create an entire new screen buffer for the alt buffer. The Terminal
doesn't need all that! All we need to do is have a separate
TextBuffer
for the alt buffer contents. This makes other parts easier as well - we
don't really need to do anything with the
_mutableViewport
in the altbuffer, because it's always in the same place. So, we can just leave it
alone and when we come back to the main buffer, there it is. Helper
methods have been updated to account for this.