Compare commits

...

15 Commits

Author SHA1 Message Date
Mike Griese
c21f74029d I think this is all I need to support wrapped lines in the Terminal 2020-01-27 17:15:38 -06:00
Mike Griese
3719bad9ab Merge branch 'pull/4289' into dev/migrie/f/just-wrapping 2020-01-27 16:47:38 -06:00
Mike Griese
bbb08b4478 Merge branch 'dev/migrie/f/conpty-wrapping-003' into dev/migrie/f/just-wrapping 2020-01-27 16:46:52 -06:00
Mike Griese
7f341a25a5 Merge branch 'master' into dev/migrie/f/conpty-wrapping-003
# Conflicts:
#	src/buffer/out/textBuffer.cpp
#	src/buffer/out/textBuffer.hpp
#	src/host/screenInfo.cpp
2020-01-27 16:46:29 -06:00
James Holderness
550a685879 Make the InVTMode method private, since it's only used internally by the SCREEN_INFORMATION class now. 2020-01-23 18:51:36 +00:00
James Holderness
94f4db32cd Remove control chars from Terminal::_WriteBuffer that are now handled in the dispatcher. 2020-01-18 02:12:06 +00:00
James Holderness
36c41f06a4 Simplify the delayed EOL implementation, removing special case handling of VT control characters. 2020-01-18 02:12:06 +00:00
James Holderness
abf3ba8207 Don't treat BS differently in VT mode in the WriteCharsLegacy function. 2020-01-18 02:12:06 +00:00
James Holderness
77eb3ba037 Remove WC_NONDESTRUCTIVE_TAB flag, since that functionality is no longer required. 2020-01-18 02:12:06 +00:00
James Holderness
8f9f3a9e7c Don't treat a TAB differently in VT mode in the WriteCharsLegacy function. 2020-01-18 02:12:06 +00:00
Mike Griese
416be4656f This is some cleanup, almost ready for PR but I need to write tests which are blocked on #4213 2020-01-14 16:46:33 -06:00
Mike Griese
aae6ce60a4 This works, fixing the ECH at end of wrapped line bug
The trick was that when a line that was exactly filled was followed by an
  empty line, we'd ECH when the cursor is virtually on the last char of the
  wrapped line. That was wrong. For a scenario like:

  |ABCDEF|
  |      |

  We'd incorrectly render that as ["ABCDEF", "^[[K", "\r\n"]. That ECH in the
  middle there would erase the 'F', because the cursor was virtually still on
  the 'F' (because it had deferred wrapping to the next char).

  So, when we're about to ECH following a wrapped line, reset the _wrappedRow
  first, to make sure we correctly \r\n first.
2020-01-14 16:07:22 -06:00
Mike Griese
1a2654d291 Try to wrap the line properly with conpty
This confusingly doesn't always work
2020-01-13 17:07:43 -06:00
Mike Griese
9aec69467c add a doc comment because I'm not a barbarian 2020-01-13 11:34:50 -06:00
Mike Griese
b5c8c854cc let's first move reflowing to the text buffer 2020-01-13 11:23:42 -06:00
12 changed files with 137 additions and 148 deletions

View File

@@ -431,27 +431,6 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
{
proposedCursorPosition.Y++;
}
else if (wch == UNICODE_CARRIAGERETURN)
{
proposedCursorPosition.X = 0;
}
else if (wch == UNICODE_BACKSPACE)
{
if (cursorPosBefore.X == 0)
{
proposedCursorPosition.X = bufferSize.Width() - 1;
proposedCursorPosition.Y--;
}
else
{
proposedCursorPosition.X--;
}
}
else if (wch == UNICODE_BEL)
{
// TODO: GitHub #1883
// For now its empty just so we don't try to write the BEL character
}
else
{
// TODO: MSFT 21006766
@@ -483,6 +462,13 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView)
// With well behaving shells during normal operation this safeguard should normally not be encountered.
proposedCursorPosition.X = 0;
proposedCursorPosition.Y++;
// Try the character again.
// TODO/DANGER: Does this actually work? The above comment seems
// to mention the i-=1, but there was no such line here. This is
// _scary_. Perhaps this was never hit before we had conpty
// wrapping?
i--;
}
}

