diff --git a/src/host/ut_host/ScreenBufferTests.cpp b/src/host/ut_host/ScreenBufferTests.cpp index 786fe399c98..706f4e3540a 100644 --- a/src/host/ut_host/ScreenBufferTests.cpp +++ b/src/host/ut_host/ScreenBufferTests.cpp @@ -182,6 +182,7 @@ class ScreenBufferTests TEST_METHOD(DontResetColorsAboveVirtualBottom); TEST_METHOD(ScrollOperations); + TEST_METHOD(InsertReplaceMode); TEST_METHOD(InsertChars); TEST_METHOD(DeleteChars); TEST_METHOD(ScrollingWideCharsHorizontally); @@ -3782,6 +3783,82 @@ void ScreenBufferTests::ScrollOperations() VERIFY_IS_TRUE(_ValidateLinesContain(revealedStart, revealedEnd, L' ', expectedFillAttr)); } +void ScreenBufferTests::InsertReplaceMode() +{ + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& si = gci.GetActiveOutputBuffer().GetActiveBuffer(); + auto& stateMachine = si.GetStateMachine(); + WI_SetFlag(si.OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING); + + const auto bufferHeight = si.GetBufferSize().Height(); + const auto viewport = si.GetViewport(); + const auto targetRow = viewport.Top() + 5; + const auto targetCol = til::CoordType{ 10 }; + + // Fill the entire buffer with Zs. Blue on Green. + const auto bufferChar = L'Z'; + const auto bufferAttr = TextAttribute{ FOREGROUND_BLUE | BACKGROUND_GREEN }; + _FillLines(0, bufferHeight, bufferChar, bufferAttr); + + // Fill the target row with asterisks and a range of letters at the start. Red on Blue. + const auto initialChars = L"ABCDEFGHIJKLMNOPQRST"; + const auto initialAttr = TextAttribute{ FOREGROUND_RED | BACKGROUND_BLUE }; + _FillLine(targetRow, L'*', initialAttr); + _FillLine(targetRow, initialChars, initialAttr); + + // Set the attributes that will be used for the new content. + auto newAttr = TextAttribute{ RGB(12, 34, 56), RGB(78, 90, 12) }; + newAttr.SetCrossedOut(true); + newAttr.SetReverseVideo(true); + newAttr.SetUnderlined(true); + si.SetAttributes(newAttr); + + Log::Comment(L"Write additional content into a line of text with IRM mode enabled."); + + // Set the cursor position partway through the target row. + VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }, true)); + // Enable Insert/Replace mode. + stateMachine.ProcessString(L"\033[4h"); + // Write out some new content. + const auto newChars = L"12345"; + stateMachine.ProcessString(newChars); + + VERIFY_IS_TRUE(_ValidateLineContains({ 0, targetRow }, L"ABCDEFGHIJ", initialAttr), + L"First half of the line should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol, targetRow }, newChars, newAttr), + L"New content should be inserted at the cursor position with active attributes."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol + 5, targetRow }, L"KLMNOPQRST", initialAttr), + L"Second half of the line should have moved 5 columns across."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol + 25, targetRow }, L'*', initialAttr), + L"With the remainder of the line filled with asterisks."); + VERIFY_IS_TRUE(_ValidateLineContains(targetRow + 1, bufferChar, bufferAttr), + L"The following line should be unaffected."); + + // Fill the target row with the initial content again. + _FillLine(targetRow, L'*', initialAttr); + _FillLine(targetRow, initialChars, initialAttr); + + Log::Comment(L"Write additional content into a line of text with IRM mode disabled."); + + // Set the cursor position partway through the target row. + VERIFY_SUCCEEDED(si.SetCursorPosition({ targetCol, targetRow }, true)); + // Disable Insert/Replace mode. + stateMachine.ProcessString(L"\033[4l"); + // Write out some new content. + stateMachine.ProcessString(newChars); + + VERIFY_IS_TRUE(_ValidateLineContains({ 0, targetRow }, L"ABCDEFGHIJ", initialAttr), + L"First half of the line should remain unchanged."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol, targetRow }, newChars, newAttr), + L"New content should be added at the cursor position with active attributes."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol + 5, targetRow }, L"PQRST", initialAttr), + L"Second half of the line should have been partially overwritten."); + VERIFY_IS_TRUE(_ValidateLineContains({ targetCol + 25, targetRow }, L'*', initialAttr), + L"With the remainder of the line filled with asterisks."); + VERIFY_IS_TRUE(_ValidateLineContains(targetRow + 1, bufferChar, bufferAttr), + L"The following line should be unaffected."); +} + void ScreenBufferTests::InsertChars() { BEGIN_TEST_METHOD_PROPERTIES() diff --git a/src/terminal/adapter/DispatchTypes.hpp b/src/terminal/adapter/DispatchTypes.hpp index e621398b859..b20adf4bfc4 100644 --- a/src/terminal/adapter/DispatchTypes.hpp +++ b/src/terminal/adapter/DispatchTypes.hpp @@ -395,6 +395,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes enum ModeParams : VTInt { + IRM_InsertReplaceMode = ANSIStandardMode(4), DECCKM_CursorKeysMode = DECPrivateMode(1), DECANM_AnsiMode = DECPrivateMode(2), DECCOLM_SetNumberOfColumns = DECPrivateMode(3), diff --git a/src/terminal/adapter/ITermDispatch.hpp b/src/terminal/adapter/ITermDispatch.hpp index 3129b94ca49..4898a3cd942 100644 --- a/src/terminal/adapter/ITermDispatch.hpp +++ b/src/terminal/adapter/ITermDispatch.hpp @@ -89,8 +89,8 @@ class Microsoft::Console::VirtualTerminal::ITermDispatch virtual bool PushGraphicsRendition(const VTParameters options) = 0; // XTPUSHSGR virtual bool PopGraphicsRendition() = 0; // XTPOPSGR - virtual bool SetMode(const DispatchTypes::ModeParams param) = 0; // DECSET - virtual bool ResetMode(const DispatchTypes::ModeParams param) = 0; // DECRST + virtual bool SetMode(const DispatchTypes::ModeParams param) = 0; // SM, DECSET + virtual bool ResetMode(const DispatchTypes::ModeParams param) = 0; // RM, DECRST virtual bool RequestMode(const DispatchTypes::ModeParams param) = 0; // DECRQM virtual bool DeviceStatusReport(const DispatchTypes::StatusType statusType, const VTParameter id) = 0; // DSR diff --git a/src/terminal/adapter/adaptDispatch.cpp b/src/terminal/adapter/adaptDispatch.cpp index 40edfe1cf96..3990fbdccaa 100644 --- a/src/terminal/adapter/adaptDispatch.cpp +++ b/src/terminal/adapter/adaptDispatch.cpp @@ -102,6 +102,20 @@ void AdaptDispatch::_WriteToBuffer(const std::wstring_view string) } const OutputCellIterator it(std::wstring_view{ stringPosition, string.cend() }, attributes); + if (_modes.test(Mode::InsertReplace)) + { + // If insert-replace mode is enabled, we first measure how many cells + // the string will occupy, and scroll the target area right by that + // amount to make space for the incoming text. + auto measureIt = it; + while (measureIt && measureIt.GetCellDistance(it) < lineWidth) + { + measureIt++; + } + const auto row = cursorPosition.y; + const auto cellCount = measureIt.GetCellDistance(it); + _ScrollRectHorizontally(textBuffer, { cursorPosition.x, row, lineWidth, row + 1 }, cellCount); + } const auto itEnd = textBuffer.WriteLine(it, cursorPosition, wrapAtEOL, lineWidth - 1); if (itEnd.GetInputDistance(it) == 0) @@ -1555,7 +1569,7 @@ bool AdaptDispatch::_PassThroughInputModes() } // Routine Description: -// - Support routine for routing private mode parameters to be set/reset as flags +// - Support routine for routing mode parameters to be set/reset as flags // Arguments: // - param - mode parameter to set/reset // - enable - True for set, false for unset. @@ -1565,6 +1579,9 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con { switch (param) { + case DispatchTypes::ModeParams::IRM_InsertReplaceMode: + _modes.set(Mode::InsertReplace, enable); + return true; case DispatchTypes::ModeParams::DECCKM_CursorKeysMode: _terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, enable); return !_PassThroughInputModes(); @@ -1647,7 +1664,7 @@ bool AdaptDispatch::_ModeParamsHelper(const DispatchTypes::ModeParams param, con } // Routine Description: -// - DECSET - Enables the given DEC private mode params. +// - SM/DECSET - Enables the given mode parameter (both ANSI and private). // Arguments: // - param - mode parameter to set // Return Value: @@ -1658,7 +1675,7 @@ bool AdaptDispatch::SetMode(const DispatchTypes::ModeParams param) } // Routine Description: -// - DECRST - Disables the given DEC private mode params. +// - RM/DECRST - Disables the given mode parameter (both ANSI and private). // Arguments: // - param - mode parameter to reset // Return Value: @@ -1681,6 +1698,9 @@ bool AdaptDispatch::RequestMode(const DispatchTypes::ModeParams param) switch (param) { + case DispatchTypes::ModeParams::IRM_InsertReplaceMode: + enabled = _modes.test(Mode::InsertReplace); + break; case DispatchTypes::ModeParams::DECCKM_CursorKeysMode: enabled = _terminalInput.GetInputMode(TerminalInput::Mode::CursorKey); break; @@ -2322,7 +2342,7 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled) // we actually perform. As the appropriate functionality is added to our ANSI support, // we should update this. // X Text cursor enable DECTCEM Cursor enabled. -// Insert/replace IRM Replace mode. +// X Insert/replace IRM Replace mode. // X Origin DECOM Absolute (cursor origin at upper-left of screen.) // X Autowrap DECAWM Autowrap enabled (matches XTerm behavior). // National replacement DECNRCM Multinational set. @@ -2350,7 +2370,7 @@ bool AdaptDispatch::AcceptC1Controls(const bool enabled) bool AdaptDispatch::SoftReset() { _api.GetTextBuffer().GetCursor().SetIsVisible(true); // Cursor enabled. - _modes.reset(Mode::Origin); // Absolute cursor addressing. + _modes.reset(Mode::InsertReplace, Mode::Origin); // Replace mode; Absolute cursor addressing. _api.SetAutoWrapMode(true); // Wrap at end of line. _terminalInput.SetInputMode(TerminalInput::Mode::CursorKey, false); // Normal characters. _terminalInput.SetInputMode(TerminalInput::Mode::Keypad, false); // Numeric characters. diff --git a/src/terminal/adapter/adaptDispatch.hpp b/src/terminal/adapter/adaptDispatch.hpp index 76dbfc05f5b..1a283a1c987 100644 --- a/src/terminal/adapter/adaptDispatch.hpp +++ b/src/terminal/adapter/adaptDispatch.hpp @@ -82,8 +82,8 @@ namespace Microsoft::Console::VirtualTerminal bool ScrollDown(const VTInt distance) override; // SD bool InsertLine(const VTInt distance) override; // IL bool DeleteLine(const VTInt distance) override; // DL - bool SetMode(const DispatchTypes::ModeParams param) override; // DECSET - bool ResetMode(const DispatchTypes::ModeParams param) override; // DECRST + bool SetMode(const DispatchTypes::ModeParams param) override; // SM, DECSET + bool ResetMode(const DispatchTypes::ModeParams param) override; // RM, DECRST bool RequestMode(const DispatchTypes::ModeParams param) override; // DECRQM bool SetKeypadMode(const bool applicationMode) override; // DECKPAM, DECKPNM bool SetAnsiMode(const bool ansiMode) override; // DECANM @@ -155,6 +155,7 @@ namespace Microsoft::Console::VirtualTerminal private: enum class Mode { + InsertReplace, Origin, Column, AllowDECCOLM, diff --git a/src/terminal/adapter/termDispatch.hpp b/src/terminal/adapter/termDispatch.hpp index 56ab5d1578d..0a3b7a688a7 100644 --- a/src/terminal/adapter/termDispatch.hpp +++ b/src/terminal/adapter/termDispatch.hpp @@ -82,8 +82,8 @@ class Microsoft::Console::VirtualTerminal::TermDispatch : public Microsoft::Cons bool PushGraphicsRendition(const VTParameters /*options*/) override { return false; } // XTPUSHSGR bool PopGraphicsRendition() override { return false; } // XTPOPSGR - bool SetMode(const DispatchTypes::ModeParams /*param*/) override { return false; } // DECSET - bool ResetMode(const DispatchTypes::ModeParams /*param*/) override { return false; } // DECRST + bool SetMode(const DispatchTypes::ModeParams /*param*/) override { return false; } // SM, DECSET + bool ResetMode(const DispatchTypes::ModeParams /*param*/) override { return false; } // RM, DECRST bool RequestMode(const DispatchTypes::ModeParams /*param*/) override { return false; } // DECRQM bool DeviceStatusReport(const DispatchTypes::StatusType /*statusType*/, const VTParameter /*id*/) override { return false; } // DSR diff --git a/src/terminal/parser/OutputStateMachineEngine.cpp b/src/terminal/parser/OutputStateMachineEngine.cpp index 48625cd5af7..509a16433ad 100644 --- a/src/terminal/parser/OutputStateMachineEngine.cpp +++ b/src/terminal/parser/OutputStateMachineEngine.cpp @@ -512,6 +512,12 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete }); TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSEL); break; + case CsiActionCodes::SM_SetMode: + success = parameters.for_each([&](const auto mode) { + return _dispatch->SetMode(DispatchTypes::ANSIStandardMode(mode)); + }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::SM); + break; case CsiActionCodes::DECSET_PrivateModeSet: success = parameters.for_each([&](const auto mode) { return _dispatch->SetMode(DispatchTypes::DECPrivateMode(mode)); @@ -519,6 +525,12 @@ bool OutputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParamete //TODO: MSFT:6367459 Add specific logging for each of the DECSET/DECRST codes TermTelemetry::Instance().Log(TermTelemetry::Codes::DECSET); break; + case CsiActionCodes::RM_ResetMode: + success = parameters.for_each([&](const auto mode) { + return _dispatch->ResetMode(DispatchTypes::ANSIStandardMode(mode)); + }); + TermTelemetry::Instance().Log(TermTelemetry::Codes::RM); + break; case CsiActionCodes::DECRST_PrivateModeReset: success = parameters.for_each([&](const auto mode) { return _dispatch->ResetMode(DispatchTypes::DECPrivateMode(mode)); diff --git a/src/terminal/parser/OutputStateMachineEngine.hpp b/src/terminal/parser/OutputStateMachineEngine.hpp index af48cf37922..83088e9a160 100644 --- a/src/terminal/parser/OutputStateMachineEngine.hpp +++ b/src/terminal/parser/OutputStateMachineEngine.hpp @@ -130,7 +130,9 @@ namespace Microsoft::Console::VirtualTerminal VPR_VerticalPositionRelative = VTID("e"), HVP_HorizontalVerticalPosition = VTID("f"), TBC_TabClear = VTID("g"), + SM_SetMode = VTID("h"), DECSET_PrivateModeSet = VTID("?h"), + RM_ResetMode = VTID("l"), DECRST_PrivateModeReset = VTID("?l"), SGR_SetGraphicsRendition = VTID("m"), DSR_DeviceStatusReport = VTID("n"), diff --git a/src/terminal/parser/telemetry.cpp b/src/terminal/parser/telemetry.cpp index 83318b1719d..56775c27999 100644 --- a/src/terminal/parser/telemetry.cpp +++ b/src/terminal/parser/telemetry.cpp @@ -221,7 +221,9 @@ void TermTelemetry::WriteFinalTraceLog() const TraceLoggingUInt32(_uiTimesUsed[SGR], "SGR"), TraceLoggingUInt32(_uiTimesUsed[DECSC], "DECSC"), TraceLoggingUInt32(_uiTimesUsed[DECRC], "DECRC"), + TraceLoggingUInt32(_uiTimesUsed[SM], "SM"), TraceLoggingUInt32(_uiTimesUsed[DECSET], "DECSET"), + TraceLoggingUInt32(_uiTimesUsed[RM], "RM"), TraceLoggingUInt32(_uiTimesUsed[DECRST], "DECRST"), TraceLoggingUInt32(_uiTimesUsed[DECKPAM], "DECKPAM"), TraceLoggingUInt32(_uiTimesUsed[DECKPNM], "DECKPNM"), diff --git a/src/terminal/parser/telemetry.hpp b/src/terminal/parser/telemetry.hpp index 01eee817e0a..6de52d060e2 100644 --- a/src/terminal/parser/telemetry.hpp +++ b/src/terminal/parser/telemetry.hpp @@ -48,7 +48,9 @@ namespace Microsoft::Console::VirtualTerminal SGR, DECSC, DECRC, + SM, DECSET, + RM, DECRST, DECKPAM, DECKPNM,