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 IRM (Insert Replace Mode) #14700

Merged
5 commits merged into from
Jan 19, 2023
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
77 changes: 77 additions & 0 deletions src/host/ut_host/ScreenBufferTests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ class ScreenBufferTests
TEST_METHOD(DontResetColorsAboveVirtualBottom);

TEST_METHOD(ScrollOperations);
TEST_METHOD(InsertReplaceMode);
TEST_METHOD(InsertChars);
TEST_METHOD(DeleteChars);
TEST_METHOD(ScrollingWideCharsHorizontally);
Expand Down Expand Up @@ -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()
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 @@ -395,6 +395,7 @@ namespace Microsoft::Console::VirtualTerminal::DispatchTypes

enum ModeParams : VTInt
{
IRM_InsertReplaceMode = ANSIStandardMode(4),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I love that my weird "tagged enum" thing didn't turn into YAGNI. I added it because we'd eventually do IRM, and need SM/RM. 😄

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it's definitely been useful. I used it for differentiating the ANSI and private DSR operations as well.

DECCKM_CursorKeysMode = DECPrivateMode(1),
DECANM_AnsiMode = DECPrivateMode(2),
DECCOLM_SetNumberOfColumns = DECPrivateMode(3),
Expand Down
4 changes: 2 additions & 2 deletions src/terminal/adapter/ITermDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 25 additions & 5 deletions src/terminal/adapter/adaptDispatch.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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.
Expand All @@ -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();
Expand Down Expand Up @@ -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:
Expand All @@ -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:
Expand All @@ -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;
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
5 changes: 3 additions & 2 deletions src/terminal/adapter/adaptDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -155,6 +155,7 @@ namespace Microsoft::Console::VirtualTerminal
private:
enum class Mode
{
InsertReplace,
Origin,
Column,
AllowDECCOLM,
Expand Down
4 changes: 2 additions & 2 deletions src/terminal/adapter/termDispatch.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 12 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -512,13 +512,25 @@ 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));
});
//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));
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/parser/OutputStateMachineEngine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/parser/telemetry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down
2 changes: 2 additions & 0 deletions src/terminal/parser/telemetry.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,9 @@ namespace Microsoft::Console::VirtualTerminal
SGR,
DECSC,
DECRC,
SM,
DECSET,
RM,
DECRST,
DECKPAM,
DECKPNM,
Expand Down