mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-10 00:48:23 -06:00
ConPTY: Emit DSR CPR on resize (#19089)
This will help terminals with a reflow behavior unlike the one implemented in ConPTY, such as VS Code. Closes #18725 ## Validation Steps Performed * Tested under a debugger ✅ * Use PowerShell 5 and reflow lines in the ConPTY buffer until they're pushed outside the top viewport. Then type something in the prompt. Cursor is in a consistent position, even if slightly off. ✅
This commit is contained in:
parent
666a75bc70
commit
c55aca508b
@ -184,6 +184,9 @@ void PtySignalInputThread::_DoResizeWindow(const ResizeWindowData& data)
|
||||
}
|
||||
|
||||
_api.ResizeWindow(data.sx, data.sy);
|
||||
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
gci.GetVtIo()->RequestCursorPositionFromTerminal();
|
||||
}
|
||||
|
||||
void PtySignalInputThread::_DoClearBuffer(const bool keepCursorRow) const
|
||||
|
||||
@ -21,13 +21,13 @@ using namespace Microsoft::Console::VirtualTerminal;
|
||||
// - hPipe - a handle to the file representing the read end of the VT pipe.
|
||||
// - inheritCursor - a bool indicating if the state machine should expect a
|
||||
// cursor positioning sequence. See MSFT:15681311.
|
||||
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor) :
|
||||
VtInputThread::VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR) :
|
||||
_hFile{ std::move(hPipe) }
|
||||
{
|
||||
THROW_HR_IF(E_HANDLE, _hFile.get() == INVALID_HANDLE_VALUE);
|
||||
|
||||
auto dispatch = std::make_unique<InteractDispatch>();
|
||||
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), inheritCursor);
|
||||
auto engine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), std::move(capturedCPR));
|
||||
_pInputStateMachine = std::make_unique<StateMachine>(std::move(engine));
|
||||
}
|
||||
|
||||
@ -185,8 +185,7 @@ void VtInputThread::_InputThread()
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
til::enumset<DeviceAttribute, uint64_t> VtInputThread::WaitUntilDA1(DWORD timeout) const noexcept
|
||||
InputStateMachineEngine& VtInputThread::GetInputStateMachineEngine() const noexcept
|
||||
{
|
||||
const auto& engine = static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
|
||||
return engine.WaitUntilDA1(timeout);
|
||||
return static_cast<InputStateMachineEngine&>(_pInputStateMachine->Engine());
|
||||
}
|
||||
|
||||
@ -20,16 +20,17 @@ namespace Microsoft::Console
|
||||
{
|
||||
namespace VirtualTerminal
|
||||
{
|
||||
class InputStateMachineEngine;
|
||||
enum class DeviceAttribute : uint64_t;
|
||||
}
|
||||
|
||||
class VtInputThread
|
||||
{
|
||||
public:
|
||||
VtInputThread(_In_ wil::unique_hfile hPipe, const bool inheritCursor);
|
||||
VtInputThread(_In_ wil::unique_hfile hPipe, std::function<void()> capturedCPR = nullptr);
|
||||
|
||||
[[nodiscard]] HRESULT Start();
|
||||
til::enumset<VirtualTerminal::DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;
|
||||
VirtualTerminal::InputStateMachineEngine& GetInputStateMachineEngine() const noexcept;
|
||||
|
||||
private:
|
||||
static DWORD WINAPI StaticVtInputThreadProc(_In_ LPVOID lpParameter);
|
||||
|
||||
@ -11,6 +11,7 @@
|
||||
#include "output.h" // CloseConsoleProcessState
|
||||
#include "../interactivity/inc/ServiceLocator.hpp"
|
||||
#include "../renderer/base/renderer.hpp"
|
||||
#include "../terminal/parser/InputStateMachineEngine.hpp"
|
||||
#include "../types/inc/CodepointWidthDetector.hpp"
|
||||
#include "../types/inc/utils.hpp"
|
||||
|
||||
@ -155,7 +156,9 @@ bool VtIo::IsUsingVt() const
|
||||
{
|
||||
if (IsValidHandle(_hInput.get()))
|
||||
{
|
||||
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), _lookingForCursorPosition);
|
||||
_pVtInputThread = std::make_unique<VtInputThread>(std::move(_hInput), [this]() {
|
||||
_cursorPositionReportReceived();
|
||||
});
|
||||
}
|
||||
}
|
||||
CATCH_RETURN();
|
||||
@ -177,6 +180,7 @@ bool VtIo::IsUsingVt() const
|
||||
// wait for the DA1 response below and effectively wait for both.
|
||||
if (_lookingForCursorPosition)
|
||||
{
|
||||
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
|
||||
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
|
||||
}
|
||||
|
||||
@ -197,7 +201,7 @@ bool VtIo::IsUsingVt() const
|
||||
// Allow the input thread to momentarily gain the console lock.
|
||||
auto& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
|
||||
const auto suspension = gci.SuspendLock();
|
||||
_deviceAttributes = _pVtInputThread->WaitUntilDA1(3000);
|
||||
_deviceAttributes = _pVtInputThread->GetInputStateMachineEngine().WaitUntilDA1(3000);
|
||||
}
|
||||
}
|
||||
|
||||
@ -226,6 +230,35 @@ bool VtIo::IsUsingVt() const
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
void VtIo::RequestCursorPositionFromTerminal()
|
||||
{
|
||||
if (_lookingForCursorPosition)
|
||||
{
|
||||
// By delaying sending another DSR CPR until we received a response to the previous one,
|
||||
// we debounce our requests to the terminal. We don't want to flood it unnecessarily.
|
||||
_scheduleAnotherCPR = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_lookingForCursorPosition = true;
|
||||
_pVtInputThread->GetInputStateMachineEngine().CaptureNextCPR();
|
||||
|
||||
Writer writer{ this };
|
||||
writer.WriteUTF8("\x1b[6n"); // Cursor Position Report (DSR CPR)
|
||||
writer.Submit();
|
||||
}
|
||||
|
||||
void VtIo::_cursorPositionReportReceived()
|
||||
{
|
||||
_lookingForCursorPosition = false;
|
||||
|
||||
if (_scheduleAnotherCPR)
|
||||
{
|
||||
_scheduleAnotherCPR = false;
|
||||
RequestCursorPositionFromTerminal();
|
||||
}
|
||||
}
|
||||
|
||||
void VtIo::SetDeviceAttributes(const til::enumset<DeviceAttribute, uint64_t> attributes) noexcept
|
||||
{
|
||||
_deviceAttributes = attributes;
|
||||
|
||||
@ -61,6 +61,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
bool IsUsingVt() const;
|
||||
[[nodiscard]] HRESULT StartIfNeeded();
|
||||
|
||||
void RequestCursorPositionFromTerminal();
|
||||
void SetDeviceAttributes(til::enumset<DeviceAttribute, uint64_t> attributes) noexcept;
|
||||
til::enumset<DeviceAttribute, uint64_t> GetDeviceAttributes() const noexcept;
|
||||
void SendCloseEvent();
|
||||
@ -77,7 +78,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
};
|
||||
|
||||
[[nodiscard]] HRESULT _Initialize(const HANDLE InHandle, const HANDLE OutHandle, _In_opt_ const HANDLE SignalHandle);
|
||||
|
||||
void _cursorPositionReportReceived();
|
||||
void _uncork();
|
||||
void _flushNow();
|
||||
|
||||
@ -105,6 +106,7 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
|
||||
State _state = State::Uninitialized;
|
||||
bool _lookingForCursorPosition = false;
|
||||
bool _scheduleAnotherCPR = false;
|
||||
bool _closeEventSent = false;
|
||||
int _corked = 0;
|
||||
|
||||
|
||||
@ -89,14 +89,24 @@ static bool operator==(const Ss3ToVkey& pair, const Ss3ActionCodes code) noexcep
|
||||
return pair.action == code;
|
||||
}
|
||||
|
||||
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR) :
|
||||
_pDispatch(std::move(pDispatch)),
|
||||
_lookingForDSR(lookingForDSR),
|
||||
_doubleClickTime(std::chrono::milliseconds(GetDoubleClickTime()))
|
||||
InputStateMachineEngine::InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR) :
|
||||
_pDispatch{ std::move(pDispatch) },
|
||||
_capturedCPR{ std::move(capturedCPR) },
|
||||
_doubleClickTime{ std::chrono::milliseconds(GetDoubleClickTime()) }
|
||||
{
|
||||
THROW_HR_IF_NULL(E_INVALIDARG, _pDispatch.get());
|
||||
}
|
||||
|
||||
IInteractDispatch& InputStateMachineEngine::GetDispatch() const noexcept
|
||||
{
|
||||
return *_pDispatch.get();
|
||||
}
|
||||
|
||||
void InputStateMachineEngine::CaptureNextCPR() noexcept
|
||||
{
|
||||
_lookingForCPR = true;
|
||||
}
|
||||
|
||||
til::enumset<DeviceAttribute, uint64_t> InputStateMachineEngine::WaitUntilDA1(DWORD timeout) const noexcept
|
||||
{
|
||||
uint64_t val = 0;
|
||||
@ -413,13 +423,19 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
|
||||
// The F3 case is special - it shares a code with the DeviceStatusResponse.
|
||||
// If we're looking for that response, then do that, and break out.
|
||||
// Else, fall though to the _GetCursorKeysModifierState handler.
|
||||
if (_lookingForDSR)
|
||||
if (_lookingForCPR)
|
||||
{
|
||||
_pDispatch->MoveCursor(parameters.at(0), parameters.at(1));
|
||||
// Right now we're only looking for on initial cursor
|
||||
// position response. After that, only look for F3.
|
||||
_lookingForDSR = false;
|
||||
return true;
|
||||
_lookingForCPR = false;
|
||||
_capturedCPR();
|
||||
|
||||
const auto y = parameters.at(0).value();
|
||||
const auto x = parameters.at(1).value();
|
||||
|
||||
if (y > 0 && x > 0)
|
||||
{
|
||||
_pDispatch->MoveCursor(y, x);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
// Heuristic: If the hosting terminal used the win32 input mode, chances are high
|
||||
// that this is a CPR requested by the terminal application as opposed to a F3 key.
|
||||
@ -491,10 +507,6 @@ bool InputStateMachineEngine::ActionCsiDispatch(const VTID id, const VTParameter
|
||||
|
||||
_deviceAttributes.fetch_or(attributes.bits(), std::memory_order_relaxed);
|
||||
til::atomic_notify_all(_deviceAttributes);
|
||||
|
||||
// VtIo first sends a DSR CPR and then a DA1 request.
|
||||
// If we encountered a DA1 response here, the DSR request is definitely done now.
|
||||
_lookingForDSR = false;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@ -159,8 +159,10 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
class InputStateMachineEngine : public IStateMachineEngine
|
||||
{
|
||||
public:
|
||||
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, const bool lookingForDSR = false);
|
||||
InputStateMachineEngine(std::unique_ptr<IInteractDispatch> pDispatch, std::function<void()> capturedCPR = nullptr);
|
||||
|
||||
IInteractDispatch& GetDispatch() const noexcept;
|
||||
void CaptureNextCPR() noexcept;
|
||||
til::enumset<DeviceAttribute, uint64_t> WaitUntilDA1(DWORD timeout) const noexcept;
|
||||
|
||||
bool EncounteredWin32InputModeSequence() const noexcept override;
|
||||
@ -189,7 +191,8 @@ namespace Microsoft::Console::VirtualTerminal
|
||||
private:
|
||||
const std::unique_ptr<IInteractDispatch> _pDispatch;
|
||||
std::atomic<uint64_t> _deviceAttributes{ 0 };
|
||||
bool _lookingForDSR = false;
|
||||
bool _lookingForCPR = false;
|
||||
std::function<void()> _capturedCPR;
|
||||
bool _encounteredWin32InputModeSequence = false;
|
||||
bool _expectingStringTerminator = false;
|
||||
DWORD _mouseButtonState = 0;
|
||||
|
||||
@ -2,39 +2,26 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
#include "precomp.h"
|
||||
#include "WexTestClass.h"
|
||||
#include "../../inc/consoletaeftemplates.hpp"
|
||||
|
||||
#include "stateMachine.hpp"
|
||||
#include "InputStateMachineEngine.hpp"
|
||||
#include "ascii.hpp"
|
||||
#include "../input/terminalInput.hpp"
|
||||
#include "../../inc/unicode.hpp"
|
||||
#include "../../interactivity/inc/EventSynthesis.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <ascii.hpp>
|
||||
#include <consoletaeftemplates.hpp>
|
||||
#include <InputStateMachineEngine.hpp>
|
||||
#include <stateMachine.hpp>
|
||||
#include <unicode.hpp>
|
||||
|
||||
#include "../../../interactivity/inc/EventSynthesis.hpp"
|
||||
#include "../../../interactivity/inc/VtApiRedirection.hpp"
|
||||
#include "../../input/terminalInput.hpp"
|
||||
|
||||
using namespace WEX::Common;
|
||||
using namespace WEX::Logging;
|
||||
using namespace WEX::TestExecution;
|
||||
|
||||
namespace Microsoft
|
||||
namespace Microsoft::Console::VirtualTerminal
|
||||
{
|
||||
namespace Console
|
||||
{
|
||||
namespace VirtualTerminal
|
||||
{
|
||||
class InputEngineTest;
|
||||
class TestInteractDispatch;
|
||||
};
|
||||
};
|
||||
};
|
||||
class InputEngineTest;
|
||||
class TestInteractDispatch;
|
||||
}
|
||||
using namespace Microsoft::Console::VirtualTerminal;
|
||||
|
||||
bool IsShiftPressed(const DWORD modifierState)
|
||||
@ -413,7 +400,6 @@ void InputEngineTest::C0Test()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
Log::Comment(L"Sending 0x0-0x19 to parser to make sure they're translated correctly back to C-key");
|
||||
@ -513,7 +499,6 @@ void InputEngineTest::AlphanumericTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
Log::Comment(L"Sending every printable ASCII character");
|
||||
@ -563,7 +548,6 @@ void InputEngineTest::RoundTripTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
// Send Every VKEY through the TerminalInput module, then take the char's
|
||||
@ -626,7 +610,6 @@ void InputEngineTest::NonAsciiTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine.get());
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
Log::Comment(L"Sending various non-ascii strings, and seeing what we get out");
|
||||
|
||||
@ -679,11 +662,9 @@ void InputEngineTest::CursorPositioningTest()
|
||||
auto pfn = std::bind(&TestState::TestInputCallback, &testState, std::placeholders::_1);
|
||||
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
VERIFY_IS_NOT_NULL(dispatch.get());
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), true);
|
||||
VERIFY_IS_NOT_NULL(inputEngine.get());
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch), []() {});
|
||||
inputEngine->CaptureNextCPR();
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
Log::Comment(NoThrowString().Format(
|
||||
@ -724,7 +705,6 @@ void InputEngineTest::CSICursorBackTabTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
INPUT_RECORD inputRec;
|
||||
@ -752,7 +732,6 @@ void InputEngineTest::EnhancedKeysTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
// The following vkeys should be handled as enhanced keys
|
||||
@ -802,7 +781,6 @@ void InputEngineTest::SS3CursorKeyTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
// clang-format off
|
||||
@ -846,7 +824,6 @@ void InputEngineTest::AltBackspaceTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
INPUT_RECORD inputRec;
|
||||
@ -874,7 +851,6 @@ void InputEngineTest::AltCtrlDTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
INPUT_RECORD inputRec;
|
||||
@ -923,7 +899,6 @@ void InputEngineTest::AltIntermediateTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfnInputStateMachineCallback, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(stateMachine);
|
||||
testState._stateMachine = stateMachine.get();
|
||||
|
||||
// Write a Alt+/, Ctrl+e pair to the input engine, then take its output and
|
||||
@ -955,7 +930,6 @@ void InputEngineTest::AltBackspaceEnterTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
INPUT_RECORD inputRec;
|
||||
@ -1051,7 +1025,6 @@ void InputEngineTest::VerifySGRMouseData(const std::vector<std::tuple<SGR_PARAMS
|
||||
// Let's force it to a high value to make the double click tests pass.
|
||||
inputEngine->_doubleClickTime = std::chrono::milliseconds(1000);
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
SGR_PARAMS input;
|
||||
@ -1282,7 +1255,6 @@ void InputEngineTest::CtrlAltZCtrlAltXTest()
|
||||
auto dispatch = std::make_unique<TestInteractDispatch>(pfn, &testState);
|
||||
auto inputEngine = std::make_unique<InputStateMachineEngine>(std::move(dispatch));
|
||||
auto _stateMachine = std::make_unique<StateMachine>(std::move(inputEngine));
|
||||
VERIFY_IS_NOT_NULL(_stateMachine);
|
||||
testState._stateMachine = _stateMachine.get();
|
||||
|
||||
// This is a test for GH#4201. See that issue for more details.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user