View File

@@ -86,7 +86,7 @@ try
Execute(L'\n');
return true;
case DispatchTypes::LineFeedType::WithReturn:
Execute(L'\r');
CarriageReturn();
Execute(L'\n');
return true;
default:

View File

@@ -330,59 +330,12 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
// Only act on a delayed EOL if we didn't move the cursor to a different position from where the EOL was marked.
if (coordDelayedAt.X == CursorPosition.X && coordDelayedAt.Y == CursorPosition.Y)
{
bool fDoEolWrap = false;
CursorPosition.X = 0;
CursorPosition.Y++;
if (WI_IsFlagSet(dwFlags, WC_DELAY_EOL_WRAP))
{
// Correct if it's a printable character and whoever called us still understands/wants delayed EOL wrap.
if (*lpString >= UNICODE_SPACE)
{
fDoEolWrap = true;
}
else if (*lpString == UNICODE_BACKSPACE)
{
// if we have an active wrap and a backspace comes in, process it by moving the cursor
// back one cell position unless it's already at the start of a row.
*pcb += sizeof(WCHAR);
lpString++;
pwchRealUnicode++;
if (CursorPosition.X != 0)
{
--CursorPosition.X;
Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);
CursorPosition = cursor.GetPosition();
}
continue;
}
}
else
{
// Uh oh, we've hit a consumer that doesn't know about delayed end of lines. To rectify this, just quickly jump
// forward to the next line as if we had done it earlier, then let everything else play out normally.
fDoEolWrap = true;
}
Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);
if (fDoEolWrap)
{
CursorPosition.X = 0;
CursorPosition.Y++;
Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY);
CursorPosition = cursor.GetPosition();
}
}
}
if (screenInfo.InVTMode())
{
// if we're at the beginning of a row and we get a backspace and told to limit backspacing, skip it
if (*lpString == UNICODE_BACKSPACE && CursorPosition.X == 0 && WI_IsFlagSet(dwFlags, WC_LIMIT_BACKSPACE))
{
*pcb += sizeof(wchar_t);
++lpString;
++pwchRealUnicode;
continue;
CursorPosition = cursor.GetPosition();
}
}
@@ -449,28 +402,23 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
goto EndWhile;
break;
case UNICODE_TAB:
if (screenInfo.InVTMode())
{
const ULONG TabSize = NUMBER_OF_SPACES_IN_TAB(XPosition);
XPosition = (SHORT)(XPosition + TabSize);
if (XPosition >= coordScreenBufferSize.X)
{
goto EndWhile;
}
else
{
const ULONG TabSize = NUMBER_OF_SPACES_IN_TAB(XPosition);
XPosition = (SHORT)(XPosition + TabSize);
if (XPosition >= coordScreenBufferSize.X || WI_IsFlagSet(dwFlags, WC_NONDESTRUCTIVE_TAB))
{
goto EndWhile;
}
for (ULONG j = 0; j < TabSize && i < LOCAL_BUFFER_SIZE; j++, i++)
{
*LocalBufPtr = UNICODE_SPACE;
LocalBufPtr++;
}
for (ULONG j = 0; j < TabSize && i < LOCAL_BUFFER_SIZE; j++, i++)
{
*LocalBufPtr = UNICODE_SPACE;
LocalBufPtr++;
}
pwchBuffer++;
break;
}
case UNICODE_LINEFEED:
case UNICODE_CARRIAGERETURN:
goto EndWhile;
@@ -753,52 +701,40 @@ constexpr unsigned int LOCAL_BUFFER_SIZE = 100;
}
case UNICODE_TAB:
{
// if VT-style tabs are set, then handle them the VT way, including not inserting spaces.
// just move the cursor to the next tab stop.
if (screenInfo.InVTMode())
const size_t TabSize = NUMBER_OF_SPACES_IN_TAB(cursor.GetPosition().X);
CursorPosition.X = (SHORT)(cursor.GetPosition().X + TabSize);
// move cursor forward to next tab stop. fill space with blanks.
// we get here when the tab extends beyond the right edge of the
// window. if the tab goes wraps the line, set the cursor to the first
// position in the next line.
pwchBuffer++;
TempNumSpaces += TabSize;
size_t NumChars = 0;
if (CursorPosition.X >= coordScreenBufferSize.X)
{
const COORD cCursorOld = cursor.GetPosition();
CursorPosition = screenInfo.GetForwardTab(cCursorOld);
NumChars = gsl::narrow<size_t>(coordScreenBufferSize.X - cursor.GetPosition().X);
CursorPosition.X = 0;
CursorPosition.Y = cursor.GetPosition().Y + 1;
// since you just tabbed yourself past the end of the row, set the wrap
textBuffer.GetRowByOffset(cursor.GetPosition().Y).GetCharRow().SetWrapForced(true);
}
else
{
const size_t TabSize = NUMBER_OF_SPACES_IN_TAB(cursor.GetPosition().X);
CursorPosition.X = (SHORT)(cursor.GetPosition().X + TabSize);
// move cursor forward to next tab stop. fill space with blanks.
// we get here when the tab extends beyond the right edge of the
// window. if the tab goes wraps the line, set the cursor to the first
// position in the next line.
pwchBuffer++;
TempNumSpaces += TabSize;
size_t NumChars = 0;
if (CursorPosition.X >= coordScreenBufferSize.X)
{
NumChars = gsl::narrow<size_t>(coordScreenBufferSize.X - cursor.GetPosition().X);
CursorPosition.X = 0;
CursorPosition.Y = cursor.GetPosition().Y + 1;
// since you just tabbed yourself past the end of the row, set the wrap
textBuffer.GetRowByOffset(cursor.GetPosition().Y).GetCharRow().SetWrapForced(true);
}
else
{
NumChars = gsl::narrow<size_t>(CursorPosition.X - cursor.GetPosition().X);
CursorPosition.Y = cursor.GetPosition().Y;
}
if (!WI_IsFlagSet(dwFlags, WC_NONDESTRUCTIVE_TAB))
{
try
{
const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars);
const auto done = screenInfo.Write(it, cursor.GetPosition());
NumChars = done.GetCellDistance(it);
}
CATCH_LOG();
}
NumChars = gsl::narrow<size_t>(CursorPosition.X - cursor.GetPosition().X);
CursorPosition.Y = cursor.GetPosition().Y;
}
try
{
const OutputCellIterator it(UNICODE_SPACE, Attributes, NumChars);
const auto done = screenInfo.Write(it, cursor.GetPosition());
NumChars = done.GetCellDistance(it);
}
CATCH_LOG();
Status = AdjustCursorPosition(screenInfo, CursorPosition, (dwFlags & WC_KEEP_CURSOR_VISIBLE) != 0, psScrollY);
break;
}

