Skip to content

Commit df9bf4e

Browse files
committed
Merged PR 3813180: Migrate selected GitHub changes up to 6b728cd
- Remember last-used string in the Find dialog in conhost (GH-2845) (cherry picked from commit bfb1484) - Bugfix: TextBuffer Line Wrapping from VTRenderer (GH-2797) Change VT renderer to do erase line instead of a ton of erase chars (cherry picked from commit 8afc5b2) - Add some retry support to Renderer::PaintFrame (GH-2830) If _PaintFrameForEngine returns E_PENDING, we'll give it another two tries to get itself straight. If it continues to fail, we'll take down the application. We observed that the DX renderer was failing to present the swap chain and failfast'ing when it did so; however, there are some errors from which DXGI guidance suggests we try to recover. We'll now return E_PENDING (and destroy our device resources) when we hit those errors. Fixes GH-2265. (cherry picked from commit 277acc3) - Enable VT processing by default for ConPTY (GH-2824) This change enables VT processing by default for _all_ conpty clients. See GH-1965 for a discussion on why we believe this is a righteous change. Also mentioned in the issue was the idea of only checking the `VirtualTerminalLevel` reg key in the conpty startup. I don't think this would be a more difficult change, looks like all we'd need is a simple `reg.LoadGlobalsFromRegistry();` call instead of this change. **Validation Steps Performed** Manually launched a scratch app in both the terminal and the console. The console launch's output mode was 0x3, and the terminal's was 0x7. 0x4 is the ` ENABLE_VIRTUAL_TERMINAL_PROCESSING` flag, which the client now had by default in the Terminal. Closes GH-1965 (cherry picked from commit 1c412d4) - Remove unwanted DECSTBM clipping (GH-2764) The `DECSTBM` margins are meant to define the range of lines within which certain vertical scrolling operations take place. However, we were applying these margin restrictions in the `ScrollRegion` function, which is also used in a number of places that shouldn't be affected by `DECSTBM`. This includes the `ICH` and `DCH` escape sequences (which are only affected by the horizontal margins, which we don't yet support), the `ScrollConsoleScreenBuffer` API (which is public Console API, not meant to be affected by the VT terminal emulation), and the `CSI 3 J` erase scrollback extension (which isn't really scrolling as such, but uses the `ScrollRegion` function to manipulate the scrollback buffer). This commit moves the margin clipping out of the `ScrollRegion` function, so it can be applied exclusively in the places that need it. While testing, I also noticed that one of the `ScrollRegion` calls in the `AdjustCursorPosition` function was not setting the horizontal range correctly - the scrolling should always affect the full buffer width rather than just the viewport width - so ... Related work items: #23507749, #23563809, #23563819, #23563837, #23563841, #23563844
2 parents 93bc0af + 2e17593 commit df9bf4e

File tree

18 files changed

+183
-78
lines changed

18 files changed

+183
-78
lines changed

src/cascadia/TerminalCore/lib/terminalcore-lib.vcxproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<ProjectName>TerminalCore</ProjectName>
1414
<TargetName>TerminalCore</TargetName>
1515
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
16-
<WindowsTargetPlatformVersion>10.0.17763.0</WindowsTargetPlatformVersion>
16+
<WindowsTargetPlatformVersion>10.0.18362.0</WindowsTargetPlatformVersion>
1717
<RootNamespace>Microsoft.Terminal.Core</RootNamespace>
1818
</PropertyGroup>
1919
<!-- ============================ References ============================ -->

src/host/_stream.cpp

+3-27
Original file line numberDiff line numberDiff line change
@@ -140,15 +140,11 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
140140
const COORD newPostMarginsOrigin = { 0, moveToYPosition };
141141
const COORD newViewOrigin = { 0, newViewTop };
142142

143-
// Unset the margins to scroll the content below the margins,
144-
// then restore them after.
145-
screenInfo.SetScrollMargins(Viewport::FromInclusive({ 0 }));
146143
try
147144
{
148145
ScrollRegion(screenInfo, scrollRect, std::nullopt, newPostMarginsOrigin, UNICODE_SPACE, bufferAttributes);
149146
}
150147
CATCH_LOG();
151-
screenInfo.SetScrollMargins(relativeMargins);
152148

153149
// Move the viewport down
154150
auto hr = screenInfo.SetViewportOrigin(true, newViewOrigin, true);
@@ -186,39 +182,19 @@ using Microsoft::Console::VirtualTerminal::StateMachine;
186182
SMALL_RECT scrollRect = { 0 };
187183
scrollRect.Top = srMargins.Top;
188184
scrollRect.Bottom = srMargins.Bottom;
189-
scrollRect.Left = screenInfo.GetViewport().Left(); // NOTE: Left/Right Scroll margins don't do anything currently.
190-
scrollRect.Right = screenInfo.GetViewport().RightInclusive();
185+
scrollRect.Left = 0; // NOTE: Left/Right Scroll margins don't do anything currently.
186+
scrollRect.Right = bufferSize.X - 1; // -1, otherwise this would be an exclusive rect.
191187

192188
COORD dest;
193189
dest.X = scrollRect.Left;
194190
dest.Y = scrollRect.Top - diff;
195191

196-
SMALL_RECT clipRect = scrollRect;
197-
// Typically ScrollRegion() clips by the scroll margins. However, if
198-
// we're scrolling down at the top of the viewport, we'll need to
199-
// not clip at the margins, instead move the contents of the margins
200-
// up above the viewport. So we'll clear out the current margins, and
201-
// set them to the viewport+(#diff rows above the viewport).
202-
if (scrollDownAtTop)
203-
{
204-
clipRect.Top -= diff;
205-
auto fakeMargins = srMargins;
206-
fakeMargins.Top -= diff;
207-
auto fakeRelative = viewport.ConvertToOrigin(Viewport::FromInclusive(fakeMargins));
208-
screenInfo.SetScrollMargins(fakeRelative);
209-
}
210-
211192
try
212193
{
213-
ScrollRegion(screenInfo, scrollRect, clipRect, dest, UNICODE_SPACE, bufferAttributes);
194+
ScrollRegion(screenInfo, scrollRect, scrollRect, dest, UNICODE_SPACE, bufferAttributes);
214195
}
215196
CATCH_LOG();
216197

217-
if (scrollDownAtTop)
218-
{
219-
// Undo the fake margins we set above
220-
screenInfo.SetScrollMargins(relativeMargins);
221-
}
222198
coordCursor.Y -= diff;
223199
}
224200

