Add support for the DECALN escape sequence (#3968)

## Summary of the Pull Request

This adds support for the [`DECALN`](https://vt100.net/docs/vt510-rm/DECALN.html) escape sequence, which produces a kind of test pattern, originally used on VT terminals to adjust the screen alignment. It's needed to pass several of the tests in the [Vttest](https://invisible-island.net/vttest/) suite.

## PR Checklist
* [x] Closes #3671
* [x] CLA signed. If not, go over [here](https://cla.opensource.microsoft.com/microsoft/Terminal) and sign the CLA
* [x] Tests added/passed
* [ ] Requires documentation to be updated
* [ ] I've discussed this with core contributors already. If not checked, I'm ready to accept this work might be rejected in favor of a different grand plan. Issue number where discussion took place: #xxx

## Detailed Description of the Pull Request / Additional comments

To start with, the `ActionEscDispatch` method in the `OutputStateMachineEngine` needed to be extended to check for a new intermediate type (`#`). Then when that intermediate is followed by an `8`, it dispatches to a new `ScreenAlignmentPattern` method in the `ITermDispatch` interface.

The implementation of the `ScreenAlignmentPattern` itself is fairly simple. It uses the recently added `PrivateFillRegion` API to fill the screen with the character `E` using default attributes. Then in addition to that, a bunch of VT properties are reset:

* The meta/extended attributes are reset (although the active colors must be left unchanged).
* The origin mode is set to absolute positioning.
* The scrolling margins are cleared.
* The cursor position is moved to home.

## Validation Steps Performed

I've added a screen buffer test that makes sure the `DECALN` sequence fills the screen with the correct character and attributes, and that the above mentioned properties are all updated appropriately.

I've also tested in Vttest, and confirmed that the first two pages of the _Test of cursor movements_ are now showing the frame of E's that are expected there.
This commit is contained in:
James Holderness 2019-12-17 18:11:52 +00:00 committed by msftbot[bot]
parent d7ae8e6db9
commit c0b8b85a47
9 changed files with 118 additions and 1 deletions

View File

@ -200,6 +200,8 @@ class ScreenBufferTests
TEST_METHOD(CursorUpDownExactlyAtMargins);
TEST_METHOD(CursorSaveRestore);
TEST_METHOD(ScreenAlignmentPattern);
};
void ScreenBufferTests::SingleAlternateBufferCreationTest()
@ -5519,3 +5521,62 @@ void ScreenBufferTests::CursorSaveRestore()
stateMachine.ProcessString(resetDECOM);
stateMachine.ProcessString(L"\x1b[r");
}
void ScreenBufferTests::ScreenAlignmentPattern()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer();
auto& stateMachine = si.GetStateMachine();
const auto& cursor = si.GetTextBuffer().GetCursor();
WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
Log::Comment(L"Set the initial buffer state.");
const auto bufferWidth = si.GetBufferSize().Width();
const auto bufferHeight = si.GetBufferSize().Height();
// Move the viewport down a few lines, and only cover part of the buffer width.
si.SetViewport(Viewport::FromDimensions({ 5, 10 }, { bufferWidth - 10, 30 }), true);
const auto viewportStart = si.GetViewport().Top();
const auto viewportEnd = si.GetViewport().BottomExclusive();
// Fill the entire buffer with Zs. Blue on Green.
const auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN };
_FillLines(0, bufferHeight, L'Z', bufferAttr);
// Set the initial attributes.
auto initialAttr = TextAttribute{ RGB(12, 34, 56), RGB(78, 90, 12) };
initialAttr.SetMetaAttributes(COMMON_LVB_REVERSE_VIDEO | COMMON_LVB_UNDERSCORE);
si.SetAttributes(initialAttr);
// Set some margins.
stateMachine.ProcessString(L"\x1b[10;20r");
VERIFY_IS_TRUE(si.AreMarginsSet());
// Place the cursor in the center.
auto cursorPos = COORD{ bufferWidth / 2, (viewportStart + viewportEnd) / 2 };
VERIFY_SUCCEEDED(si.SetCursorPosition(cursorPos, true));
Log::Comment(L"Execute the DECALN escape sequence.");
stateMachine.ProcessString(L"\x1b#8");
Log::Comment(L"Lines within view should be filled with Es, with default attributes.");
auto defaultAttr = TextAttribute{};
VERIFY_IS_TRUE(_ValidateLinesContain(viewportStart, viewportEnd, L'E', defaultAttr));
Log::Comment(L"Field of Zs outside viewport should remain unchanged.");
VERIFY_IS_TRUE(_ValidateLinesContain(0, viewportStart, L'Z', bufferAttr));
VERIFY_IS_TRUE(_ValidateLinesContain(viewportEnd, bufferHeight, L'Z', bufferAttr));
Log::Comment(L"Margins should not be set.");
VERIFY_IS_FALSE(si.AreMarginsSet());
Log::Comment(L"Cursor position shold be moved to home.");
auto homePosition = COORD{ 0, viewportStart };
VERIFY_ARE_EQUAL(homePosition, cursor.GetPosition());
Log::Comment(L"Meta/rendition attributes should be reset.");
auto expectedAttr = initialAttr;
expectedAttr.SetMetaAttributes(0);
VERIFY_ARE_EQUAL(expectedAttr, si.GetAttributes());
}

View File

@ -90,6 +90,7 @@ public:
virtual bool SoftReset() = 0; // DECSTR
virtual bool HardReset() = 0; // RIS
virtual bool ScreenAlignmentPattern() = 0; // DECALN
virtual bool SetCursorStyle(const DispatchTypes::CursorStyle cursorStyle) = 0; // DECSCUSR
virtual bool SetCursorColor(const COLORREF Color) = 0; // OSCSetCursorColor, OSCResetCursorColor

View File

@ -1543,6 +1543,42 @@ bool AdaptDispatch::HardReset()
return fSuccess;
}
// Routine Description:
// - DECALN - Fills the entire screen with a test pattern of uppercase Es,
// resets the margins and rendition attributes, and moves the cursor to
// the home position.
// Arguments:
// - None
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::ScreenAlignmentPattern()
{
CONSOLE_SCREEN_BUFFER_INFOEX csbiex = { 0 };
csbiex.cbSize = sizeof(CONSOLE_SCREEN_BUFFER_INFOEX);
// Make sure to reset the viewport (with MoveToBottom )to where it was
// before the user scrolled the console output
bool fSuccess = !!(_conApi->MoveToBottom() && _conApi->GetConsoleScreenBufferInfoEx(&csbiex));
if (fSuccess)
{
// Fill the screen with the letter E using the default attributes.
auto fillPosition = COORD{ 0, csbiex.srWindow.Top };
auto fillLength = (csbiex.srWindow.Bottom - csbiex.srWindow.Top) * csbiex.dwSize.X;
fSuccess = _conApi->PrivateFillRegion(fillPosition, fillLength, L'E', false);
// Reset the meta/extended attributes (but leave the colors unchanged).
fSuccess = fSuccess && _conApi->PrivateSetLegacyAttributes(0, false, false, true);
fSuccess = fSuccess && _conApi->PrivateSetExtendedTextAttributes(ExtendedAttributes::Normal);
// Reset the origin mode to absolute.
fSuccess = fSuccess && SetOriginMode(false);
// Clear the scrolling margins.
fSuccess = fSuccess && _DoSetTopBottomScrollingMargins(0, 0);
// Set the cursor position to home.
fSuccess = fSuccess && CursorPosition(1, 1);
}
return fSuccess;
}
//Routine Description:
// Erase Scrollback (^[[3J - ED extension by xterm)
// Because conhost doesn't exactly have a scrollback, We have to be tricky here.

View File

@ -84,6 +84,7 @@ namespace Microsoft::Console::VirtualTerminal
bool DesignateCharset(const wchar_t wchCharset) override; // DesignateCharset
bool SoftReset() override; // DECSTR
bool HardReset() override; // RIS
bool ScreenAlignmentPattern() override; // DECALN
bool EnableDECCOLMSupport(const bool fEnabled) override; // ?40
bool EnableVT200MouseMode(const bool fEnabled) override; // ?1000
bool EnableUTF8ExtendedMouseMode(const bool fEnabled) override; // ?1005

View File

@ -87,6 +87,7 @@ public:
bool SoftReset() override { return false; } // DECSTR
bool HardReset() override { return false; } // RIS
bool ScreenAlignmentPattern() override { return false; } // DECALN
bool SetCursorStyle(const DispatchTypes::CursorStyle /*cursorStyle*/) override { return false; } // DECSCUSR
bool SetCursorColor(const COLORREF /*Color*/) override { return false; } // OSCSetCursorColor, OSCResetCursorColor

View File

@ -257,6 +257,20 @@ bool OutputStateMachineEngine::ActionEscDispatch(const wchar_t wch,
break;
}
}
else if (wchIntermediate == L'#')
{
switch (wch)
{
case VTActionCodes::DECALN_ScreenAlignmentPattern:
fSuccess = _dispatch->ScreenAlignmentPattern();
TermTelemetry::Instance().Log(TermTelemetry::Codes::DECALN);
break;
default:
// If no functions to call, overall dispatch was a failure.
fSuccess = false;
break;
}
}
}
_ClearLastChar();

View File

@ -127,7 +127,8 @@ namespace Microsoft::Console::VirtualTerminal
// 'q' is overloaded - no postfix is DECLL, ' ' postfix is DECSCUSR, and '"' is DECSCA
DECSCUSR_SetCursorStyle = L'q', // I believe we'll only ever implement DECSCUSR
DTTERM_WindowManipulation = L't',
REP_RepeatCharacter = L'b'
REP_RepeatCharacter = L'b',
DECALN_ScreenAlignmentPattern = L'8'
};
enum OscActionCodes : unsigned int

View File

@ -246,6 +246,7 @@ void TermTelemetry::WriteFinalTraceLog() const
TraceLoggingUInt32(_uiTimesUsed[OSCFG], "OscForegroundColor"),
TraceLoggingUInt32(_uiTimesUsed[OSCBG], "OscBackgroundColor"),
TraceLoggingUInt32(_uiTimesUsed[REP], "REP"),
TraceLoggingUInt32(_uiTimesUsed[DECALN], "DECALN"),
TraceLoggingUInt32Array(_uiTimesFailed, ARRAYSIZE(_uiTimesFailed), "Failed"),
TraceLoggingUInt32(_uiTimesFailedOutsideRange, "FailedOutsideRange"));
}

View File

@ -84,6 +84,7 @@ namespace Microsoft::Console::VirtualTerminal
REP,
OSCFG,
OSCBG,
DECALN,
// Only use this last enum as a count of the number of codes.
NUMBER_OF_CODES
};