diff --git a/src/cascadia/TerminalCore/ITerminalApi.hpp b/src/cascadia/TerminalCore/ITerminalApi.hpp index 064c4cd7def..77ae773a87d 100644 --- a/src/cascadia/TerminalCore/ITerminalApi.hpp +++ b/src/cascadia/TerminalCore/ITerminalApi.hpp @@ -28,6 +28,7 @@ namespace Microsoft::Terminal::Core virtual bool SetCursorPosition(short x, short y) noexcept = 0; virtual COORD GetCursorPosition() noexcept = 0; + virtual bool CursorLineFeed(const bool withReturn) noexcept = 0; virtual bool DeleteCharacter(const size_t count) noexcept = 0; virtual bool InsertCharacter(const size_t count) noexcept = 0; diff --git a/src/cascadia/TerminalCore/Terminal.cpp b/src/cascadia/TerminalCore/Terminal.cpp index f71ffb76472..c8dabc88552 100644 --- a/src/cascadia/TerminalCore/Terminal.cpp +++ b/src/cascadia/TerminalCore/Terminal.cpp @@ -414,7 +414,6 @@ Viewport Terminal::_GetVisibleViewport() const noexcept void Terminal::_WriteBuffer(const std::wstring_view& stringView) { auto& cursor = _buffer->GetCursor(); - const Viewport bufferSize = _buffer->GetSize(); // Defer the cursor drawing while we are iterating the string, for a better performance. // We can not waste time displaying a cursor event when we know more text is coming right behind it. @@ -425,104 +424,85 @@ void Terminal::_WriteBuffer(const std::wstring_view& stringView) const auto wch = stringView.at(i); const COORD cursorPosBefore = cursor.GetPosition(); COORD proposedCursorPosition = cursorPosBefore; - bool notifyScroll = false; - if (wch == UNICODE_LINEFEED) - { - proposedCursorPosition.Y++; - } - else if (wch == UNICODE_CARRIAGERETURN) + // TODO: MSFT 21006766 + // This is not great but I need it demoable. Fix by making a buffer stream writer. + // + // If wch is a surrogate character we need to read 2 code units + // from the stringView to form a single code point. + const auto isSurrogate = wch >= 0xD800 && wch <= 0xDFFF; + const auto view = stringView.substr(i, isSurrogate ? 2 : 1); + const OutputCellIterator it{ view, _buffer->GetCurrentAttributes() }; + const auto end = _buffer->Write(it); + const auto cellDistance = end.GetCellDistance(it); + const auto inputDistance = end.GetInputDistance(it); + + if (inputDistance > 0) { - 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 + // If "wch" was a surrogate character, we just consumed 2 code units above. + // -> Increment "i" by 1 in that case and thus by 2 in total in this iteration. + proposedCursorPosition.X += gsl::narrow(cellDistance); + i += inputDistance - 1; } else { - // TODO: MSFT 21006766 - // This is not great but I need it demoable. Fix by making a buffer stream writer. - // - // If wch is a surrogate character we need to read 2 code units - // from the stringView to form a single code point. - const auto isSurrogate = wch >= 0xD800 && wch <= 0xDFFF; - const auto view = stringView.substr(i, isSurrogate ? 2 : 1); - const OutputCellIterator it{ view, _buffer->GetCurrentAttributes() }; - const auto end = _buffer->Write(it); - const auto cellDistance = end.GetCellDistance(it); - const auto inputDistance = end.GetInputDistance(it); - - if (inputDistance > 0) - { - // If "wch" was a surrogate character, we just consumed 2 code units above. - // -> Increment "i" by 1 in that case and thus by 2 in total in this iteration. - proposedCursorPosition.X += gsl::narrow(cellDistance); - i += inputDistance - 1; - } - else - { - // If _WriteBuffer() is called with a consecutive string longer than the viewport/buffer width - // the call to _buffer->Write() will refuse to write anything on the current line. - // GetInputDistance() thus returns 0, which would in turn cause i to be - // decremented by 1 below and force the outer loop to loop forever. - // This if() basically behaves as if "\r\n" had been encountered above and retries the write. - // With well behaving shells during normal operation this safeguard should normally not be encountered. - proposedCursorPosition.X = 0; - proposedCursorPosition.Y++; - } + // If _WriteBuffer() is called with a consecutive string longer than the viewport/buffer width + // the call to _buffer->Write() will refuse to write anything on the current line. + // GetInputDistance() thus returns 0, which would in turn cause i to be + // decremented by 1 below and force the outer loop to loop forever. + // This if() basically behaves as if "\r\n" had been encountered above and retries the write. + // With well behaving shells during normal operation this safeguard should normally not be encountered. + proposedCursorPosition.X = 0; + proposedCursorPosition.Y++; } - // If we're about to scroll past the bottom of the buffer, instead cycle the buffer. - const auto newRows = proposedCursorPosition.Y - bufferSize.Height() + 1; - if (newRows > 0) - { - for (auto dy = 0; dy < newRows; dy++) - { - _buffer->IncrementCircularBuffer(); - proposedCursorPosition.Y--; - } - notifyScroll = true; - } + _AdjustCursorPosition(proposedCursorPosition); + } - // This section is essentially equivalent to `AdjustCursorPosition` - // Update Cursor Position - cursor.SetPosition(proposedCursorPosition); + cursor.EndDeferDrawing(); +} - const COORD cursorPosAfter = cursor.GetPosition(); +void Terminal::_AdjustCursorPosition(const COORD proposedPosition) +{ +#pragma warning(suppress : 26496) // cpp core checks wants this const but it's modified below. + auto proposedCursorPosition = proposedPosition; + auto& cursor = _buffer->GetCursor(); + const Viewport bufferSize = _buffer->GetSize(); + bool notifyScroll = false; - // Move the viewport down if the cursor moved below the viewport. - if (cursorPosAfter.Y > _mutableViewport.BottomInclusive()) + // If we're about to scroll past the bottom of the buffer, instead cycle the buffer. + const auto newRows = proposedCursorPosition.Y - bufferSize.Height() + 1; + if (newRows > 0) + { + for (auto dy = 0; dy < newRows; dy++) { - const auto newViewTop = std::max(0, cursorPosAfter.Y - (_mutableViewport.Height() - 1)); - if (newViewTop != _mutableViewport.Top()) - { - _mutableViewport = Viewport::FromDimensions({ 0, gsl::narrow(newViewTop) }, _mutableViewport.Dimensions()); - notifyScroll = true; - } + _buffer->IncrementCircularBuffer(); + proposedCursorPosition.Y--; } + notifyScroll = true; + } + + // Update Cursor Position + cursor.SetPosition(proposedCursorPosition); - if (notifyScroll) + const COORD cursorPosAfter = cursor.GetPosition(); + + // Move the viewport down if the cursor moved below the viewport. + if (cursorPosAfter.Y > _mutableViewport.BottomInclusive()) + { + const auto newViewTop = std::max(0, cursorPosAfter.Y - (_mutableViewport.Height() - 1)); + if (newViewTop != _mutableViewport.Top()) { - _buffer->GetRenderTarget().TriggerRedrawAll(); - _NotifyScrollEvent(); + _mutableViewport = Viewport::FromDimensions({ 0, gsl::narrow(newViewTop) }, _mutableViewport.Dimensions()); + notifyScroll = true; } } - cursor.EndDeferDrawing(); + if (notifyScroll) + { + _buffer->GetRenderTarget().TriggerRedrawAll(); + _NotifyScrollEvent(); + } } void Terminal::UserScrollViewport(const int viewTop) diff --git a/src/cascadia/TerminalCore/Terminal.hpp b/src/cascadia/TerminalCore/Terminal.hpp index 89819515883..b60953e5c73 100644 --- a/src/cascadia/TerminalCore/Terminal.hpp +++ b/src/cascadia/TerminalCore/Terminal.hpp @@ -75,6 +75,7 @@ class Microsoft::Terminal::Core::Terminal final : bool ReverseText(bool reversed) noexcept override; bool SetCursorPosition(short x, short y) noexcept override; COORD GetCursorPosition() noexcept override; + bool CursorLineFeed(const bool withReturn) noexcept override; bool DeleteCharacter(const size_t count) noexcept override; bool InsertCharacter(const size_t count) noexcept override; bool EraseCharacters(const size_t numChars) noexcept override; @@ -230,6 +231,8 @@ class Microsoft::Terminal::Core::Terminal final : void _WriteBuffer(const std::wstring_view& stringView); + void _AdjustCursorPosition(const COORD proposedPosition); + void _NotifyScrollEvent() noexcept; #pragma region TextSelection diff --git a/src/cascadia/TerminalCore/TerminalApi.cpp b/src/cascadia/TerminalCore/TerminalApi.cpp index 88588e42965..3a6a543be07 100644 --- a/src/cascadia/TerminalCore/TerminalApi.cpp +++ b/src/cascadia/TerminalCore/TerminalApi.cpp @@ -132,6 +132,27 @@ COORD Terminal::GetCursorPosition() noexcept return newPos; } +// Method Description: +// - Moves the cursor down one line, and possibly also to the leftmost column. +// Arguments: +// - withReturn, set to true if a carriage return should be performed as well. +// Return value: +// - true if succeeded, false otherwise +bool Terminal::CursorLineFeed(const bool withReturn) noexcept +try +{ + auto cursorPos = _buffer->GetCursor().GetPosition(); + cursorPos.Y++; + if (withReturn) + { + cursorPos.X = 0; + } + _AdjustCursorPosition(cursorPos); + + return true; +} +CATCH_LOG_RETURN_FALSE() + // Method Description: // - deletes count characters starting from the cursor's current position // - it moves over the remaining text to 'replace' the deleted text diff --git a/src/cascadia/TerminalCore/TerminalDispatch.cpp b/src/cascadia/TerminalCore/TerminalDispatch.cpp index 54e33d42cfa..bdcff521bc5 100644 --- a/src/cascadia/TerminalCore/TerminalDispatch.cpp +++ b/src/cascadia/TerminalCore/TerminalDispatch.cpp @@ -83,12 +83,9 @@ try // There is currently no need for mode-specific line feeds in the Terminal, // so for now we just treat them as a line feed without carriage return. case DispatchTypes::LineFeedType::WithoutReturn: - Execute(L'\n'); - return true; + return _terminalApi.CursorLineFeed(false); case DispatchTypes::LineFeedType::WithReturn: - Execute(L'\r'); - Execute(L'\n'); - return true; + return _terminalApi.CursorLineFeed(true); default: return false; } diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index b5e97d17f20..1d4c010cc49 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -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; - - 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; - } - - if (fDoEolWrap) - { - CursorPosition.X = 0; - CursorPosition.Y++; + CursorPosition.X = 0; + CursorPosition.Y++; - Status = AdjustCursorPosition(screenInfo, CursorPosition, WI_IsFlagSet(dwFlags, WC_KEEP_CURSOR_VISIBLE), psScrollY); + 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(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(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(CursorPosition.X - cursor.GetPosition().X); - CursorPosition.Y = cursor.GetPosition().Y; - } + NumChars = gsl::narrow(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(); - } + 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; } diff --git a/src/host/cmdline.h b/src/host/cmdline.h index 6c696c67478..8cbbb40923b 100644 --- a/src/host/cmdline.h +++ b/src/host/cmdline.h @@ -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 diff --git a/src/host/outputStream.cpp b/src/host/outputStream.cpp index 897e7b33cbd..3fdbeb8db7d 100644 --- a/src/host/outputStream.cpp +++ b/src/host/outputStream.cpp @@ -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); } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index e5740bdce40..842439602ca 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -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: -// -// 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: diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 4fe8d21689d..44eb039a9f7 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -113,8 +113,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console 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 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console bool _IsAltBuffer() const; bool _IsInPtyMode() const; + bool _IsInVTMode() const; std::shared_ptr _stateMachine;