src/host/getset.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -1368,6 +1368,11 @@ void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool
13681368
srScroll.Right = SHORT_MAX;
13691369
srScroll.Top = viewport.Top;
13701370
srScroll.Bottom = viewport.Bottom;
1371+
// Clip to the DECSTBM margin boundary
1372+
if (screenInfo.AreMarginsSet())
1373+
{
1374+
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
1375+
}
13711376
// Paste coordinate for cut text above
13721377
COORD coordDestination;
13731378
coordDestination.X = 0;
@@ -2044,6 +2049,11 @@ void DoSrvPrivateModifyLinesImpl(const unsigned int count, const bool insert)
20442049
srScroll.Right = SHORT_MAX;
20452050
srScroll.Top = cursorPosition.Y;
20462051
srScroll.Bottom = screenInfo.GetViewport().BottomInclusive();
2052+
// Clip to the DECSTBM margin boundary
2053+
if (screenInfo.AreMarginsSet())
2054+
{
2055+
srScroll.Bottom = screenInfo.GetAbsoluteScrollMargins().BottomInclusive();
2056+
}
20472057
// Paste coordinate for cut text above
20482058
COORD coordDestination;
20492059
coordDestination.X = 0;

src/host/output.cpp

-25
Original file line numberDiff line numberDiff line change
@@ -361,19 +361,6 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
361361
// If there was no clip rect, we'll clip to the entire buffer size.
362362
auto clip = Viewport::FromInclusive(clipRectGiven.value_or(buffer.ToInclusive()));
363363

364-
// Account for the scroll margins set by DECSTBM
365-
// DECSTBM command can sometimes apply a clipping behavior as well. Check if we have any
366-
// margins defined by DECSTBM and further restrict the clipping area here.
367-
if (screenInfo.AreMarginsSet())
368-
{
369-
const auto margin = screenInfo.GetScrollingRegion();
370-
371-
// Update the clip rectangle to only include the area that is also in the margin.
372-
clip = Viewport::Intersect(clip, margin);
373-
374-
// We'll also need to update the source rectangle, but we need to do that later.
375-
}
376-
377364
// OK, make sure that the clip rectangle also fits inside the buffer
378365
clip = Viewport::Intersect(buffer, clip);
379366

@@ -416,18 +403,6 @@ void ScrollRegion(SCREEN_INFORMATION& screenInfo,
416403
targetOrigin.Y += currentSourceOrigin.Y - originalSourceOrigin.Y;
417404
}
418405

419-
// See MSFT:20204600 - Update the source rectangle to only include the region
420-
// inside the scroll margins. We need to do this AFTER we calculate the
421-
// delta between the currentSourceOrigin and the originalSourceOrigin.
422-
// Don't combine this with the above block, because if there are margins set
423-
// and the source rectangle was clipped by the buffer, we still want to
424-
// adjust the target origin point based on the clipping of the buffer.
425-
if (screenInfo.AreMarginsSet())
426-
{
427-
const auto margin = screenInfo.GetScrollingRegion();
428-
source = Viewport::Intersect(source, margin);
429-
}
430-
431406
// And now the target viewport is the same size as the source viewport but at the different position.
432407
auto target = Viewport::FromDimensions(targetOrigin, source.Dimensions());
433408

src/host/settings.cpp

+18
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,24 @@ void Settings::Validate()
326326
WI_ClearAllFlags(_wFillAttribute, ~(FG_ATTRS | BG_ATTRS));
327327
WI_ClearAllFlags(_wPopupFillAttribute, ~(FG_ATTRS | BG_ATTRS));
328328

329+
// If the extended color options are set to invalid values (all the same color), reset them.
330+
if (_CursorColor != Cursor::s_InvertCursorColor && _CursorColor == _DefaultBackground)
331+
{
332+
_CursorColor = Cursor::s_InvertCursorColor;
333+
}
334+
335+
if (_DefaultForeground != INVALID_COLOR && _DefaultForeground == _DefaultBackground)
336+
{
337+
// INVALID_COLOR is used as an "unset" sentinel in future attribute functions.
338+
_DefaultForeground = _DefaultBackground = INVALID_COLOR;
339+
// If the damaged settings _further_ propagated to the default fill attribute, fix it.
340+
if (_wFillAttribute == 0)
341+
{
342+
// These attributes were taken from the Settings ctor and equal "gray on black"
343+
_wFillAttribute = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
344+
}
345+
}
346+
329347
FAIL_FAST_IF(!(_dwWindowSize.X > 0));
330348
FAIL_FAST_IF(!(_dwWindowSize.Y > 0));
331349
FAIL_FAST_IF(!(_dwScreenBufferSize.X > 0));

src/host/srvinit.cpp

+11
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,17 @@ static bool s_IsOnDesktop()
139139
reg.LoadFromRegistry(Title);
140140
}
141141
}
142+
else
143+
{
144+
// microsoft/terminal#1965 - Let's just always enable VT processing by
145+
// default for conpty clients. This prevents peculiar differences in
146+
// behavior between conhost and terminal applications when the user has
147+
// VirtualTerminalLevel=1 in their registry.
148+
// We want everyone to be using VT by default anyways, so this is a
149+
// strong nudge in that direction. If an application _doesn't_ want VT
150+
// processing, it's free to disable this setting, even in conpty mode.
151+
settings.SetVirtTermLevel(1);
152+
}
142153

143154
// 1. The settings we were passed contains STARTUPINFO structure settings to be applied last.
144155
settings.ApplyStartupInfo(pStartupSettings);

src/host/ut_host/ApiRoutinesTests.cpp

+10
Original file line numberDiff line numberDiff line change
@@ -594,9 +594,12 @@ class ApiRoutinesTests
594594
TEST_METHOD(ApiScrollConsoleScreenBufferW)
595595
{
596596
BEGIN_TEST_METHOD_PROPERTIES()
597+
TEST_METHOD_PROPERTY(L"data:setMargins", L"{false, true}")
597598
TEST_METHOD_PROPERTY(L"data:checkClipped", L"{false, true}")
598599
END_TEST_METHOD_PROPERTIES();
599600

601+
bool setMargins;
602+
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins), L"Get whether or not we should set the DECSTBM margins.");
600603
bool checkClipped;
601604
VERIFY_SUCCEEDED(TestData::TryGetValue(L"checkClipped", checkClipped), L"Get whether or not we should check all the options using a clipping rectangle.");
602605

@@ -605,6 +608,13 @@ class ApiRoutinesTests
605608

606609
VERIFY_SUCCEEDED(si.GetTextBuffer().ResizeTraditional({ 5, 5 }), L"Make the buffer small so this doesn't take forever.");
607610

611+
// Tests are run both with and without the DECSTBM margins set. This should not alter
612+
// the results, since ScrollConsoleScreenBuffer should not be affected by VT margins.
613+
auto& stateMachine = si.GetStateMachine();
614+
stateMachine.ProcessString(setMargins ? L"\x1b[2;4r" : L"\x1b[r");
615+
// Make sure we clear the margins on exit so they can't break other tests.
616+
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
617+
608618
gci.LockConsole();
609619
auto Unlock = wil::scope_exit([&] { gci.UnlockConsole(); });
610620

