diff --git a/source/platform/terminal.cpp b/source/platform/terminal.cpp index 78ad6e08..7e089c82 100644 --- a/source/platform/terminal.cpp +++ b/source/platform/terminal.cpp @@ -741,6 +741,11 @@ class Win32InputModeUnwrapper : public InputGetter InputGetter ∈ InputState &state; + enum { maxSize = 31 }; + + ushort ungetSize {0}; + short ungetBuffer[maxSize]; + public: Win32InputModeUnwrapper(InputGetter &aIn, InputState &aState) noexcept : @@ -750,23 +755,31 @@ class Win32InputModeUnwrapper : public InputGetter int get() noexcept override { + if (ungetSize > 0) + return ungetBuffer[--ungetSize]; + GetChBuf buf(in); CSIData csi; TEvent ev {}; + // If we get a win32-input-mode event with no scan code and + // a single-byte character, take just that character. if ( buf.get() == '\x1B' && buf.get() == '[' && csi.readFrom(buf) && csi.terminator == '_' && parseWin32InputModeKey(csi, ev, state) == Accepted && ev.keyDown.charScan.scanCode == 0 && ev.keyDown.textLength == 1 ) - return ev.keyDown.text[0]; + return (uchar) ev.keyDown.text[0]; buf.reject(); return -1; } - void unget(int) noexcept override + void unget(int key) noexcept override { - // Do nothing. It is desirable not to reject win32-input-mode events, - // as that would just spill escape sequences into the input queue. + // We could reconstruct the original win32-input-mode event and call + // 'in.unget()', but there is no need for that. However, we still need + // to be able to temporarily store characters returned by 'get()'. + if (ungetSize < maxSize) + ungetBuffer[ungetSize++] = (short) key; } }; diff --git a/test/platform/terminal.test.cpp b/test/platform/terminal.test.cpp index 45e29b0b..43b26363 100644 --- a/test/platform/terminal.test.cpp +++ b/test/platform/terminal.test.cpp @@ -5,6 +5,7 @@ #include #include "terminal.test.h" +#include static bool operator==(const KeyDownEvent &a, const KeyDownEvent &b) { @@ -53,40 +54,74 @@ TEST(TermIO, ShouldNormalizeKeys) TEST(TermIO, ShouldReadWin32InputModeKeys) { - static const TestCase testCases[] = + static const TestCase> testCases[] = { - {"[65;30;65;1;16;1_", {Accepted, keyDownEv(0x1e41, kbShift, "A")}}, - {"[65;30;65;1;16_", {Accepted, keyDownEv(0x1e41, kbShift, "A")}}, - {"[16;42;0;0;0;1_", {Ignored}}, - {"[65;30;97;1;0;1_", {Accepted, keyDownEv(0x1e61, 0x0000, "a")}}, - {"[65;30;97;1_", {Accepted, keyDownEv(0x1e61, 0x0000, "a")}}, - {"[112;59;0;1;8;1_", {Accepted, keyDownEv(kbCtrlF1, kbLeftCtrl, "")}}, - {"[112;59;;1;8_", {Accepted, keyDownEv(kbCtrlF1, kbLeftCtrl, "")}}, - {"[112;59;0;0;8;1_", {Ignored}}, + {"\x1B[65;30;65;1;16;1_", {keyDownEv(0x1e41, kbShift, "A")}}, + {"\x1B[65;30;65;1;16_", {keyDownEv(0x1e41, kbShift, "A")}}, + {"\x1B[16;42;0;0;0;1_", {}}, + {"\x1B[65;30;97;1;0;1_", {keyDownEv(0x1e61, 0x0000, "a")}}, + {"\x1B[65;30;97;1_", {keyDownEv(0x1e61, 0x0000, "a")}}, + {"\x1B[112;59;0;1;8;1_", {keyDownEv(kbCtrlF1, kbLeftCtrl, "")}}, + {"\x1B[112;59;;1;8_", {keyDownEv(kbCtrlF1, kbLeftCtrl, "")}}, + {"\x1B[112;59;0;0;8;1_", {}}, // https://github.com/microsoft/terminal/issues/15083 - // SGR mouse event - {"[0;0;27;1;0;1_" - "\x1B[0;0;91;1;0;1_" - "\x1B[0;0;60;1;0;1_" - "\x1B[0;0;48;1;0;1_" - "\x1B[0;0;59;1;0;1_" - "\x1B[0;0;53;1;0;1_" - "\x1B[0;0;50;1;0;1_" - "\x1B[0;0;59;1;0;1_" - "\x1B[0;0;49;1;0;1_" - "\x1B[0;0;50;1;0;1_" - "\x1B[0;0;77;1;0;1_", {Accepted, mouseEv({51, 11}, 0x0000, 0x0000, mbLeftButton, 0x0000)}}, + { // SGR mouse event + "\x1B[0;0;27;1;0;1_" + "\x1B[0;0;91;1;0;1_" + "\x1B[0;0;60;1;0;1_" + "\x1B[0;0;48;1;0;1_" + "\x1B[0;0;59;1;0;1_" + "\x1B[0;0;53;1;0;1_" + "\x1B[0;0;50;1;0;1_" + "\x1B[0;0;59;1;0;1_" + "\x1B[0;0;49;1;0;1_" + "\x1B[0;0;50;1;0;1_" + "\x1B[0;0;77;1;0;1_", + {mouseEv({51, 11}, 0x0000, 0x0000, mbLeftButton, 0x0000)}, + }, + { // Paste event + "\x1B[17;29;0;1;8;1_" // Ctrl (press) + "\x1B[16;42;0;1;24;1_" // Shift (press) + "\x1B[0;0;27;1;0;1_" // \x1B[200~ (bracketed paste begin) + "\x1B[0;0;91;1;0;1_" + "\x1B[0;0;50;1;0;1_" + "\x1B[0;0;48;1;0;1_" + "\x1B[0;0;48;1;0;1_" + "\x1B[0;0;126;1;0;1_" + "\x1B[65;30;97;1;0;1_" // 'a' (press) + "\x1B[65;30;97;0;0;1_" // 'a' (release) + "\x1B[0;0;27;1;0;1_" // \x1B[201~ (bracketed paste end) + "\x1B[0;0;91;1;0;1_" + "\x1B[0;0;50;1;0;1_" + "\x1B[0;0;48;1;0;1_" + "\x1B[0;0;49;1;0;1_" + "\x1B[0;0;126;1;0;1_" + "\x1B[86;47;22;0;24;1_" // Ctrl+Shift+V (release) + "\x1B[17;29;0;0;16;1_" // Shift (release) + "\x1B[16;42;0;0;0;1_", // Ctrl (release) + {keyDownEv(0x1e61, kbPaste, "a")}, + }, }; for (auto &testCase : testCases) { StrInputGetter in(testCase.input); - GetChBuf buf(in); - ParseResultEvent actual {}; + std::vector actual {}; InputState state {}; - actual.parseResult = TermIO::parseEscapeSeq(buf, actual.ev, state); + while (true) + { + GetChBuf buf(in); + TEvent ev {}; + ParseResult result = TermIO::parseEvent(buf, ev, state); + if (state.bracketedPaste && ev.what == evKeyDown) + ev.keyDown.controlKeyState |= kbPaste; + + if (result == Accepted) + actual.push_back(ev); + else if (result == Rejected) + break; + } expectResultMatches(actual, testCase); - EXPECT_EQ(in.bytesLeft(), 0); } } diff --git a/test/platform/terminal.test.h b/test/platform/terminal.test.h index 7e45fd0c..cadd4d84 100644 --- a/test/platform/terminal.test.h +++ b/test/platform/terminal.test.h @@ -3,6 +3,54 @@ #include +inline bool operator==(const TEvent &a, const TEvent &b) +{ + if (a.what != b.what) + return false; + if (a.what == evNothing) + return true; + if (a.what == evKeyDown) + return + a.keyDown.keyCode == b.keyDown.keyCode && + a.keyDown.controlKeyState == b.keyDown.controlKeyState && + a.keyDown.getText() == b.keyDown.getText(); + if (a.what == evMouse) + return + a.mouse.where == b.mouse.where && + a.mouse.eventFlags == b.mouse.eventFlags && + a.mouse.controlKeyState == b.mouse.controlKeyState && + a.mouse.buttons == b.mouse.buttons && + a.mouse.wheel == b.mouse.wheel; + abort(); +} + +inline std::ostream &operator<<(std::ostream &os, const TEvent &ev) +{ + os << "{"; + if (ev.what == evKeyDown) + { + os << "{"; + printKeyCode(os, ev.keyDown.keyCode); + os << "}, {"; + printControlKeyState(os, ev.keyDown.controlKeyState); + os << "}, '" << ev.keyDown.getText() << "'"; + } + else if (ev.what == evMouse) + { + os << "(" << ev.mouse.where.x << "," << ev.mouse.where.y << ")"; + os << ", "; + printMouseEventFlags(os, ev.mouse.eventFlags); + os << ", "; + printControlKeyState(os, ev.mouse.controlKeyState); + os << ", "; + printMouseButtonState(os, ev.mouse.buttons); + os << ", "; + printMouseWheelState(os, ev.mouse.wheel); + } + os << "}"; + return os; +} + namespace tvision { @@ -41,29 +89,13 @@ struct ParseResultEvent TEvent ev; }; -static bool operator==(const ParseResultEvent &a, const ParseResultEvent &b) +inline bool operator==(const ParseResultEvent &a, const ParseResultEvent &b) { if (a.parseResult != b.parseResult) return false; if (a.parseResult == Ignored) return true; - if (a.ev.what != b.ev.what) - return false; - if (a.ev.what == evNothing) - return true; - if (a.ev.what == evKeyDown) - return - a.ev.keyDown.keyCode == b.ev.keyDown.keyCode && - a.ev.keyDown.controlKeyState == b.ev.keyDown.controlKeyState && - a.ev.keyDown.getText() == b.ev.keyDown.getText(); - if (a.ev.what == evMouse) - return - a.ev.mouse.where == b.ev.mouse.where && - a.ev.mouse.eventFlags == b.ev.mouse.eventFlags && - a.ev.mouse.controlKeyState == b.ev.mouse.controlKeyState && - a.ev.mouse.buttons == b.ev.mouse.buttons && - a.ev.mouse.wheel == b.ev.mouse.wheel; - abort(); + return a.ev == b.ev; } inline std::ostream &operator<<(std::ostream &os, const ParseResultEvent &p) @@ -77,28 +109,7 @@ inline std::ostream &operator<<(std::ostream &os, const ParseResultEvent &p) { os << "Accepted, {"; printEventCode(os, p.ev.what); - os << ", {"; - if (p.ev.what == evKeyDown) - { - os << "{"; - printKeyCode(os, p.ev.keyDown.keyCode); - os << "}, {"; - printControlKeyState(os, p.ev.keyDown.controlKeyState); - os << "}, '" << p.ev.keyDown.getText() << "'"; - } - else if (p.ev.what == evMouse) - { - os << "(" << p.ev.mouse.where.x << "," << p.ev.mouse.where.y << ")"; - os << ", "; - printMouseEventFlags(os, p.ev.mouse.eventFlags); - os << ", "; - printControlKeyState(os, p.ev.mouse.controlKeyState); - os << ", "; - printMouseButtonState(os, p.ev.mouse.buttons); - os << ", "; - printMouseWheelState(os, p.ev.mouse.wheel); - } - os << "}}"; + os << ", " << p.ev << "}"; } } os << "}";