Skip to content
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

Merged
merged 20 commits into from
Apr 12, 2022

Conversation

zadjii-msft
Copy link
Member

@zadjii-msft zadjii-msft commented Feb 23, 2022

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 the
user 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 an
app 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 actually
want 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 alt
buffer, 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.

@ghost ghost added Area-Interop Communication between processes Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Priority-1 A description (P1) Product-Conpty For console issues specifically related to conpty Product-Terminal The new Windows Terminal. labels Feb 23, 2022
@github-actions

This comment was marked as resolved.

zadjii-msft added a commit that referenced this pull request Feb 24, 2022
_⚠️ 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
```
@DHowett
Copy link
Member

DHowett commented Feb 24, 2022

/cc @j4james for opinions since he's intimately familiar with these codepaths :)

@DHowett
Copy link
Member

DHowett commented Feb 25, 2022

Does TermControl's scroll bar automatically do the right thing here? I'm pleasantly surprised to not see anything in TC (whole project!) changing :)

@zadjii-msft
Copy link
Member Author

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

@codeofdusk
Copy link
Contributor

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).

@zadjii-msft
Copy link
Member Author

zadjii-msft commented Feb 25, 2022

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 😛)

EDIT:
An image of the console hosting infrastructure printing the entire contents of the screen as though it was new upon exit from the alt buffer.

Money sequences are between the red input.
Interestingly, we clear the screen... then switch back to the main buffer? Might be a artifact of mc being weird, but I didn't have time today to set up simpler tests.

Another test:
A second image of the console hosting infrastructure printing the entire contents of the screen as though it was new upon exit from the alt buffer.

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.

@zadjii-msft
Copy link
Member Author

Alrighty so the dependent PRs here (#12569, #12719) have the signoffs, so we just need eyes on this one. I want to merge them all in one day, since yea, there's something weird about this one without #12719, but the combined diff is A LOT to review.

// any scrollback, so we just need to resize it and presto, we're done.
if (_inAltBuffer())
{
_altBuffer->GetCursor().StartDeferDrawing();
Copy link
Member

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()?

Comment on lines 947 to 980
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);
}
Copy link
Member

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.

Copy link
Member

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

Copy link
Member

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);
Copy link
Member

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.

Copy link
Member

@DHowett DHowett left a 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());
Copy link
Member

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?

Copy link
Member Author

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.

src/renderer/vt/VtSequences.cpp Outdated Show resolved Hide resolved
// - 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");
Copy link
Member

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

Copy link
Member Author

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.");
Copy link
Member

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.
Copy link
Member

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

Copy link
Member Author

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.

src/cascadia/TerminalCore/Terminal.hpp Outdated Show resolved Hide resolved
Comment on lines 947 to 980
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);
}
Copy link
Member

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

src/cascadia/TerminalCore/Terminal.cpp Outdated Show resolved Hide resolved
src/cascadia/TerminalCore/TerminalApi.cpp Show resolved Hide resolved
Comment on lines 664 to 680
_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();
Copy link
Member

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

@ghost ghost added Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something and removed Needs-Author-Feedback The original author of the issue/PR needs to come back and respond to something labels Apr 12, 2022
@DHowett DHowett merged commit 13fb1f5 into main Apr 12, 2022
@DHowett DHowett deleted the dev/migrie/b/alt-buffer-terminal branch April 12, 2022 22:19
@phil-blain
Copy link

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...)

@DHowett
Copy link
Member

DHowett commented Apr 13, 2022

@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 😄

@phil-blain
Copy link

I understand. Thanks @zadjii-msft for this :)

By curisority, if I clone the code and build it myself form main on 1909, will it not build ? or will it build, but some of the feature not work ?

@DHowett
Copy link
Member

DHowett commented Apr 13, 2022

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 Package-Dev.appxmanifest to specify a minimum version of 10.0.18362.0 it will probably build and deploy, and it might even work. YMMV!

@phil-blain
Copy link

Thanks. I'll try it.

@zadjii-msft
Copy link
Member Author

I think it might just work. The quirks around opacity that we're hoping will be fixed in Vb aren't removed in main yet, and we haven't added nested context menus yet, so that crash shouldn't be an issue.

@zadjii-msft
Copy link
Member Author

@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.

@phil-blain
Copy link

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...

@zadjii-msft
Copy link
Member Author

Oh it requires absolutely enormous amounts 😭 My obj folder is in excess of 26GB, just for Debug:
image

cppwinrt is just like that 🤷

@phil-blain
Copy link

OK thanks for confirming :) local VM it is then!

@ghost
Copy link

ghost commented May 24, 2022

🎉Windows Terminal Preview v1.14.143 has been released which incorporates this pull request.:tada:

Handy links:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Interop Communication between processes Area-Output Related to output processing (inserting text into buffer, retrieving buffer text, etc.) Area-TerminalControl Issues pertaining to the terminal control (input, selection, keybindings, mouse interaction, etc.) Issue-Bug It either shouldn't be doing this or needs an investigation. Issue-Feature Complex enough to require an in depth planning process and actual budgeted, scheduled work. Priority-1 A description (P1) Product-Conpty For console issues specifically related to conpty Product-Terminal The new Windows Terminal.
Projects
None yet
6 participants