Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor TerminalInput to return strings #15611

Merged

Conversation

lhecker
Copy link
Member

@lhecker lhecker commented Jun 27, 2023

TerminalInput is configurable, but almost entirely state-less.
As such it isn't helpful that it emits its output via a callback.
It makes tracing the flow of data harder purely from reading the code
and also raises uncertainty about when TerminalInput may generate
output. This commit makes it more robust by having TerminalInput
simply return its data. Furthermore, it returns that data as a string
instead of converting back and forth between text and IInputEvent.

This change will help me make conhost's InputBuffer implementation
leaner and help me confidently make more difficult changes to it
with the goal to improve our Unicode support/correctness.

Validation Steps Performed

  • Windows Terminal produces correct results with showkey -a

@lhecker lhecker added the Area-CodeHealth Issues related to code cleanliness, linting, rules, warnings, errors, static analysis, etc. label Jun 27, 2023
@@ -24,8 +24,6 @@
#include "../buffer/out/search.h"
#include "../buffer/out/TextColor.h"

#include <til/ticket_lock.h>
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR is a little less bad if you suppress whitespace changes: https://github.com/microsoft/terminal/pull/15611/files?w=1

namespace Microsoft::Console::VirtualTerminal
{
class TerminalInput final
{
public:
TerminalInput(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn);
using StringType = std::wstring;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The choice of std::wstring is inoptimal because it'll heap-allocate on every single key press. I figured it doesn't really matter though, because we already do that and more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you elected not to use memory_buffer? aw

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, will it? std::wstring absolutely has support for storing short strings inside of it. Up to 16 bytes (so for a wstring, 8 chars)

image

BUF_SIZE is 16/sizeof(value_type) 😄

Copy link
Member Author

@lhecker lhecker Jun 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's 7 chars due to the terminating null character, but the win32 key sequence is longer than that. (I mean it's also longer than 8 characters...)

I'm using std::wstring because memory_buffer is not comparable, which makes it harder to use for unit tests.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh, win32 input sequences. bah.

Copy link
Member

@DHowett DHowett Jun 27, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add an operator== to the global namespace for memory_buffer?

is it comparable in fmt 9?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I know it isn't. I'd probably just use til::small_vector instead, but it lacks an append(some_range) method. I think we should improve on this at some other time. It realistically doesn't make any difference in our overall application.

@lhecker lhecker force-pushed the dev/lhecker/8000-TerminalInput-cleanup branch from 625f121 to 376a993 Compare June 27, 2023 16:18
@lhecker lhecker force-pushed the dev/lhecker/8000-TerminalInput-cleanup branch from 376a993 to f73fe75 Compare June 27, 2023 16:25
src/terminal/parser/ut_parser/InputEngineTest.cpp Outdated Show resolved Hide resolved
Comment on lines +687 to +689
if (const auto out = _termInput.HandleFocus(focused))
{
_HandleTerminalInputCallback(*out);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this pattern is different from the one in TerminalCore, since it takes *out instead of out. Should we converge?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm... No, I don't think so. I feel like the _WriteBuffer loop makes more sense this way.

namespace Microsoft::Console::VirtualTerminal
{
class TerminalInput final
{
public:
TerminalInput(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn);
using StringType = std::wstring;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you elected not to use memory_buffer? aw

namespace Microsoft::Console::VirtualTerminal
{
class TerminalInput final
{
public:
TerminalInput(_In_ std::function<void(std::deque<std::unique_ptr<IInputEvent>>&)> pfn);
using StringType = std::wstring;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wait, will it? std::wstring absolutely has support for storing short strings inside of it. Up to 16 bytes (so for a wstring, 8 chars)

image

BUF_SIZE is 16/sizeof(value_type) 😄


for (const auto& wch : text)
{
_storage.push_back(std::make_unique<KeyEvent>(true, 1ui16, 0ui16, 0ui16, wch, 0));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

now for THIS we are gonna heap alloc every time but WHATEVER I HATE IT TOO

src/terminal/input/mouseInput.cpp Outdated Show resolved Hide resolved
src/terminal/input/terminalInput.cpp Outdated Show resolved Hide resolved
src/terminal/input/terminalInput.cpp Outdated Show resolved Hide resolved
if (til::is_leading_surrogate(ch))
{
if (_leadingSurrogate.has_value())
{
// we already were storing a leading surrogate but we got another one. Go ahead and send the
// saved surrogate piece and save the new one
const auto formatted = wil::str_printf<std::wstring>(L"%I32u", _leadingSurrogate.value());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what in blazes? so we would literally just send the raw value of the leading surrogate as an integer?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a behavior change, but i can't see anyone relying on it.
the code on the right just sends the leading surrogate as a character instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

noting of course that this is the broken case where we got a Lead + Lead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I was quite confused as well. I looked up where it came from and it's been there ever since it got introduced with the WPF PR.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made it so that it now discard invalid surrogate pairs entirely. It's rather unexpected that our input handling code would have to deal with unpaired surrogate pairs anyways. Discarding them is probably about as good as sending U+FFFD.

src/terminal/input/terminalInput.cpp Show resolved Hide resolved
@github-actions

This comment has been minimized.

@lhecker lhecker added the AutoMerge Marked for automatic merge by the bot when requirements are met label Jun 27, 2023
TerminalInput() = delete;
TerminalInput(const TerminalInput& old) = default;
TerminalInput(TerminalInput&& moved) = default;
static_assert(StringType{} == StringType{});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need this?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah woops that's a leftover from when I was testing which StringType would work with the unit tests. I needed a class that as a == operator. I'll remove it. 😅

return true;
// Note that we must return an empty string here to imply that we've handled
// the event, otherwise the key press can still end up being submitted.
return MakeOutput({});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be clear MakeOutput({}) returns the empty string, whereas MakeUnhandled() returns OutputType{}, which is... what exactly? A null string?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah kind of. It's like returning null vs. "" in JS. null in this case means "we didn't handle it", where "" means we did handle it and the result is simply an empty string.

Comment on lines -402 to -411
if (success)
{
_SendInputSequence(sequence);
success = true;
}
if (_inputMode.any(Mode::ButtonEventMouseTracking, Mode::AnyEventMouseTracking))
{
_mouseInputState.lastPos.x = position.x;
_mouseInputState.lastPos.y = position.y;
_mouseInputState.lastButton = button;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Didn't this get lost in the change?

Copy link
Member Author

@lhecker lhecker Jun 30, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code change is a little (very) confusing tbh. This if condition has moved up in the scope, so that I could use early returns and remove the local success variable. It's fine to move the if condition up because none of the called functions depend on _mouseInputState (they're all 3 static functions).

@microsoft-github-policy-service microsoft-github-policy-service bot deleted the dev/lhecker/8000-TerminalInput-cleanup branch June 30, 2023 16:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-CodeHealth Issues related to code cleanliness, linting, rules, warnings, errors, static analysis, etc. AutoMerge Marked for automatic merge by the bot when requirements are met
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants