PSReadLine icon indicating copy to clipboard operation
PSReadLine copied to clipboard

Cannot register key handlers for the same key with different modifiers

Open akbyrd opened this issue 2 years ago • 9 comments

Prerequisites

  • [X] Write a descriptive title.
  • [X] Make sure you are able to repro it on the latest released version
  • [X] Search the existing issues, especially the pinned issues.

Exception report

N/A

Screenshot

image

Environment data

PS Version: 7.3.2
PS HostName: ConsoleHost (Windows Terminal)
PSReadLine Version: 2.2.6
PSReadLine EditMode: Windows
OS: 10.0.19041.1 (WinBuild.160101.0800)
BufferWidth: 178
BufferHeight: 56

Steps to reproduce

Paste the following text:

Function Inject-Command([String] $command) {
	[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
	[Microsoft.PowerShell.PSConsoleReadLine]::Insert($command)
	[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}

Set-PSReadLineKeyHandler -Chord "Ctrl+," -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Alt+," -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Shift+," -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Ctrl+Alt+." -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Ctrl+Shift+," -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Alt+Shift+," -ScriptBlock { Inject-Command("echo success") }
Set-PSReadLineKeyHandler -Chord "Ctrl+Alt+Shift+," -ScriptBlock { Inject-Command("echo success") }
  • Notice the first 4 handlers are able to be registered
  • Notice the remaining 3 handlers are not able to be registered
  • Type ,
  • Notice , is replaced with echo success (this handler was never registered)

Expected behavior

  • Each key handler should be able to be registered
  • Unmodified , should not have a handler registered

Actual behavior

  • Each key handler is not able to be able to be registered
  • Unmodified , has a handler registered

akbyrd avatar Jan 29 '23 00:01 akbyrd

The vast majority of these key combinations aren't even able to be received by PSReadLine, just on Windows (and experimenting on macOS/Linux is going to reveal this is even more complicated), try [Console]::ReadKey() and then execute those chords, and see what happens:

  • Ctrl+, is intercepted by the OS and opens up the Terminal app's settings
  • Alt-, ought to work, it comes through as Alt-,
  • Shift-, might work, assuming in PSReadLine we understand to translate what comes through (Shift-<) as equivalent
  • Ctrl+Alt+. is weird, it reports control and alt modifiers with OemPeriod key, but no KeyChar, so PSReadLine is presumably confused by that
  • Ctrl+Shift+, is again intercepted by the OS, no idea why but Windows opened up a prompt to "select an app to open this .json file" (I have no JSON file, I'm just in the Windows Terminal, pressing that key combo, maybe it's opening the Terminal app's settings file?)
  • Alt+Shift+, might work, it comes through as Alt-Shift-< (like Alt-, but with the shift pre-translating the key to < like Shift-, did
  • Ctrl+Alt+Shift+, is weird, like Ctrl+Alt+. it reports control, alt, and shift modifiers with OemComma key, but again no KeyChar and I don't know if PSReadLine will handle that

In short, control characters have a long history, and I would not expect random combinations of Ctrl and keys other than alpha-numeric to necessarily behave as you might expect, they're liable to get intercepted at different levels of the stack (the keyboard drive itself, the OS, the terminal emulator, .NET's own ReadKey implementation, etc.).

andyleejordan avatar Jan 30 '23 19:01 andyleejordan

Yea, there's a lot fiddly stuff going on here.

Here's a more minimal case to examine. I'm launching PowerShell directly now to eliminate Windows Terminal from the equation. I've also removed my PowerShell profile

Register these 2 handlers, then press Ctrl+,

Set-PSReadLineKeyHandler -Chord "Ctrl+," -ScriptBlock { Inject-Command("'c,'") }
Set-PSReadLineKeyHandler -Chord "Ctrl+Shift+," -ScriptBlock { Inject-Command("'cs,'") }

The expectation is that Ctrl+, is bound to the first handler. However, the second handler is executed.

image

As far as I can tell, Ctrl+, is not being intercepted by anyone else.

akbyrd avatar Jan 30 '23 20:01 akbyrd

@akbyrd try this experiment in PowerShell:

@andyschwartc421 ~
> [Console]::ReadKey()
,
KeyChar      Key Modifiers
-------      --- ---------
      , OemComma         0

Like, directly execute [Console]::ReadKey() and then test your key chords. Neither Ctrl+, nor Ctrl+Shift+, were getting sent to ReadKey() for me, I had to press just ,. If ReadKey can't get it, PSReadLine definitely can't get it.

andyleejordan avatar Jan 30 '23 21:01 andyleejordan

ReadKey sees all three chords on my machine.

PS C:\Windows\System32> [Console]::ReadKey()
,
KeyChar      Key Modifiers
-------      --- ---------
      , OemComma         0

PS C:\Windows\System32> [Console]::ReadKey()

KeyChar      Key Modifiers
-------      --- ---------
       OemComma   Control

PS C:\Windows\System32> [Console]::ReadKey()

KeyChar      Key      Modifiers
-------      ---      ---------
       OemComma Shift, Control

I didn't explicitly state it in my previous post, but the Ctrl+, handler works fine until the Ctrl+Shift+, handler is registered. It's only once both are registered that it misbehaves.

akbyrd avatar Jan 30 '23 21:01 akbyrd

Maybe I am missing something but is it odd that for the last two you posted it does not show the , in the KeyChar? Does that mean ReadKey() is having issues getting it? @andschwa

StevenBucher98 avatar Mar 27 '23 21:03 StevenBucher98

It is odd, and it means we (and the users) can't depend on KeyChar nor Modifiers in these cases. It looks like Key is at least consistently OemComma, but yeah what .NET is returning to is via ReadKey is probably the root of the problem.

andyleejordan avatar Mar 28 '23 20:03 andyleejordan

It is odd, and it means we (and the users) can't depend on KeyChar nor Modifiers in these cases. It looks like Key is at least consistently OemComma, but yeah what .NET is returning to is via ReadKey is probably the root of the problem.

KeyChar shows a representation of the character, but it's not always particularly relevant. iirc for anything with a direct representation (like when Ctrl is involved) it just shows default(char).

Modifiers should be accurate on Windows though. I believe Jason made Shift essentially ignored and instead it looks for the character that would be created. So Ctrl + Shift + , is no different from Ctrl + ,, but Ctrl + < gives the desired result (feel free to correct me if I'm wrong there @lzybkr).

SeeminglyScience avatar Mar 29 '23 18:03 SeeminglyScience

In summary: key chords are a mess.

andyleejordan avatar Mar 30 '23 17:03 andyleejordan

@SeeminglyScience - mostly accurate IIRC. On non-Windows, you couldn't reliably get the shift state required to generate specific keystroke, so I moved towards a world where you wouldn't specify the shift state. I think there were also problems with consistency on Windows even when you could count on the shift state - you might have a profile that works with one keyboard layout but not another.

In hindsight I might have gone too far. The world seems to expect that Ctrl+C means Ctrl+c - so maybe the shift state should have been required for alphabetic characters.

lzybkr avatar Mar 30 '23 23:03 lzybkr