VT sequence support for EraseInLine, EraseInDisplay, DeleteCharacter and InsertCharacter (#2144)
* We now support EraseInLine, EraseInDisplay, DeleteCharacter and InsertCharacter
This commit is contained in:
parent
2d3e271a4f
commit
63df881f31
|
@ -558,22 +558,37 @@ bool TextBuffer::IncrementCircularBuffer()
|
|||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character on the final line of the text buffer.
|
||||
// - By default, we search the entire buffer to find the last non-space character
|
||||
//Arguments:
|
||||
// - <none>
|
||||
//Return Value:
|
||||
// - Coordinate position in screen coordinates (offset coordinates, not array index coordinates).
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter() const
|
||||
{
|
||||
COORD coordEndOfText;
|
||||
// Always search the whole buffer, by starting at the bottom.
|
||||
coordEndOfText.Y = GetSize().BottomInclusive();
|
||||
return GetLastNonSpaceCharacter(GetSize());
|
||||
}
|
||||
|
||||
//Routine Description:
|
||||
// - Retrieves the position of the last non-space character in the given viewport
|
||||
// - This is basically an optimized version of GetLastNonSpaceCharacter(), and can be called when
|
||||
// - we know the last character is within the given viewport (so we don't need to check the entire buffer)
|
||||
//Arguments:
|
||||
// - The viewport
|
||||
//Return value:
|
||||
// - Coordinate position (relative to the text buffer)
|
||||
COORD TextBuffer::GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const
|
||||
{
|
||||
COORD coordEndOfText = { 0 };
|
||||
// Search the given viewport by starting at the bottom.
|
||||
coordEndOfText.Y = viewport.BottomInclusive();
|
||||
|
||||
const ROW* pCurrRow = &GetRowByOffset(coordEndOfText.Y);
|
||||
// The X position of the end of the valid text is the Right draw boundary (which is one beyond the final valid character)
|
||||
coordEndOfText.X = static_cast<short>(pCurrRow->GetCharRow().MeasureRight()) - 1;
|
||||
|
||||
// If the X coordinate turns out to be -1, the row was empty, we need to search backwards for the real end of text.
|
||||
bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0); // this row is empty, and we're not at the top
|
||||
const auto viewportTop = viewport.Top();
|
||||
bool fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop); // this row is empty, and we're not at the top
|
||||
while (fDoBackUp)
|
||||
{
|
||||
coordEndOfText.Y--;
|
||||
|
@ -581,7 +596,7 @@ COORD TextBuffer::GetLastNonSpaceCharacter() const
|
|||
// We need to back up to the previous row if this line is empty, AND there are more rows
|
||||
|
||||
coordEndOfText.X = static_cast<short>(pCurrRow->GetCharRow().MeasureRight()) - 1;
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > 0);
|
||||
fDoBackUp = (coordEndOfText.X < 0 && coordEndOfText.Y > viewportTop);
|
||||
}
|
||||
|
||||
// don't allow negative results
|
||||
|
|
|
@ -105,6 +105,7 @@ public:
|
|||
bool IncrementCircularBuffer();
|
||||
|
||||
COORD GetLastNonSpaceCharacter() const;
|
||||
COORD GetLastNonSpaceCharacter(const Microsoft::Console::Types::Viewport viewport) const;
|
||||
|
||||
Cursor& GetCursor();
|
||||
const Cursor& GetCursor() const;
|
||||
|
|
|
@ -24,7 +24,11 @@ namespace Microsoft::Terminal::Core
|
|||
virtual bool SetCursorPosition(short x, short y) = 0;
|
||||
virtual COORD GetCursorPosition() = 0;
|
||||
|
||||
virtual bool DeleteCharacter(const unsigned int uiCount) = 0;
|
||||
virtual bool InsertCharacter(const unsigned int uiCount) = 0;
|
||||
virtual bool EraseCharacters(const unsigned int numChars) = 0;
|
||||
virtual bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) = 0;
|
||||
virtual bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) = 0;
|
||||
|
||||
virtual bool SetWindowTitle(std::wstring_view title) = 0;
|
||||
|
||||
|
|
|
@ -66,7 +66,11 @@ public:
|
|||
bool ReverseText(bool reversed) override;
|
||||
bool SetCursorPosition(short x, short y) override;
|
||||
COORD GetCursorPosition() override;
|
||||
bool DeleteCharacter(const unsigned int uiCount) override;
|
||||
bool InsertCharacter(const unsigned int uiCount) override;
|
||||
bool EraseCharacters(const unsigned int numChars) override;
|
||||
bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override;
|
||||
bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override;
|
||||
bool SetWindowTitle(std::wstring_view title) override;
|
||||
bool SetColorTableEntry(const size_t tableIndex, const COLORREF dwColor) override;
|
||||
bool SetCursorStyle(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::CursorStyle cursorStyle) override;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include "pch.h"
|
||||
#include "Terminal.hpp"
|
||||
#include "../src/inc/unicode.hpp"
|
||||
|
||||
using namespace Microsoft::Terminal::Core;
|
||||
using namespace Microsoft::Console::Types;
|
||||
|
@ -126,17 +127,242 @@ COORD Terminal::GetCursorPosition()
|
|||
return newPos;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - deletes uiCount characters starting from the cursor's current position
|
||||
// - it moves over the remaining text to 'replace' the deleted text
|
||||
// - for example, if the buffer looks like this ('|' is the cursor): [abc|def]
|
||||
// - calling DeleteCharacter(1) will change it to: [abc|ef],
|
||||
// - i.e. the 'd' gets deleted and the 'ef' gets shifted over 1 space and **retain their previous text attributes**
|
||||
// Arguments:
|
||||
// - uiCount, the number of characters to delete
|
||||
// Return value:
|
||||
// - true if succeeded, false otherwise
|
||||
bool Terminal::DeleteCharacter(const unsigned int uiCount)
|
||||
{
|
||||
SHORT dist;
|
||||
if (!SUCCEEDED(UIntToShort(uiCount, &dist)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
const auto copyToPos = cursorPos;
|
||||
const COORD copyFromPos{ cursorPos.X + dist, cursorPos.Y };
|
||||
auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X;
|
||||
SHORT width;
|
||||
if (!SUCCEEDED(UIntToShort(sourceWidth, &width)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a rectangle of the source
|
||||
auto source = Viewport::FromDimensions(copyFromPos, width, 1);
|
||||
|
||||
// Get a rectangle of the target
|
||||
const auto target = Viewport::FromDimensions(copyToPos, source.Dimensions());
|
||||
const auto walkDirection = Viewport::DetermineWalkDirection(source, target);
|
||||
|
||||
auto sourcePos = source.GetWalkOrigin(walkDirection);
|
||||
auto targetPos = target.GetWalkOrigin(walkDirection);
|
||||
|
||||
// Iterate over the source cell data and copy it over to the target
|
||||
do
|
||||
{
|
||||
const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos)));
|
||||
_buffer->Write(OutputCellIterator({ &data, 1 }), targetPos);
|
||||
} while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Inserts uiCount spaces starting from the cursor's current position, moving over the existing text
|
||||
// - for example, if the buffer looks like this ('|' is the cursor): [abc|def]
|
||||
// - calling InsertCharacter(1) will change it to: [abc| def],
|
||||
// - i.e. the 'def' gets shifted over 1 space and **retain their previous text attributes**
|
||||
// Arguments:
|
||||
// - uiCount, the number of spaces to insert
|
||||
// Return value:
|
||||
// - true if succeeded, false otherwise
|
||||
bool Terminal::InsertCharacter(const unsigned int uiCount)
|
||||
{
|
||||
// NOTE: the code below is _extremely_ similar to DeleteCharacter
|
||||
// We will want to use this same logic and implement a helper function instead
|
||||
// that does the 'move a region from here to there' operation
|
||||
// TODO: Github issue #2163
|
||||
SHORT dist;
|
||||
if (!SUCCEEDED(UIntToShort(uiCount, &dist)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
const auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
const auto copyFromPos = cursorPos;
|
||||
const COORD copyToPos{ cursorPos.X + dist, cursorPos.Y };
|
||||
auto sourceWidth = _mutableViewport.RightExclusive() - copyFromPos.X;
|
||||
SHORT width;
|
||||
if (!SUCCEEDED(UIntToShort(sourceWidth, &width)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get a rectangle of the source
|
||||
auto source = Viewport::FromDimensions(copyFromPos, width, 1);
|
||||
const auto sourceOrigin = source.Origin();
|
||||
|
||||
// Get a rectangle of the target
|
||||
const auto target = Viewport::FromDimensions(copyToPos, source.Dimensions());
|
||||
const auto walkDirection = Viewport::DetermineWalkDirection(source, target);
|
||||
|
||||
auto sourcePos = source.GetWalkOrigin(walkDirection);
|
||||
auto targetPos = target.GetWalkOrigin(walkDirection);
|
||||
|
||||
// Iterate over the source cell data and copy it over to the target
|
||||
do
|
||||
{
|
||||
const auto data = OutputCell(*(_buffer->GetCellDataAt(sourcePos)));
|
||||
_buffer->Write(OutputCellIterator({ &data, 1 }), targetPos);
|
||||
} while (source.WalkInBounds(sourcePos, walkDirection) && target.WalkInBounds(targetPos, walkDirection));
|
||||
auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), dist);
|
||||
_buffer->Write(eraseIter, cursorPos);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::EraseCharacters(const unsigned int numChars)
|
||||
{
|
||||
const auto absoluteCursorPos = _buffer->GetCursor().GetPosition();
|
||||
const auto viewport = _GetMutableViewport();
|
||||
const short distanceToRight = viewport.RightExclusive() - absoluteCursorPos.X;
|
||||
const short fillLimit = std::min(static_cast<short>(numChars), distanceToRight);
|
||||
auto eraseIter = OutputCellIterator(L' ', _buffer->GetCurrentAttributes(), fillLimit);
|
||||
auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), fillLimit);
|
||||
_buffer->Write(eraseIter, absoluteCursorPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - erases a line of text, either from
|
||||
// 1. beginning to the cursor's position
|
||||
// 2. cursor's position to end
|
||||
// 3. beginning to end
|
||||
// - depending on the erase type
|
||||
// Arguments:
|
||||
// - the erase type
|
||||
// Return value:
|
||||
// - true if succeeded, false otherwise
|
||||
bool Terminal::EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType)
|
||||
{
|
||||
const auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
const auto viewport = _GetMutableViewport();
|
||||
COORD startPos = { 0 };
|
||||
startPos.Y = cursorPos.Y;
|
||||
// nlength determines the number of spaces we need to write
|
||||
DWORD nlength = 0;
|
||||
|
||||
// Determine startPos.X and nlength by the eraseType
|
||||
switch (eraseType)
|
||||
{
|
||||
case DispatchTypes::EraseType::FromBeginning:
|
||||
nlength = cursorPos.X - viewport.Left() + 1;
|
||||
break;
|
||||
case DispatchTypes::EraseType::ToEnd:
|
||||
startPos.X = cursorPos.X;
|
||||
nlength = viewport.RightInclusive() - startPos.X;
|
||||
break;
|
||||
case DispatchTypes::EraseType::All:
|
||||
startPos.X = viewport.Left();
|
||||
nlength = viewport.RightInclusive() - startPos.X;
|
||||
break;
|
||||
case DispatchTypes::EraseType::Scrollback:
|
||||
return false;
|
||||
}
|
||||
|
||||
auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), nlength);
|
||||
_buffer->Write(eraseIter, startPos);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Method description:
|
||||
// - erases text in the buffer in two ways depending on erase type
|
||||
// 1. 'erases' all text visible to the user (i.e. the text in the viewport)
|
||||
// 2. erases all the text in the scrollback
|
||||
// Arguments:
|
||||
// - the erase type
|
||||
// Return Value:
|
||||
// - true if succeeded, false otherwise
|
||||
bool Terminal::EraseInDisplay(const DispatchTypes::EraseType eraseType)
|
||||
{
|
||||
// Store the relative cursor position so we can restore it later after we move the viewport
|
||||
const auto cursorPos = _buffer->GetCursor().GetPosition();
|
||||
auto relativeCursor = cursorPos;
|
||||
_mutableViewport.ConvertToOrigin(&relativeCursor);
|
||||
|
||||
// Initialize the new location of the viewport
|
||||
// the top and bottom parameters are determined by the eraseType
|
||||
SMALL_RECT newWin;
|
||||
newWin.Left = _mutableViewport.Left();
|
||||
newWin.Right = _mutableViewport.RightExclusive();
|
||||
|
||||
if (eraseType == DispatchTypes::EraseType::All)
|
||||
{
|
||||
// In this case, we simply move the viewport down, effectively pushing whatever text was on the screen into the scrollback
|
||||
// and thus 'erasing' the text visible to the user
|
||||
const auto coordLastChar = _buffer->GetLastNonSpaceCharacter(_mutableViewport);
|
||||
if (coordLastChar.X == 0 && coordLastChar.Y == 0)
|
||||
{
|
||||
// Nothing to clear, just return
|
||||
return true;
|
||||
}
|
||||
|
||||
short sNewTop = coordLastChar.Y + 1;
|
||||
|
||||
// Increment the circular buffer only if the new location of the viewport would be 'below' the buffer
|
||||
const short delta = (sNewTop + _mutableViewport.Height()) - (_buffer->GetSize().Height());
|
||||
for (auto i = 0; i < delta; i++)
|
||||
{
|
||||
_buffer->IncrementCircularBuffer();
|
||||
sNewTop--;
|
||||
}
|
||||
|
||||
newWin.Top = sNewTop;
|
||||
newWin.Bottom = sNewTop + _mutableViewport.Height();
|
||||
}
|
||||
else if (eraseType == DispatchTypes::EraseType::Scrollback)
|
||||
{
|
||||
// We only want to erase the scrollback, and leave everything else on the screen as it is
|
||||
// so we grab the text in the viewport and rotate it up to the top of the buffer
|
||||
COORD scrollFromPos{ 0, 0 };
|
||||
_mutableViewport.ConvertFromOrigin(&scrollFromPos);
|
||||
_buffer->ScrollRows(scrollFromPos.Y, _mutableViewport.Height(), -scrollFromPos.Y);
|
||||
|
||||
// Since we only did a rotation, the text that was in the scrollback is now _below_ where we are going to move the viewport
|
||||
// and we have to make sure we erase that text
|
||||
auto eraseStart = _mutableViewport.Height();
|
||||
auto eraseEnd = _buffer->GetLastNonSpaceCharacter(_mutableViewport).Y;
|
||||
auto eraseIter = OutputCellIterator(UNICODE_SPACE, _buffer->GetCurrentAttributes(), _mutableViewport.RightInclusive() * (eraseEnd - eraseStart + 1));
|
||||
for (SHORT i = eraseStart; i <= eraseEnd; i++)
|
||||
{
|
||||
COORD erasePos{ 0, i };
|
||||
_buffer->Write(eraseIter, erasePos);
|
||||
}
|
||||
|
||||
// Reset the scroll offset now because there's nothing for the user to 'scroll' to
|
||||
_scrollOffset = 0;
|
||||
|
||||
newWin.Top = 0;
|
||||
newWin.Bottom = _mutableViewport.Height();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Move the viewport, adjust the scoll bar if needed, and restore the old cursor position
|
||||
_mutableViewport = Viewport::FromExclusive(newWin);
|
||||
Terminal::_NotifyScrollEvent();
|
||||
SetCursorPosition(relativeCursor.X, relativeCursor.Y);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Terminal::SetWindowTitle(std::wstring_view title)
|
||||
{
|
||||
_title = title;
|
||||
|
|
|
@ -47,6 +47,20 @@ bool TerminalDispatch::CursorForward(const unsigned int uiDistance)
|
|||
return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::CursorBackward(const unsigned int uiDistance)
|
||||
{
|
||||
const auto cursorPos = _terminalApi.GetCursorPosition();
|
||||
const COORD newCursorPos{ cursorPos.X - gsl::narrow<short>(uiDistance), cursorPos.Y };
|
||||
return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::CursorUp(const unsigned int uiDistance)
|
||||
{
|
||||
const auto cursorPos = _terminalApi.GetCursorPosition();
|
||||
const COORD newCursorPos{ cursorPos.X, cursorPos.Y + gsl::narrow<short>(uiDistance) };
|
||||
return _terminalApi.SetCursorPosition(newCursorPos.X, newCursorPos.Y);
|
||||
}
|
||||
|
||||
bool TerminalDispatch::EraseCharacters(const unsigned int uiNumChars)
|
||||
{
|
||||
return _terminalApi.EraseCharacters(uiNumChars);
|
||||
|
@ -98,9 +112,45 @@ bool TerminalDispatch::SetDefaultBackground(const DWORD dwColor)
|
|||
}
|
||||
|
||||
// Method Description:
|
||||
// - For now, this is a hacky backspace
|
||||
// - TODO: GitHub #1883
|
||||
bool TerminalDispatch::EraseInLine(const DispatchTypes::EraseType)
|
||||
// - Erases characters in the buffer depending on the erase type
|
||||
// Arguments:
|
||||
// - eraseType: the erase type (from beginning, to end, or all)
|
||||
// Return Value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::EraseInLine(const DispatchTypes::EraseType eraseType)
|
||||
{
|
||||
return _terminalApi.EraseCharacters(1);
|
||||
return _terminalApi.EraseInLine(eraseType);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Deletes uiCount number of characters starting from where the cursor is currently
|
||||
// Arguments:
|
||||
// - uiCount, the number of characters to delete
|
||||
// Return Value:
|
||||
// True if handled successfully. False otherwise.
|
||||
bool TerminalDispatch::DeleteCharacter(const unsigned int uiCount)
|
||||
{
|
||||
return _terminalApi.DeleteCharacter(uiCount);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Adds uiCount number of spaces starting from where the cursor is currently
|
||||
// Arguments:
|
||||
// - uiCount, the number of spaces to add
|
||||
// Return Value:
|
||||
// True if handled successfully, false otherwise
|
||||
bool TerminalDispatch::InsertCharacter(const unsigned int uiCount)
|
||||
{
|
||||
return _terminalApi.InsertCharacter(uiCount);
|
||||
}
|
||||
|
||||
// Method Description:
|
||||
// - Moves the viewport and erases text from the buffer depending on the eraseType
|
||||
// Arguments:
|
||||
// - eraseType: the desired erase type
|
||||
// Return Value:
|
||||
// True if handled successfully. False otherwise
|
||||
bool TerminalDispatch::EraseInDisplay(const DispatchTypes::EraseType eraseType)
|
||||
{
|
||||
return _terminalApi.EraseInDisplay(eraseType);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ public:
|
|||
const unsigned int uiColumn) override; // CUP
|
||||
|
||||
bool CursorForward(const unsigned int uiDistance) override;
|
||||
bool CursorBackward(const unsigned int uiDistance) override;
|
||||
bool CursorUp(const unsigned int uiDistance) override;
|
||||
|
||||
bool EraseCharacters(const unsigned int uiNumChars) override;
|
||||
bool SetWindowTitle(std::wstring_view title) override;
|
||||
|
@ -29,7 +31,10 @@ public:
|
|||
|
||||
bool SetDefaultForeground(const DWORD dwColor) override;
|
||||
bool SetDefaultBackground(const DWORD dwColor) override;
|
||||
bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType /* eraseType*/) override; // ED
|
||||
bool EraseInLine(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override; // ED
|
||||
bool DeleteCharacter(const unsigned int uiCount) override;
|
||||
bool InsertCharacter(const unsigned int uiCount) override;
|
||||
bool EraseInDisplay(const ::Microsoft::Console::VirtualTerminal::DispatchTypes::EraseType eraseType) override;
|
||||
|
||||
private:
|
||||
::Microsoft::Terminal::Core::ITerminalApi& _terminalApi;
|
||||
|
|
Loading…
Reference in a new issue