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

Add support for VT100 Auto Wrap Mode (DECAWM) #3943

Merged
11 commits merged into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions src/host/_stream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
const BOOL fKeepCursorVisible,
_Inout_opt_ PSHORT psScrollY)
{
const bool inVtMode = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
const COORD bufferSize = screenInfo.GetBufferSize().Dimensions();
if (coordCursor.X < 0)
{
Expand All @@ -70,7 +71,16 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
}
else
{
coordCursor.X = screenInfo.GetTextBuffer().GetCursor().GetPosition().X;
if (inVtMode)
{
// In VT mode, the cursor must be left in the last column.
coordCursor.X = bufferSize.X - 1;
}
else
{
// For legacy apps, it is left where it was at the start of the write.
coordCursor.X = screenInfo.GetTextBuffer().GetCursor().GetPosition().X;
}
}
}

Expand All @@ -85,7 +95,6 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
const bool fMarginsSet = srMargins.Bottom > srMargins.Top;
COORD currentCursor = screenInfo.GetTextBuffer().GetCursor().GetPosition();
const int iCurrentCursorY = currentCursor.Y;
const bool inVtMode = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);

const bool fCursorInMargins = iCurrentCursorY <= srMargins.Bottom && iCurrentCursorY >= srMargins.Top;
const bool cursorAboveViewport = coordCursor.Y < 0 && inVtMode;
Expand Down Expand Up @@ -308,6 +317,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
WCHAR LocalBuffer[LOCAL_BUFFER_SIZE];
size_t TempNumSpaces = 0;
const bool fUnprocessed = WI_IsFlagClear(screenInfo.OutputMode, ENABLE_PROCESSED_OUTPUT);
const bool fWrapAtEOL = WI_IsFlagSet(screenInfo.OutputMode, ENABLE_WRAP_AT_EOL_OUTPUT);

