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

Pasting into Elvish is slow #1744

Open
lilacse opened this issue Jan 8, 2024 · 13 comments
Open

Pasting into Elvish is slow #1744

lilacse opened this issue Jan 8, 2024 · 13 comments

Comments

@lilacse
Copy link

lilacse commented Jan 8, 2024

Currently, Elvish seems to try to check if the current input is valid on every character that is pasted into the prompt and update the prompt input's highlighting accordingly. Pasting into Elvish hence involves an "animation" of watching letters pop up one by one after pressing Ctrl-V, instead of a paste that happens instantaneously like pasting into Bash does.

This is significantly more noticeable on older computers with lesser resources (I mainly get this experience on a laptop that runs a 7th gen Intel), although the effect can still be visible on newer ones if the pasted text is long enough. The terminal emulator used doesn't seem to affect this behaviour.

Wondering if it is possible to fix or workaround it? Since this behaviour makes situations such as pasting long commands somewhat annoying. It is also impossible to cancel the paste halfway, as the unfinished input will still be (slowly) written to the prompt after using Ctrl-C to clear it.

@krader1961
Copy link
Contributor

What version of Elvish are you using? What does elvish -version output? Bracketed paste support was added in 2016 and should keep what you describe from happening. I don't have an old system to test on but pasting a 2KB block of Elvish on my regular system doesn't result in anything resembling the behavior you describe.

@krader1961
Copy link
Contributor

In fact, I used the screen command to take advantage of its ability to slow down pasting and see the behavior you're describing. But that is because screen doesn't support bracketed paste mode. So it seems likely you're using a very old version of Elvish or your terminal doesn't support bracketed pasting.

@lilacse
Copy link
Author

lilacse commented Jan 9, 2024

It was on 0.20.0-dev.0.20230826021805-1c0cffbfed89+official, but to be sure I downloaded the latest HEAD again. So, now I have 0.20.0-dev.0.20240108164435-9112eb1ab2f4+official.

I tried poking around it a little more, it seems like it isn't that slow with built-in commands such as echo or put, but with external commands it becomes really noticeable.

A quick screen recording,

2024-01-09.09-43-48.mp4