View File

@@ -145,7 +145,7 @@ void RedrawCommandLine(COOKED_READ_DATA& cookedReadData);
//#define WC_FALSIFY_UNICODE 0x08
#define WC_LIMIT_BACKSPACE 0x10
#define WC_NONDESTRUCTIVE_TAB 0x20
//#define WC_NONDESTRUCTIVE_TAB 0x20 - This is not needed anymore, because the VT code handles tabs internally now.
//#define WC_NEWLINE_SAVE_X 0x40 - This has been replaced with an output mode flag instead as it's line discipline behavior that may not necessarily be coupled with VT.
#define WC_DELAY_EOL_WRAP 0x80

View File

@@ -85,7 +85,7 @@ void WriteBuffer::_DefaultStringCase(const std::wstring_view string)
&dwNumBytes,
nullptr,
_io.GetActiveOutputBuffer().GetTextBuffer().GetCursor().GetPosition().X,
WC_LIMIT_BACKSPACE | WC_NONDESTRUCTIVE_TAB | WC_DELAY_EOL_WRAP,
WC_LIMIT_BACKSPACE | WC_DELAY_EOL_WRAP,
nullptr);
}

View File

@@ -127,7 +127,7 @@ SCREEN_INFORMATION::~SCREEN_INFORMATION()
const NTSTATUS status = pScreen->_InitializeOutputStateMachine();
if (pScreen->InVTMode())
if (pScreen->_IsInVTMode())
{
// microsoft/terminal#411: If VT mode is enabled, lets construct the
// VT tab stops. Without this line, if a user has
@@ -191,17 +191,6 @@ StateMachine& SCREEN_INFORMATION::GetStateMachine()
return *_stateMachine;
}
// Method Description:
// - returns true if this buffer is in Virtual Terminal Output mode.
// Arguments:
// <none>
// Return Value:
// true iff this buffer is in Virtual Terminal Output mode.
bool SCREEN_INFORMATION::InVTMode() const
{
return WI_IsFlagSet(OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// Routine Description:
// - This routine inserts the screen buffer pointer into the console's list of screen buffers.
// Arguments:
@@ -1964,6 +1953,17 @@ bool SCREEN_INFORMATION::_IsInPtyMode() const
return _IsAltBuffer() || gci.IsInVtIoMode();
}
// Routine Description:
// - returns true if this buffer is in Virtual Terminal Output mode.
// Parameters:
// - None
// Return Value:
// - true iff this buffer is in Virtual Terminal Output mode.
bool SCREEN_INFORMATION::_IsInVTMode() const
{
return WI_IsFlagSet(OutputMode, ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// Routine Description:
// - Sets a VT tab stop in the column sColumn. If there is already a tab there, it does nothing.
// Parameters:

View File

@@ -113,8 +113,6 @@ public:
bool SendNotifyBeep() const;
bool PostUpdateWindowSize() const;
bool InVTMode() const;
// TODO: MSFT 9355062 these methods should probably be a part of construction/destruction. http://osgvsowi/9355062
static void s_InsertScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo);
static void s_RemoveScreenBuffer(_In_ SCREEN_INFORMATION* const pScreenInfo);
@@ -273,6 +271,7 @@ private:
bool _IsAltBuffer() const;
bool _IsInPtyMode() const;
bool _IsInVTMode() const;
std::shared_ptr<Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;

View File

@@ -19,7 +19,7 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
_ColorTable(ColorTable),
_cColorTable(cColorTable),
_fUseAsciiOnly(fUseAsciiOnly),
_previousLineWrapped(false),
// _previousLineWrapped(false),
_usingUnderLine(false),
_needToDisableCursor(false),
_lastCursorIsVisible(false),
@@ -235,6 +235,8 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
{
HRESULT hr = S_OK;
_trace.TraceMoveCursor(coord);
if (coord.X != _lastText.X || coord.Y != _lastText.Y)
{
if (coord.X == 0 && coord.Y == 0)
@@ -248,8 +250,15 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
// If the previous line wrapped, then the cursor is already at this
// position, we just don't know it yet. Don't emit anything.
if (_previousLineWrapped)
bool previousLineWrapped = false;
if (_wrappedRow.has_value())
{
previousLineWrapped = coord.Y == _wrappedRow.value() + 1;
}
if (previousLineWrapped)
{
_trace.TraceWrapped();
hr = S_OK;
}
else
@@ -298,6 +307,9 @@ XtermEngine::XtermEngine(_In_ wil::unique_hfile hPipe,
_newBottomLine = false;
}
_deferredCursorPos = INVALID_COORDS;
_wrappedRow = std::nullopt;
return hr;
}

View File

@@ -376,8 +376,6 @@ using namespace Microsoft::Console::Types;
return S_OK;
}
RETURN_IF_FAILED(_MoveCursor(coord));
std::wstring unclusteredString;
unclusteredString.reserve(clusters.size());
short totalWidth = 0;
@@ -445,10 +443,37 @@ using namespace Microsoft::Console::Types;
(totalWidth - numSpaces) :
totalWidth;
if (cchActual == 0)
{
// If the previous row wrapped, but this line is empty, then we actually
// do want to move the cursor down. Otherwise, we'll possibly end up
// accidentally erasing the last character from the previous line, as
// the cursor is still waiting on that character for the next character
// to follow it.
_wrappedRow = std::nullopt;
// TODO:<Before PR>: Write a test that emulates ~/vttests/reflow-120.py
// TODO:<Before PR>: Write a test that emulates ~/vttests/reflow-advanced.py
}
// Move the cursor to the start of this run.
RETURN_IF_FAILED(_MoveCursor(coord));
// Write the actual text string
std::wstring wstr = std::wstring(unclusteredString.data(), cchActual);
RETURN_IF_FAILED(VtEngine::_WriteTerminalUtf8(wstr));
// If we've written text to the last column of the viewport, then mark
// that we've wrapped this line. The next time we attempt to move the
// cursor, if we're trying to move it to the start of the next line,
// we'll remember that this line was wrapped, and not manually break the
// line.
// Don't do this is the last character we're writing is a space - The last
// char will always be a space, but if we see that, we shouldn't wrap.
if ((_lastText.X + (totalWidth - numSpaces)) > _lastViewport.RightInclusive())
{
_wrappedRow = coord.Y;
}
// Update our internal tracker of the cursor's position.
// See MSFT:20266233
// If the cursor is at the rightmost column of the terminal, and we write a

View File

@@ -210,3 +210,30 @@ void RenderTracing::TraceLastText(const COORD lastTextPos) const
UNREFERENCED_PARAMETER(lastTextPos);
#endif UNIT_TESTING
}
void RenderTracing::TraceMoveCursor(const COORD pos) const
{
#ifndef UNIT_TESTING
const auto lastTextStr = _CoordToString(pos);
const auto cursor = lastTextStr.c_str();
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceMoveCursor",
TraceLoggingString(cursor),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
#else
UNREFERENCED_PARAMETER(pos);
#endif UNIT_TESTING
}
void RenderTracing::TraceWrapped() const
{
#ifndef UNIT_TESTING
const auto* const msg = "Wrapped instead of \\r\\n";
TraceLoggingWrite(g_hConsoleVtRendererTraceProvider,
"VtEngine_TraceWrapped",
TraceLoggingString(msg),
TraceLoggingLevel(WINEVENT_LEVEL_VERBOSE));
#else
UNREFERENCED_PARAMETER(pos);
#endif UNIT_TESTING
}

View File

@@ -29,6 +29,8 @@ namespace Microsoft::Console::VirtualTerminal
void TraceString(const std::string_view& str) const;
void TraceInvalidate(const Microsoft::Console::Types::Viewport view) const;
void TraceLastText(const COORD lastText) const;
void TraceMoveCursor(const COORD pos) const;
void TraceWrapped() const;
void TraceInvalidateAll(const Microsoft::Console::Types::Viewport view) const;
void TraceTriggerCircling(const bool newFrame) const;
void TraceStartPaint(const bool quickReturn,

View File

@@ -141,6 +141,8 @@ namespace Microsoft::Console::Render
Microsoft::Console::VirtualTerminal::RenderTracing _trace;
bool _inResizeRequest{ false };
std::optional<short> _wrappedRow{ std::nullopt };
[[nodiscard]] HRESULT _Write(std::string_view const str) noexcept;
[[nodiscard]] HRESULT _WriteFormattedString(const std::string* const pFormat, ...) noexcept;
[[nodiscard]] HRESULT _Flush() noexcept;