// Must not adjust cursor here. It has to stay on for many write scenarios. Consumers should call for the
// cursor to be turned off if they want that.
Expand All @@ -323,7 +333,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
while (*pcb < BufferSize)
{
// correct for delayed EOL
if (cursor.IsDelayedEOLWrap())
if (cursor.IsDelayedEOLWrap() && fWrapAtEOL)
{
const COORD coordDelayedAt = cursor.GetDelayedAtPosition();
cursor.ResetDelayEOLWrap();
Expand Down Expand Up @@ -509,7 +519,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
CursorPosition.X = XPosition;

// enforce a delayed newline if we're about to pass the end and the WC_DELAY_EOL_WRAP flag is set.
if (WI_IsFlagSet(dwFlags, WC_DELAY_EOL_WRAP) && CursorPosition.X >= coordScreenBufferSize.X)
if (WI_IsFlagSet(dwFlags, WC_DELAY_EOL_WRAP) && CursorPosition.X >= coordScreenBufferSize.X && fWrapAtEOL)
{
// Our cursor position as of this time is going to remain on the last position in this column.
CursorPosition.X = coordScreenBufferSize.X - 1;
Expand Down Expand Up @@ -678,7 +688,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
}
CATCH_LOG();
}
if (cursor.GetPosition().X == 0 && (screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT) && pwchBuffer > pwchBufferBackupLimit)
if (cursor.GetPosition().X == 0 && fWrapAtEOL && pwchBuffer > pwchBufferBackupLimit)
{
if (CheckBisectProcessW(screenInfo,
pwchBufferBackupLimit,
Expand Down Expand Up @@ -778,7 +788,7 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
if (Char >= UNICODE_SPACE &&
IsGlyphFullWidth(Char) &&
XPosition >= (coordScreenBufferSize.X - 1) &&
(screenInfo.OutputMode & ENABLE_WRAP_AT_EOL_OUTPUT))
fWrapAtEOL)
{
const COORD TargetPoint = cursor.GetPosition();
ROW& Row = textBuffer.GetRowByOffset(TargetPoint.Y);
Expand Down
16 changes: 16 additions & 0 deletions src/host/getset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1320,6 +1320,22 @@ void ApiRoutines::GetConsoleDisplayModeImpl(ULONG& flags) noexcept
}
}

// Routine Description:
// - A private API call for setting the ENABLE_WRAP_AT_EOL_OUTPUT mode.
// This controls whether the cursor moves to the beginning of the next row
// when it reaches the end of the current row.
// Parameters:
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
// Return value:
// - STATUS_SUCCESS if handled successfully.
[[nodiscard]] NTSTATUS DoSrvPrivateSetAutoWrapMode(const bool wrapAtEOL)
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& outputMode = gci.GetActiveOutputBuffer().GetActiveBuffer().OutputMode;
WI_UpdateFlag(outputMode, ENABLE_WRAP_AT_EOL_OUTPUT, wrapAtEOL);
return STATUS_SUCCESS;
}

// Routine Description:
// - A private API call for making the cursor visible or not. Does not modify
// blinking state.
Expand Down
1 change: 1 addition & 0 deletions src/host/getset.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ void DoSrvPrivateSetDefaultAttributes(SCREEN_INFORMATION& screenInfo, const bool
[[nodiscard]] NTSTATUS DoSrvPrivateSetKeypadMode(_In_ bool fApplicationMode);

[[nodiscard]] NTSTATUS DoSrvPrivateSetScreenMode(const bool reverseMode);
[[nodiscard]] NTSTATUS DoSrvPrivateSetAutoWrapMode(const bool wrapAtEOL);

void DoSrvPrivateShowCursor(SCREEN_INFORMATION& screenInfo, const bool show) noexcept;
void DoSrvPrivateAllowCursorBlinking(SCREEN_INFORMATION& screenInfo, const bool fEnable);
Expand Down
13 changes: 13 additions & 0 deletions src/host/outputStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,6 +358,19 @@ bool ConhostInternalGetSet::PrivateSetScreenMode(const bool reverseMode)
return NT_SUCCESS(DoSrvPrivateSetScreenMode(reverseMode));
}

// Routine Description:
// - Connects the PrivateSetAutoWrapMode call directly into our Driver Message servicing call inside Conhost.exe
// PrivateSetAutoWrapMode is an internal-only "API" call that the vt commands can execute,
// but it is not represented as a function call on out public API surface.
// Arguments:
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
// Return Value:
// - true if successful (see DoSrvPrivateSetAutoWrapMode). false otherwise.
bool ConhostInternalGetSet::PrivateSetAutoWrapMode(const bool wrapAtEOL)
{
return NT_SUCCESS(DoSrvPrivateSetAutoWrapMode(wrapAtEOL));
}

// Routine Description:
// - Connects the PrivateShowCursor call directly into our Driver Message servicing call inside Conhost.exe
// PrivateShowCursor is an internal-only "API" call that the vt commands can execute,
Expand Down
1 change: 1 addition & 0 deletions src/host/outputStream.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ class ConhostInternalGetSet final : public Microsoft::Console::VirtualTerminal::
bool PrivateSetKeypadMode(const bool applicationMode) override;

bool PrivateSetScreenMode(const bool reverseMode) override;
bool PrivateSetAutoWrapMode(const bool wrapAtEOL) override;

bool PrivateShowCursor(const bool show) noexcept override;
bool PrivateAllowCursorBlinking(const bool enable) override;
Expand Down
45 changes: 45 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ class ScreenBufferTests

TEST_METHOD(SetScreenMode);
TEST_METHOD(SetOriginMode);
TEST_METHOD(SetAutoWrapMode);

TEST_METHOD(HardResetBuffer);

Expand Down Expand Up @@ -4600,6 +4601,50 @@ void ScreenBufferTests::SetOriginMode()
stateMachine.ProcessString(L"\x1B[?6l");
}

void ScreenBufferTests::SetAutoWrapMode()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
auto& si = gci.GetActiveOutputBuffer();
auto& stateMachine = si.GetStateMachine();
auto& cursor = si.GetTextBuffer().GetCursor();
const auto attributes = si.GetAttributes();
WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);

const auto view = Viewport::FromDimensions({ 0, 0 }, { 80, 25 });
si.SetViewport(view, true);

Log::Comment(L"By default, output should wrap onto the next line.");
// Output 6 characters, 3 spaces from the end of the line.
short startLine = 0;
cursor.SetPosition({ 80 - 3, startLine });
stateMachine.ProcessString(L"abcdef");
// Half of the the content should wrap onto the next line.
VERIFY_IS_TRUE(_ValidateLineContains({ 80 - 3, startLine }, L"abc", attributes));
VERIFY_IS_TRUE(_ValidateLineContains({ 0, startLine + 1 }, L"def", attributes));
VERIFY_ARE_EQUAL(COORD({ 3, startLine + 1 }), cursor.GetPosition());

Log::Comment(L"When DECAWM is reset, output is clamped to the line width.");
stateMachine.ProcessString(L"\x1b[?7l");
// Output 6 characters, 3 spaces from the end of the line.
startLine = 2;
cursor.SetPosition({ 80 - 3, startLine });
stateMachine.ProcessString(L"abcdef");
// Content should be clamped to the line width, overwriting the last char.
VERIFY_IS_TRUE(_ValidateLineContains({ 80 - 3, startLine }, L"abf", attributes));
VERIFY_ARE_EQUAL(COORD({ 79, startLine }), cursor.GetPosition());

