From 1ade14e883bd615513bd1d51b62771d79d2b3fdd Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Thu, 20 Jun 2024 18:12:36 +0200 Subject: [PATCH 1/3] =?UTF-8?q?Horizontal=20snapping=20=C3=A0=20la=20vim?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/host/_stream.cpp | 5 ++ src/host/input.cpp | 7 ++- src/host/screenInfo.cpp | 86 ++++++++++++++++++++------- src/host/screenInfo.hpp | 14 ++++- src/interactivity/win32/Clipboard.cpp | 5 ++ 5 files changed, 94 insertions(+), 23 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index 9c3d3378a80..c9ca3986d8d 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -206,6 +206,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t writer.Submit(); } + screenInfo.SnapOnOutput(); return; } @@ -328,6 +329,8 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t { writer.Submit(); } + + screenInfo.SnapOnOutput(); } // This is the main entrypoint for conhost to write VT to the buffer. @@ -375,6 +378,8 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) write(offset, std::wstring_view::npos); writer.Submit(); } + + screenInfo.SnapOnOutput(); } // Erases all contents of the given screenInfo, including the current screen and scrollback. diff --git a/src/host/input.cpp b/src/host/input.cpp index fd92154020c..d56c49b2e71 100644 --- a/src/host/input.cpp +++ b/src/host/input.cpp @@ -108,7 +108,7 @@ bool ShouldTakeOverKeyboardShortcuts() void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak) { auto& keyEvent = event.Event.KeyEvent; - const auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); + auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto ContinueProcessing = true; if (WI_IsAnyFlagSet(keyEvent.dwControlKeyState, CTRL_PRESSED) && @@ -167,6 +167,11 @@ void HandleGenericKeyEvent(INPUT_RECORD event, const bool generateBreak) keyEvent.bKeyDown = false; gci.pInputBuffer->Write(event); } + + if (gci.HasActiveOutputBuffer()) + { + gci.GetActiveOutputBuffer().SnapOnInput(keyEvent.wVirtualKeyCode); + } } } diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index 0084dc5bced..ddd358d3d14 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1669,39 +1669,85 @@ void SCREEN_INFORMATION::SetCursorDBMode(const bool DoubleCursor) return STATUS_SUCCESS; } -void SCREEN_INFORMATION::MakeCursorVisible(const til::point CursorPosition) +static constexpr bool IsInputKey(WORD vkey) { - til::point WindowOrigin; + return vkey != VK_CONTROL && + vkey != VK_LCONTROL && + vkey != VK_RCONTROL && + vkey != VK_MENU && + vkey != VK_LMENU && + vkey != VK_RMENU && + vkey != VK_SHIFT && + vkey != VK_LSHIFT && + vkey != VK_RSHIFT && + vkey != VK_LWIN && + vkey != VK_RWIN && + vkey != VK_SNAPSHOT; +} - if (CursorPosition.x > _viewport.RightInclusive()) - { - WindowOrigin.x = CursorPosition.x - _viewport.RightInclusive(); - } - else if (CursorPosition.x < _viewport.Left()) - { - WindowOrigin.x = CursorPosition.x - _viewport.Left(); - } - else +void SCREEN_INFORMATION::MakeCursorVisible(til::point position) +{ + _makeLocationVisible(position, ViewportMovementMask::Vertical | ViewportMovementMask::Horizontal); +} + +void SCREEN_INFORMATION::SnapOnInput(const WORD vkey) +{ + if (IsInputKey(vkey)) { - WindowOrigin.x = 0; + _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), ViewportMovementMask::Vertical); } +} - if (CursorPosition.y > _viewport.BottomInclusive()) +void SCREEN_INFORMATION::SnapOnOutput() +{ + _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), ViewportMovementMask::HorizontalCenter); +} + +void SCREEN_INFORMATION::_makeLocationVisible(til::point position, ViewportMovementMask movements) +{ + const auto viewportOrigin = _viewport.Origin(); + const auto viewportSize = _viewport.Dimensions(); + const auto bufferSize = _textBuffer->GetSize().Dimensions(); + auto origin = viewportOrigin; + + // Ensure the given position is in bounds. + position.x = std::clamp(position.x, 0, bufferSize.width - 1); + position.y = std::clamp(position.y, 0, bufferSize.height - 1); + + if (WI_IsAnyFlagSet(movements, ViewportMovementMask::Vertical)) { - WindowOrigin.y = CursorPosition.y - _viewport.BottomInclusive(); + origin.y = std::min(origin.y, position.y); // shift up if above + origin.y = std::max(origin.y, position.y - (viewportSize.height - 1)); // shift down if below } - else if (CursorPosition.y < _viewport.Top()) + + if (WI_IsAnyFlagSet(movements, ViewportMovementMask::Horizontal)) { - WindowOrigin.y = CursorPosition.y - _viewport.Top(); + origin.x = std::min(origin.x, position.x); // shift left if left + origin.x = std::max(origin.x, position.x - (viewportSize.width - 1)); // shift right if right } - else + + if (WI_IsAnyFlagSet(movements, ViewportMovementMask::HorizontalCenter)) { - WindowOrigin.y = 0; + // If the position is horizontally outside the viewport, snap the + // viewport to the nearest multiple of half the viewport width. + if (position.x < origin.x || position.x >= (origin.x + viewportSize.width)) + { + const auto div = viewportSize.width / 2; + // We want our viewport to be centered around the position (= "half-width" offset). + // Since the origin is the left edge, we must subtract a half-width from the position. + origin.x = position.x - div; + // Round down to the nearest multiple of the viewport width. + // This also works if origin.x is negative, because the "modulo operator" + // is not a modulo operator, it's a remainder operator. The remainder of a + // negative number is negative and so origin.x cannot end up being less than 0. + origin.x -= origin.x % div; + origin.x = std::min(origin.x, bufferSize.width - viewportSize.width); + } } - if (WindowOrigin.x != 0 || WindowOrigin.y != 0) + if (origin != viewportOrigin) { - LOG_IF_FAILED(SetViewportOrigin(false, WindowOrigin, false)); + std::ignore = SetViewportOrigin(true, origin, false); } } diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 7f0b9357a14..44eb9fe8f0a 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -47,6 +47,14 @@ Revision History: #include "../types/inc/Viewport.hpp" class ConversionAreaInfo; // forward decl window. circular reference +enum class ViewportMovementMask +{ + Vertical = 0x1, + Horizontal = 0x2, + HorizontalCenter = 0x4, +}; +DEFINE_ENUM_FLAG_OPERATORS(ViewportMovementMask); + class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider { public: @@ -71,6 +79,9 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void GetRequiredConsoleSizeInPixels(_Out_ til::size* const pRequiredSize) const; void MakeCurrentCursorVisible(); + void MakeCursorVisible(til::point position); + void SnapOnInput(WORD vkey); + void SnapOnOutput(); void ClipToScreenBuffer(_Inout_ til::inclusive_rect* const psrClip) const; @@ -184,8 +195,6 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void SetCursorDBMode(const bool DoubleCursor); [[nodiscard]] NTSTATUS SetCursorPosition(const til::point Position, const bool TurnOn); - void MakeCursorVisible(const til::point CursorPosition); - [[nodiscard]] NTSTATUS UseAlternateScreenBuffer(const TextAttribute& initAttributes); void UseMainScreenBuffer(); @@ -235,6 +244,7 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void _CalculateViewportSize(const til::rect* const prcClientArea, _Out_ til::size* const pcoordSize); void _AdjustViewportSize(const til::rect* const prcClientNew, const til::rect* const prcClientOld, const til::size* const pcoordSize); void _InternalSetViewportSize(const til::size* pcoordSize, const bool fResizeFromTop, const bool fResizeFromLeft); + void _makeLocationVisible(til::point position, ViewportMovementMask movements); static void s_CalculateScrollbarVisibility(const til::rect* const prcClientArea, const til::size* const pcoordBufferSize, diff --git a/src/interactivity/win32/Clipboard.cpp b/src/interactivity/win32/Clipboard.cpp index 8b72f01b188..03951493839 100644 --- a/src/interactivity/win32/Clipboard.cpp +++ b/src/interactivity/win32/Clipboard.cpp @@ -162,6 +162,11 @@ void Clipboard::StringPaste(_In_reads_(cchData) const wchar_t* const pData, const auto bracketedPasteMode = gci.GetBracketedPasteMode(); auto inEvents = TextToKeyEvents(pData, cchData, vtInputMode && bracketedPasteMode); gci.pInputBuffer->Write(inEvents); + + if (gci.HasActiveOutputBuffer()) + { + gci.GetActiveOutputBuffer().SnapOnInput(0); + } } catch (...) { From 75021d812c697ff5e400d07fcf4e25c344c75705 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Tue, 6 Aug 2024 00:41:49 +0200 Subject: [PATCH 2/3] =?UTF-8?q?Horizontal=20snapping=20=C3=A0=20la=20conho?= =?UTF-8?q?st?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/host/_stream.cpp | 4 ++++ src/host/screenInfo.cpp | 31 ++++++------------------------- src/host/screenInfo.hpp | 10 +--------- 3 files changed, 11 insertions(+), 34 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index c9ca3986d8d..461cce34664 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -167,6 +167,8 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto writer = gci.GetVtWriterForBuffer(&screenInfo); + screenInfo.SnapOnOutput(); + // If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode. // Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back // to a position that the Console APIs expect (= not delayed). @@ -343,6 +345,8 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) // may change, so get the VtIo reference now, just in case. auto writer = gci.GetVtWriterForBuffer(&screenInfo); + screenInfo.SnapOnOutput(); + stateMachine.ProcessString(str); if (writer) diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index ddd358d3d14..7843cc4574a 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1687,23 +1687,23 @@ static constexpr bool IsInputKey(WORD vkey) void SCREEN_INFORMATION::MakeCursorVisible(til::point position) { - _makeLocationVisible(position, ViewportMovementMask::Vertical | ViewportMovementMask::Horizontal); + _makeLocationVisible(position, true, true); } void SCREEN_INFORMATION::SnapOnInput(const WORD vkey) { if (IsInputKey(vkey)) { - _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), ViewportMovementMask::Vertical); + _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), true, false); } } void SCREEN_INFORMATION::SnapOnOutput() { - _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), ViewportMovementMask::HorizontalCenter); + _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), false, true); } -void SCREEN_INFORMATION::_makeLocationVisible(til::point position, ViewportMovementMask movements) +void SCREEN_INFORMATION::_makeLocationVisible(til::point position, bool vertical, bool horizontal) { const auto viewportOrigin = _viewport.Origin(); const auto viewportSize = _viewport.Dimensions(); @@ -1714,37 +1714,18 @@ void SCREEN_INFORMATION::_makeLocationVisible(til::point position, ViewportMovem position.x = std::clamp(position.x, 0, bufferSize.width - 1); position.y = std::clamp(position.y, 0, bufferSize.height - 1); - if (WI_IsAnyFlagSet(movements, ViewportMovementMask::Vertical)) + if (vertical) { origin.y = std::min(origin.y, position.y); // shift up if above origin.y = std::max(origin.y, position.y - (viewportSize.height - 1)); // shift down if below } - if (WI_IsAnyFlagSet(movements, ViewportMovementMask::Horizontal)) + if (horizontal) { origin.x = std::min(origin.x, position.x); // shift left if left origin.x = std::max(origin.x, position.x - (viewportSize.width - 1)); // shift right if right } - if (WI_IsAnyFlagSet(movements, ViewportMovementMask::HorizontalCenter)) - { - // If the position is horizontally outside the viewport, snap the - // viewport to the nearest multiple of half the viewport width. - if (position.x < origin.x || position.x >= (origin.x + viewportSize.width)) - { - const auto div = viewportSize.width / 2; - // We want our viewport to be centered around the position (= "half-width" offset). - // Since the origin is the left edge, we must subtract a half-width from the position. - origin.x = position.x - div; - // Round down to the nearest multiple of the viewport width. - // This also works if origin.x is negative, because the "modulo operator" - // is not a modulo operator, it's a remainder operator. The remainder of a - // negative number is negative and so origin.x cannot end up being less than 0. - origin.x -= origin.x % div; - origin.x = std::min(origin.x, bufferSize.width - viewportSize.width); - } - } - if (origin != viewportOrigin) { std::ignore = SetViewportOrigin(true, origin, false); diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 44eb9fe8f0a..e1d9e645eed 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -47,14 +47,6 @@ Revision History: #include "../types/inc/Viewport.hpp" class ConversionAreaInfo; // forward decl window. circular reference -enum class ViewportMovementMask -{ - Vertical = 0x1, - Horizontal = 0x2, - HorizontalCenter = 0x4, -}; -DEFINE_ENUM_FLAG_OPERATORS(ViewportMovementMask); - class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console::IIoProvider { public: @@ -244,7 +236,7 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void _CalculateViewportSize(const til::rect* const prcClientArea, _Out_ til::size* const pcoordSize); void _AdjustViewportSize(const til::rect* const prcClientNew, const til::rect* const prcClientOld, const til::size* const pcoordSize); void _InternalSetViewportSize(const til::size* pcoordSize, const bool fResizeFromTop, const bool fResizeFromLeft); - void _makeLocationVisible(til::point position, ViewportMovementMask movements); + void _makeLocationVisible(til::point position, bool vertical, bool horizontal); static void s_CalculateScrollbarVisibility(const til::rect* const prcClientArea, const til::size* const pcoordBufferSize, From e4814d901045929c7caf39cd0ebd3a180fc24ac8 Mon Sep 17 00:00:00 2001 From: Leonard Hecker Date: Fri, 9 Aug 2024 19:01:40 +0200 Subject: [PATCH 3/3] Experiments --- src/host/_stream.cpp | 20 ++----------------- src/host/screenInfo.cpp | 44 +++++++++++++++++------------------------ src/host/screenInfo.hpp | 13 ++++++++++-- 3 files changed, 31 insertions(+), 46 deletions(-) diff --git a/src/host/_stream.cpp b/src/host/_stream.cpp index fe80bf15bfa..4c588db4dde 100644 --- a/src/host/_stream.cpp +++ b/src/host/_stream.cpp @@ -97,17 +97,6 @@ static void AdjustCursorPosition(SCREEN_INFORMATION& screenInfo, _In_ til::point coordCursor.y = bufferSize.height - 1; } - const auto cursorMovedPastViewport = coordCursor.y > screenInfo.GetViewport().BottomInclusive(); - - // if at right or bottom edge of window, scroll right or down one char. - if (cursorMovedPastViewport) - { - til::point WindowOrigin; - WindowOrigin.x = 0; - WindowOrigin.y = coordCursor.y - screenInfo.GetViewport().BottomInclusive(); - LOG_IF_FAILED(screenInfo.SetViewportOrigin(false, WindowOrigin, true)); - } - LOG_IF_FAILED(screenInfo.SetCursorPosition(coordCursor, false)); } @@ -167,7 +156,7 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation(); auto writer = gci.GetVtWriterForBuffer(&screenInfo); - screenInfo.SnapOnOutput(); + const auto snap = screenInfo.SnapOnOutput(); // If we enter this if condition, then someone wrote text in VT mode and now switched to non-VT mode. // Since the Console APIs don't support delayed EOL wrapping, we need to first put the cursor back @@ -208,7 +197,6 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t writer.Submit(); } - screenInfo.SnapOnOutput(); return; } @@ -331,8 +319,6 @@ void WriteCharsLegacy(SCREEN_INFORMATION& screenInfo, const std::wstring_view& t { writer.Submit(); } - - screenInfo.SnapOnOutput(); } // This is the main entrypoint for conhost to write VT to the buffer. @@ -345,7 +331,7 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) // may change, so get the VtIo reference now, just in case. auto writer = gci.GetVtWriterForBuffer(&screenInfo); - screenInfo.SnapOnOutput(); + const auto snap = screenInfo.SnapOnOutput(); stateMachine.ProcessString(str); @@ -387,8 +373,6 @@ void WriteCharsVT(SCREEN_INFORMATION& screenInfo, const std::wstring_view& str) write(offset, std::wstring_view::npos); writer.Submit(); } - - screenInfo.SnapOnOutput(); } // Erases all contents of the given screenInfo, including the current screen and scrollback. diff --git a/src/host/screenInfo.cpp b/src/host/screenInfo.cpp index ac3df0315d9..a894ff25a80 100644 --- a/src/host/screenInfo.cpp +++ b/src/host/screenInfo.cpp @@ -1685,24 +1685,6 @@ static constexpr bool IsInputKey(WORD vkey) } void SCREEN_INFORMATION::MakeCursorVisible(til::point position) -{ - _makeLocationVisible(position, true, true); -} - -void SCREEN_INFORMATION::SnapOnInput(const WORD vkey) -{ - if (IsInputKey(vkey)) - { - _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), true, false); - } -} - -void SCREEN_INFORMATION::SnapOnOutput() -{ - _makeLocationVisible(_textBuffer->GetCursor().GetPosition(), false, true); -} - -void SCREEN_INFORMATION::_makeLocationVisible(til::point position, bool vertical, bool horizontal) { const auto viewportOrigin = _viewport.Origin(); const auto viewportSize = _viewport.Dimensions(); @@ -1713,21 +1695,31 @@ void SCREEN_INFORMATION::_makeLocationVisible(til::point position, bool vertical position.x = std::clamp(position.x, 0, bufferSize.width - 1); position.y = std::clamp(position.y, 0, bufferSize.height - 1); - if (vertical) + origin.y = std::min(origin.y, position.y); // shift up if above + origin.y = std::max(origin.y, position.y - (viewportSize.height - 1)); // shift down if below + + origin.x = std::min(origin.x, position.x); // shift left if left + origin.x = std::max(origin.x, position.x - (viewportSize.width - 1)); // shift right if right + + if (origin != viewportOrigin) { - origin.y = std::min(origin.y, position.y); // shift up if above - origin.y = std::max(origin.y, position.y - (viewportSize.height - 1)); // shift down if below + std::ignore = SetViewportOrigin(true, origin, false); } +} - if (horizontal) +void SCREEN_INFORMATION::SnapOnInput(const WORD vkey) +{ + if (IsInputKey(vkey)) { - origin.x = std::min(origin.x, position.x); // shift left if left - origin.x = std::max(origin.x, position.x - (viewportSize.width - 1)); // shift right if right + _makeCursorVisible(); } +} - if (origin != viewportOrigin) +void SCREEN_INFORMATION::_makeCursorVisible() +{ + if (_textBuffer->GetCursor().IsOn()) { - std::ignore = SetViewportOrigin(true, origin, false); + MakeCursorVisible(_textBuffer->GetCursor().GetPosition()); } } diff --git a/src/host/screenInfo.hpp b/src/host/screenInfo.hpp index 189b5452676..8c870edc31f 100644 --- a/src/host/screenInfo.hpp +++ b/src/host/screenInfo.hpp @@ -73,7 +73,16 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void MakeCurrentCursorVisible(); void MakeCursorVisible(til::point position); void SnapOnInput(WORD vkey); - void SnapOnOutput(); + auto SnapOnOutput() + { + const auto inBounds = _viewport.IsInBounds(_textBuffer->GetCursor().GetPosition()); + return wil::scope_exit([this, inBounds]() { + if (inBounds) + { + _makeCursorVisible(); + } + }); + } void ClipToScreenBuffer(_Inout_ til::inclusive_rect* const psrClip) const; @@ -233,7 +242,7 @@ class SCREEN_INFORMATION : public ConsoleObjectHeader, public Microsoft::Console void _CalculateViewportSize(const til::rect* const prcClientArea, _Out_ til::size* const pcoordSize); void _AdjustViewportSize(const til::rect* const prcClientNew, const til::rect* const prcClientOld, const til::size* const pcoordSize); void _InternalSetViewportSize(const til::size* pcoordSize, const bool fResizeFromTop, const bool fResizeFromLeft); - void _makeLocationVisible(til::point position, bool vertical, bool horizontal); + void _makeCursorVisible(); static void s_CalculateScrollbarVisibility(const til::rect* const prcClientArea, const til::size* const pcoordBufferSize,