src/host/ut_host/ScreenBufferTests.cpp

+26
Original file line numberDiff line numberDiff line change
@@ -3282,6 +3282,13 @@ void ScreenBufferTests::ScrollOperations()
32823282

32833283
void ScreenBufferTests::InsertChars()
32843284
{
3285+
BEGIN_TEST_METHOD_PROPERTIES()
3286+
TEST_METHOD_PROPERTY(L"Data:setMargins", L"{false, true}")
3287+
END_TEST_METHOD_PROPERTIES();
3288+
3289+
bool setMargins;
3290+
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins));
3291+
32853292
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
32863293
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
32873294
auto& stateMachine = si.GetStateMachine();
@@ -3295,6 +3302,12 @@ void ScreenBufferTests::InsertChars()
32953302
VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false));
32963303
si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true);
32973304

3305+
// Tests are run both with and without the DECSTBM margins set. This should not alter
3306+
// the results, since the ICH operation is not affected by vertical margins.
3307+
stateMachine.ProcessString(setMargins ? L"\x1b[15;20r" : L"\x1b[r");
3308+
// Make sure we clear the margins on exit so they can't break other tests.
3309+
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
3310+
32983311
Log::Comment(
32993312
L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. "
33003313
L"Then insert 5 spaces at the cursor. Watch spaces get inserted, text slides right "
@@ -3425,6 +3438,13 @@ void ScreenBufferTests::InsertChars()
34253438

34263439
void ScreenBufferTests::DeleteChars()
34273440
{
3441+
BEGIN_TEST_METHOD_PROPERTIES()
3442+
TEST_METHOD_PROPERTY(L"Data:setMargins", L"{false, true}")
3443+
END_TEST_METHOD_PROPERTIES();
3444+
3445+
bool setMargins;
3446+
VERIFY_SUCCEEDED(TestData::TryGetValue(L"setMargins", setMargins));
3447+
34283448
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
34293449
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
34303450
auto& stateMachine = si.GetStateMachine();
@@ -3438,6 +3458,12 @@ void ScreenBufferTests::DeleteChars()
34383458
VERIFY_SUCCEEDED(si.ResizeScreenBuffer({ bufferWidth, bufferHeight }, false));
34393459
si.SetViewport(Viewport::FromExclusive({ viewportStart, 0, viewportEnd, 25 }), true);
34403460

3461+
// Tests are run both with and without the DECSTBM margins set. This should not alter
3462+
// the results, since the DCH operation is not affected by vertical margins.
3463+
stateMachine.ProcessString(setMargins ? L"\x1b[15;20r" : L"\x1b[r");
3464+
// Make sure we clear the margins on exit so they can't break other tests.
3465+
auto clearMargins = wil::scope_exit([&] { stateMachine.ProcessString(L"\x1b[r"); });
3466+
34413467
Log::Comment(
34423468
L"Test 1: Fill the line with Qs. Write some text within the viewport boundaries. "
34433469
L"Then delete 5 characters at the cursor. Watch the rest of the line slide left, "

src/interactivity/win32/find.cpp

+5-1
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,16 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l
2323
// This bool is used to track which option - up or down - was used to perform the last search. That way, the next time the
2424
// find dialog is opened, it will default to the last used option.
2525
static bool fFindSearchUp = true;
26+
static std::wstring lastFindString;
27+
2628
WCHAR szBuf[SEARCH_STRING_LENGTH + 1];
2729
switch (Message)
2830
{
2931
case WM_INITDIALOG:
3032
SetWindowLongPtrW(hWnd, DWLP_USER, lParam);
3133
SendDlgItemMessageW(hWnd, ID_CONSOLE_FINDSTR, EM_LIMITTEXT, ARRAYSIZE(szBuf) - 1, 0);
3234
CheckRadioButton(hWnd, ID_CONSOLE_FINDUP, ID_CONSOLE_FINDDOWN, (fFindSearchUp ? ID_CONSOLE_FINDUP : ID_CONSOLE_FINDDOWN));
35+
SetDlgItemText(hWnd, ID_CONSOLE_FINDSTR, lastFindString.c_str());
3336
return TRUE;
3437
case WM_COMMAND:
3538
{
@@ -40,6 +43,7 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l
4043
USHORT const StringLength = (USHORT)GetDlgItemTextW(hWnd, ID_CONSOLE_FINDSTR, szBuf, ARRAYSIZE(szBuf));
4144
if (StringLength == 0)
4245
{
46+
lastFindString.clear();
4347
break;
4448
}
4549
bool const IgnoreCase = IsDlgButtonChecked(hWnd, ID_CONSOLE_FINDCASE) == 0;
@@ -48,7 +52,7 @@ INT_PTR CALLBACK FindDialogProc(HWND hWnd, UINT Message, WPARAM wParam, LPARAM l
4852
SCREEN_INFORMATION& ScreenInfo = gci.GetActiveOutputBuffer();
4953

5054
std::wstring wstr(szBuf, StringLength);
51-
55+
lastFindString = wstr;
5256
LockConsole();
5357
auto Unlock = wil::scope_exit([&] { UnlockConsole(); });
5458

src/renderer/base/renderer.cpp

+17-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
using namespace Microsoft::Console::Render;
1111
using namespace Microsoft::Console::Types;
1212

13+
static constexpr auto maxRetriesForRenderEngine = 3;
14+
1315
// Routine Description:
1416
// - Creates a new renderer controller for a console.
1517
// Arguments:
@@ -62,7 +64,21 @@ Renderer::~Renderer()
6264

6365
for (IRenderEngine* const pEngine : _rgpEngines)
6466
{
65-
LOG_IF_FAILED(_PaintFrameForEngine(pEngine));
67+
auto tries = maxRetriesForRenderEngine;
68+
while (tries > 0)
69+
{
70+
const auto hr = _PaintFrameForEngine(pEngine);
71+
if (E_PENDING == hr)
72+
{
73+
if (--tries == 0)
74+
{
75+
FAIL_FAST_HR_MSG(E_UNEXPECTED, "A rendering engine required too many retries.");
76+
}
77+
continue;
78+
}
79+
LOG_IF_FAILED(hr);
80+
break;
81+
}
6682
}
6783

6884
return S_OK;

src/renderer/dx/DxRenderer.cpp

+19-3
Original file line numberDiff line numberDiff line change
@@ -866,15 +866,31 @@ void DxEngine::_InvalidOr(RECT rc) noexcept
866866
// Arguments:
867867
// - <none>
868868
// Return Value:
869-
// - S_OK or relevant DirectX error
869+
// - S_OK on success, E_PENDING to indicate a retry or a relevant DirectX error
870870
[[nodiscard]] HRESULT DxEngine::Present() noexcept
871871
{
872872
if (_presentReady)
873873
{
874874
try
875875
{
876-
FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present(1, 0));
877-
/*FAIL_FAST_IF_FAILED(_dxgiSwapChain->Present1(1, 0, &_presentParams));*/
876+
HRESULT hr = S_OK;
877+
878+
hr = _dxgiSwapChain->Present(1, 0);
879+
/*hr = _dxgiSwapChain->Present1(1, 0, &_presentParams);*/
880+
881+
if (FAILED(hr))
882+
{
883+
// These two error codes are indicated for destroy-and-recreate
884+
if (hr == DXGI_ERROR_DEVICE_REMOVED || hr == DXGI_ERROR_DEVICE_RESET)
885+
{
886+
// We don't need to end painting here, as the renderer has done it for us.
887+
_ReleaseDeviceResources();
888+
FAIL_FAST_IF_FAILED(InvalidateAll());
889+
return E_PENDING; // Indicate a retry to the renderer.
890+
}
891+
892+
FAIL_FAST_HR(hr);
893+
}
878894

879895
RETURN_IF_FAILED(_CopyFrontToBack());
880896
_presentReady = false;

src/renderer/vt/paint.cpp

+9-1
Original file line numberDiff line numberDiff line change
@@ -475,14 +475,22 @@ using namespace Microsoft::Console::Types;
475475

476476
if (useEraseChar)
477477
{
478-
RETURN_IF_FAILED(_EraseCharacter(sNumSpaces));
479478
// ECH doesn't actually move the cursor itself. However, we think that
480479
// the cursor *should* be at the end of the area we just erased. Stash
481480
// that position as our new deferred position. If we don't move the
482481
// cursor somewhere else before the end of the frame, we'll move the
483482
// cursor to the deferred position at the end of the frame, or right
484483
// before we need to print new text.
485484
_deferredCursorPos = { _lastText.X + sNumSpaces, _lastText.Y };
485+
486+
if (_deferredCursorPos.X < _lastViewport.RightInclusive())
487+
{
488+
RETURN_IF_FAILED(_EraseCharacter(sNumSpaces));
489+
}
490+
else
491+
{
492+
RETURN_IF_FAILED(_EraseLine());
493+
}
486494
}
487495
else if (_newBottomLine)
488496
{

0 commit comments

Comments
 (0)