This is with Windows Terminal (1.18.3181.0), with a Starship prompt. A little digging around and it seems like Windows Terminal should support bracketed pasting? (microsoft/terminal#395)

By the way, after rewatching my own recording for a dozen of times, it seems like Elvish isn't evaluating if the input is valid on every character. It just takes in characters slowly for some reason.

Edit: This is reproduceable in the integrated terminal in VSCode too, on the same computer.

@iandol
Copy link
Contributor

iandol commented Jan 9, 2024

I tested on macOS + kitty + elvish nightly and it is instant (grep with 2743 characters pasted from an lorum ipsum). I also tested on a Raspberry Pi 4 (LXTerminal) which should be much less powerful than your system and there is a tiny pause for 2743 characters, but broadly identical with zsh. Try install alacritty or kitty on windows and seeing there?

@krader1961
Copy link
Contributor

krader1961 commented Jan 9, 2024

A little digging around and it seems like Windows Terminal should support bracketed pasting? (microsoft/terminal#395)

I read that issue and the last comment was someone saying it doesn't appear to work. There is also no reason that "with external commands it becomes really noticeable". I suspect that is a quirk of your environment. Elvish syntax highlighting speed should be independent of whether a command is a builtin or external. With the caveat that if the command isn't a builtin Elvish has to then traverse PATH to see if there is an external command with that name. On any system that isn't incredibly slow that should be fast enough as to be indistinguishable from the highlighting for a builtin.

That you are running Elvish on Windows is something you should have stated in the original problem statement (along with the Elvish version). I am reasonably confident that if you enable the SSH service and connect to your Windows system, via the ssh command, from a terminal on a Linux/BSD/macOS system you won't see the problem. In other words, I'm reasonably confident the problem is in the Windows terminal emulator or its ConHost/ConPTY subsystem.

@krader1961
Copy link
Contributor

Coincidentally, I read this blog post a couple of days ago that was written by a Microsoft software engineer: https://devblogs.microsoft.com/commandline/windows-command-line-introducing-the-windows-pseudo-console-conpty/

TL;DR: The Microsoft support for things the Unix community pretty much takes for granted, like bracketed paste mode, is likely to have bugs. It is pretty unlikely that Elvish will implement workarounds for bugs of that nature.

@krader1961
Copy link
Contributor

I am reasonably confident that if you enable the SSH service and connect to your Windows system, via the ssh command, from a terminal on a Linux/BSD/macOS system you won't see the problem.

Note that is how I interact with my Windows 11 VM 99% of the time. And I just tested that scenario and was able to recreate your problem. So even using an SSH connection from a terminal emulator (iTerm2 on macOS) I know handles this correctly doesn't work. Which means the Windows ConHost/ConPTY subsystem is still broken.

@lilacse
Copy link
Author

lilacse commented Jan 9, 2024

Try install alacritty or kitty on windows and seeing there?

Just tried with Alacritty, no luck. This really looks like a Windows-specific issue now, lol.

With the caveat that if the command isn't a builtin Elvish has to then traverse PATH to see if there is an external command with that name. On any system that isn't incredibly slow that should be fast enough as to be indistinguishable from the highlighting for a builtin.

This PC does have about two dozen directories in PATH, but I agree that it has to be something to do with something else of this PC. Perhaps an antivirus software or something. Or, could it be that Windows is less happy with the method that is used to traverse through PATH?

I tried running the Linux binary for Elvish under WSL2 too (also in Windows Terminal), and all pastes are instant. Looks like there's really a bug in ConHost/ConPTY... I'll try opening an issue in the Windows Terminal repo I think.

Thanks everyone for the help!

@lhecker
Copy link

lhecker commented Jan 10, 2024

Hey! I work on Windows Terminal. The reason this occurs is because elvish needs to set ENABLE_VIRTUAL_TERMINAL_INPUT on the input handle, as otherwise we don't send VT escape sequences to the console application. (If we didn't we'd break old Win32 console applications.)

In other words, this is missing a windows.ENABLE_VIRTUAL_TERMINAL_INPUT:

inMode = windows.ENABLE_WINDOW_INPUT |
windows.ENABLE_MOUSE_INPUT | windows.ENABLE_PROCESSED_INPUT

There seems to be another bug somewhere though, as I can see [200~ and [201~ in my prompt. But it does seem to work in general, as the pasted text appears instantly. It appears as if reader_unix.go contains the logic to decode those VT sequences and you should be able to merge the two code bases together. (In fact it may be necessary to do so.) If you don't need any INPUT_RECORDs at some point, please feel free to read the input with regular ReadFile calls on the input handle as this will get you a regular stream of UTF8 text, just like on UNIX. You then won't be able to receive WINDOW_BUFFER_SIZE_EVENT records anymore, however.

As an aside, I would heavily recommend calling ReadConsoleInput with a buffer size >1 here:

var buf [1]ewindows.InputRecord

Since it uses unbuffered pipes, each call incurs a syscall and cross-process communication. On the latest conhost/OpenConsole version from the main branch for instance, there's barely any difference between reading 1 INPUT_RECORD and reading 1000 - both cost about 50us on my system. I believe it's very similar (but on a smaller magnitude) for UNIX systems. I would suggest reading data in chunks on both systems and then reading bytes off of the cached chunk one by one. That would be very similar to how the bufio package does it.

@xiaq
Copy link
Member

xiaq commented Jan 10, 2024

@lhecker Thanks! There are some reasons why the code works like this though:

  • IIUC Windows's ReadConsoleInput is more capable in representing keys with modifiers than VT sequences (which can't distinguish Tab from Ctrl-I, Enter from Ctrl-J, etc.), which is why I didn't use windows.ENABLE_VIRTUAL_TERMINAL_INPUT. It makes sense to give users access to what the platform offers rather than forcing them to the least common denominator.

  • The reason Elvish reads one input at one time is that Elvish tries not to "eat" any inputs that should go to other programs. For example, if "catfoo" are sent in quick succession, Elvish shouldn't consume the foo part of the input.

    The overhead of doing this almost never matters in the scale of human-machine interactions. Even in this case, the overhead likely doesn't come from the syscall overhead - 1s/50us=20k, so Elvish should still accept 20k characters per second - but the fact that Elvish is trying to highlight the code after every input. This can be worked around by introducing a short debounce.

    Also in general, highlighting on Windows is much slower than other platforms because (1) reading disks tends to be slower on Windows, and (2) %PATH% on Windows tends to get clogged up by all sorts of random programs. Caching is probably the only way forward to properly fix this.

I don't have my Windows environment right now, and will get back to this when I do in a few days.

@xiaq xiaq closed this as completed Jan 10, 2024
@github-project-automation github-project-automation bot moved this from ❓Triage to ✅Done in All Elvish issues Jan 10, 2024
@xiaq
Copy link
Member

xiaq commented Jan 10, 2024

Oops, closed this by accident. Reopening

@xiaq xiaq reopened this Jan 10, 2024
@github-project-automation github-project-automation bot moved this from ✅Done to ❓Triage in All Elvish issues Jan 10, 2024
@krader1961
Copy link
Contributor

@lhecker, Does Windows ConPTY VT mode support extended modifier keys? If it does that would address the first point in @xiaq's comment since Elvish already supports extended modifier keys on Unix platforms. I found issues such as microsoft/terminal#4334 and this 4 year old ConPTY design document but couldn't readily determine whether Unix style VT modifier keys is supported by Windows and, if so, when that support was introduced.

Yes, I could write some code to test the hypothesis but since I don't use Windows other than for testing programs like Elvish on Windows it is easier to simply ask for clarification. :-)

@lhecker
Copy link

lhecker commented Jan 15, 2024

Yes, the document you found describes our primary way of doing it right now. It's the win32-input-mode introduced here: microsoft/terminal#6309
It's essentially a lossless 1:1 translation of KEY_EVENTs as VT sequences. However, it was initially introduced primarily for the communication between components that make up ConPTY. For general usage, it has a number of bugs that make it somewhat difficult to use. As an example, when you paste text you won't receive the text as is, but rather with each individual character encoded in win32-input-mode, including synthesized Ctrl and Alt key presses. This blows up a stream of average English text by roughly 50x and makes it somewhat slow to paste larger amounts. Additionally, there are bugs where VT sequences themselves would get double-encoded via win32-input-mode, among others the \x1b[200~ and \x1b[201~ bracketed paste sequences. Pasting "A" for instance results in

␛[0;0;27;1;0;1_       ESC
␛[0;0;91;1;0;1_       [
␛[0;0;50;1;0;1_       2
␛[0;0;48;1;0;1_       0
␛[0;0;48;1;0;1_       0
␛[0;0;126;1;0;1_      ~
␛[16;42;0;1;16;1_     Shift down
␛[65;30;65;1;16;1_    Shift+A down
␛[65;30;65;0;16;1_    Shift+A up
␛[16;42;0;0;0;1_      Shift up
␛[0;0;27;1;0;1_       ESC
␛[0;0;91;1;0;1_       [
␛[0;0;50;1;0;1_       2
␛[0;0;48;1;0;1_       0
␛[0;0;49;1;0;1_       1
␛[0;0;126;1;0;1_      ~

We expect to resolve these issues this year.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: ❓Triage
Development

No branches or pull requests

5 participants