Log::Comment(L"When DECAWM is set, output is wrapped again.");
stateMachine.ProcessString(L"\x1b[?7h");
// Output 6 characters, 3 spaces from the end of the line.
startLine = 4;
cursor.SetPosition({ 80 - 3, startLine });
stateMachine.ProcessString(L"abcdef");
// Half of the the content should wrap onto the next line.
VERIFY_IS_TRUE(_ValidateLineContains({ 80 - 3, startLine }, L"abc", attributes));
VERIFY_IS_TRUE(_ValidateLineContains({ 0, startLine + 1 }, L"def", attributes));
VERIFY_ARE_EQUAL(COORD({ 3, startLine + 1 }), cursor.GetPosition());
}

void ScreenBufferTests::HardResetBuffer()
{
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/DispatchTypes.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes
DECCOLM_SetNumberOfColumns = 3,
DECSCNM_ScreenMode = 5,
DECOM_OriginMode = 6,
DECAWM_AutoWrapMode = 7,
ATT610_StartCursorBlink = 12,
DECTCEM_TextCursorEnableMode = 25,
XTERM_EnableDECCOLMSupport = 40,
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch
virtual bool EnableCursorBlinking(const bool enable) = 0; // ATT610
virtual bool SetScreenMode(const bool reverseMode) = 0; //DECSCNM
virtual bool SetOriginMode(const bool relativeMode) = 0; // DECOM
virtual bool SetAutoWrapMode(const bool wrapAtEOL) = 0; // DECAWM
virtual bool SetTopBottomScrollingMargins(const size_t topMargin, const size_t bottomMargin) = 0; // DECSTBM
virtual bool WarningBell() = 0; // BEL
virtual bool CarriageReturn() = 0; // CR
Expand Down
22 changes: 21 additions & 1 deletion src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -940,6 +940,9 @@ bool AdaptDispatch::_PrivateModeParamsHelper(const DispatchTypes::PrivateModePar
// The cursor is also moved to the new home position when the origin mode is set or reset.
success = SetOriginMode(enable) && CursorPosition(1, 1);
break;
case DispatchTypes::PrivateModeParams::DECAWM_AutoWrapMode:
success = SetAutoWrapMode(enable);
break;
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
success = EnableCursorBlinking(enable);
break;
Expand Down Expand Up @@ -1108,6 +1111,19 @@ bool AdaptDispatch::SetOriginMode(const bool relativeMode) noexcept
return true;
}

// Routine Description:
// - DECAWM - Sets the Auto Wrap Mode.
// This controls whether the cursor moves to the beginning of the next row
// when it reaches the end of the current row.
// Arguments:
// - wrapAtEOL - set to true to wrap, false to overwrite the last character.
// Return Value:
// - True if handled successfully. False otherwise.
bool AdaptDispatch::SetAutoWrapMode(const bool wrapAtEOL)
{
return _pConApi->PrivateSetAutoWrapMode(wrapAtEOL);
}

// Routine Description:
// - DECSTBM - Set Scrolling Region
// This control function sets the top and bottom margins for the current page.
Expand Down Expand Up @@ -1391,7 +1407,7 @@ bool AdaptDispatch::DesignateCharset(const wchar_t wchCharset) noexcept
// X Text cursor enable DECTCEM Cursor enabled.
// Insert/replace IRM Replace mode.
// X Origin DECOM Absolute (cursor origin at upper-left of screen.)
// Autowrap DECAWM No autowrap.
// X Autowrap DECAWM Autowrap enabled (matches XTerm behavior).
// National replacement DECNRCM Multinational set.
// character set
// Keyboard action KAM Unlocked.
Expand Down Expand Up @@ -1422,6 +1438,10 @@ bool AdaptDispatch::SoftReset()
success = SetOriginMode(false); // Absolute cursor addressing.
}
if (success)
{
success = SetAutoWrapMode(true); // Wrap at end of line.
}
if (success)
{
success = SetCursorKeysMode(false); // Normal characters.
}
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ namespace Microsoft::Console::VirtualTerminal
bool EnableCursorBlinking(const bool enable) override; // ATT610
bool SetScreenMode(const bool reverseMode) override; //DECSCNM
bool SetOriginMode(const bool relativeMode) noexcept override; // DECOM
bool SetAutoWrapMode(const bool wrapAtEOL) override; // DECAWM
bool SetTopBottomScrollingMargins(const size_t topMargin,
const size_t bottomMargin) override; // DECSTBM
bool WarningBell() override; // BEL
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/conGetSet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ namespace Microsoft::Console::VirtualTerminal
virtual bool PrivateSetKeypadMode(const bool applicationMode) = 0;

virtual bool PrivateSetScreenMode(const bool reverseMode) = 0;
virtual bool PrivateSetAutoWrapMode(const bool wrapAtEOL) = 0;

virtual bool PrivateShowCursor(const bool show) = 0;
virtual bool PrivateAllowCursorBlinking(const bool enable) = 0;
Expand Down
1 change: 1 addition & 0 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons
bool EnableCursorBlinking(const bool /*enable*/) noexcept override { return false; } // ATT610
bool SetScreenMode(const bool /*reverseMode*/) noexcept override { return false; } //DECSCNM
bool SetOriginMode(const bool /*relativeMode*/) noexcept override { return false; }; // DECOM
bool SetAutoWrapMode(const bool /*wrapAtEOL*/) noexcept override { return false; }; // DECAWM
bool SetTopBottomScrollingMargins(const size_t /*topMargin*/, const size_t /*bottomMargin*/) noexcept override { return false; } // DECSTBM
bool WarningBell() noexcept override { return false; } // BEL
bool CarriageReturn() noexcept override { return false; } // CR
Expand Down
7 changes: 7 additions & 0 deletions src/terminal/adapter/ut_adapter/adapterTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ class TestGetSet final : public ConGetSet
return true;
}

bool PrivateSetAutoWrapMode(const bool /*wrapAtEOL*/) override
{
Log::Comment(L"PrivateSetAutoWrapMode MOCK called...");

return false;
}

bool PrivateShowCursor(const bool show) override
{
Log::Comment(L"PrivateShowCursor MOCK called...");
Expand Down
30 changes: 30 additions & 0 deletions src/terminal/parser/ut_parser/OutputEngineTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ class StatefulDispatch final : public TermDispatch
_cursorBlinking{ true },
_isScreenModeReversed{ false },
_isOriginModeRelative{ false },
_isAutoWrapEnabled{ true },
_warningBell{ false },
_carriageReturn{ false },
_lineFeed{ false },
Expand Down Expand Up @@ -865,6 +866,9 @@ class StatefulDispatch final : public TermDispatch
// The cursor is also moved to the new home position when the origin mode is set or reset.
fSuccess = SetOriginMode(fEnable) && CursorPosition(1, 1);
break;
case DispatchTypes::PrivateModeParams::DECAWM_AutoWrapMode:
fSuccess = SetAutoWrapMode(fEnable);
break;
case DispatchTypes::PrivateModeParams::ATT610_StartCursorBlink:
fSuccess = EnableCursorBlinking(fEnable);
break;
Expand Down Expand Up @@ -936,6 +940,12 @@ class StatefulDispatch final : public TermDispatch
return true;
}

bool SetAutoWrapMode(const bool wrapAtEOL) noexcept override
{
_isAutoWrapEnabled = wrapAtEOL;
return true;
}

bool WarningBell() noexcept override
{
_warningBell = true;
Expand Down Expand Up @@ -1011,6 +1021,7 @@ class StatefulDispatch final : public TermDispatch
bool _cursorBlinking;
bool _isScreenModeReversed;
bool _isOriginModeRelative;
bool _isAutoWrapEnabled;
bool _warningBell;
bool _carriageReturn;
bool _lineFeed;
Expand Down Expand Up @@ -1345,6 +1356,25 @@ class StateMachineExternalTest final
pDispatch->ClearState();
}

TEST_METHOD(TestAutoWrapMode)
{
auto dispatch = std::make_unique<StatefulDispatch>();
auto pDispatch = dispatch.get();
auto engine = std::make_unique<OutputStateMachineEngine>(std::move(dispatch));
StateMachine mach(std::move(engine));

mach.ProcessString(L"\x1b[?7l");
VERIFY_IS_FALSE(pDispatch->_isAutoWrapEnabled);

pDispatch->ClearState();
pDispatch->_isAutoWrapEnabled = false;

mach.ProcessString(L"\x1b[?7h");
VERIFY_IS_TRUE(pDispatch->_isAutoWrapEnabled);

pDispatch->ClearState();
}

TEST_METHOD(TestCursorBlinking)
{
auto dispatch = std::make_unique<StatefulDispatch>();
Expand Down