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

How to detect shift shift key in VSCode? #5280

Closed
kasecato opened this issue Apr 14, 2016 · 90 comments · Fixed by #115190
Closed

How to detect shift shift key in VSCode? #5280

kasecato opened this issue Apr 14, 2016 · 90 comments · Fixed by #115190
Assignees
Labels
feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders keybindings VS Code keybinding issues verification-needed Verification of issue is requested verified Verification succeeded
Milestone

Comments

@kasecato
Copy link
Contributor

  • VSCode Version: 1.0.0
  • OS Version: OS X El Capitan 10.11.4

Steps to Reproduce:

I'm trying to bind quick open (ctrl+p) command to shift shift (shift x2) keys like IntelliJ IDEA in VS Code Extension (TypeScript).

But vscode.commands.registerCommand('type', (args) => args.text) can't detect shfit key.

How can I detect shift shift key?

// VSCode v0.10.12 or higher (not working <= v0.10.11)
'use strict';
import * as vscode from 'vscode';

export function activate(context: vscode.ExtensionContext) {

    let disposable = vscode.commands.registerCommand('type', (args) => {

        vscode.window.showInformationMessage(args.text);

        vscode.commands.executeCommand('default:type', {
            text: args.text
        });
    });

    context.subscriptions.push(disposable);
}
@alexdima alexdima added the feature-request Request for new features or functionality label Apr 28, 2016
@alexdima
Copy link
Member

That is a cool idea. Currently, the keybinding dispatching ignores only modifier keys (does not try to dispatch them unless they are accompanied by a non-modifier key).

@alexdima alexdima added this to the May 2016 milestone Apr 28, 2016
@alexdima alexdima modified the milestones: June 2016, May 2016 May 26, 2016
@alexdima alexdima removed this from the June 2016 milestone Jun 29, 2016
@jpoon
Copy link

jpoon commented Sep 12, 2016

@alexandrudima With the Vim extension, users can remap their vim keybindings. We're discovering that some key combinations don't get dispatched to the extension: VSCodeVim/Vim#757. Particularly, Ctrl (ie. Ctrl+a). The only modifiers that seem to be dispatched are Alt and Shift.

Verified this by building a new extension with @k--kato's sample code above with VSCode 1.5.1

Related question: Can you have more than one extension register the type command?

@alexdima
Copy link
Member

alexdima commented Sep 12, 2016

@jpoon the keybindings.json use key codes. ctrl-a is correctly dispatched to extensions. e.g. here I have an extension that registers ctrl-t and it works correctly -- https://github.com/alexandrudima/vscode-lcov/blob/master/package.json#L109.

This issue is regarding registering a keybinding in the form of shift shift where only modifier keys are pressed.

Registering type is a take all or nothing. It is possible to unregister it via disposing the registration.

@jpoon
Copy link

jpoon commented Sep 12, 2016

(this likely belongs in a separate issue, let me know if you want me to move it)

With registering type, the alt and shift modifiers are dispatched. However, if you want to bind to the ctrl modifier, you need to make a separate entry in the package.json like you suggested above. Why does this need to be a separate action? It'd be great if ctrl+key also goes through the type register command.

As for the more than one extension question, what I'm seeing is:

  1. I have VSCodeVim installed which registers the type command
  2. I develop a new extension that attempts to also register the type command
  3. When I run my new extension, VSCode spits out an error saying that the type ID is already spoken for.

This seems to imply that only one extension at a time can register the type command?

@alexdima
Copy link
Member

@jpoon This has a very simple reason. It is driven by our platform (web browser). Pressing a key combination can end up generating the following DOM events:

  • keydown
  • keypress
  • input
  • compositionstart
  • compositionupdate
  • compositionend
  • keyup
  • maybe more, every couple of years, a few new ones pop up, or new properties are added to them.

It all depends on the keyboard layout you have installed and the key combinations you press. For example:

  • in the US keyboard layout, pressing a will generate a keydown, a keypress, an input and a keyup
  • in the US keyboard layout, pressing ctrl+a will generate a couple keydowns and a couple keyups
  • the rule of thumb mostly is that a keypress and a input is sent if the key combination produces a visible character... mostly... you see sometimes even Escape produces a keypress even if it doesn't produce any visible input.
  • in the Japanese/Hiragana IME, typing sennsei will generate many keydowns and keyups, a compositionstart, followed by multiple compositionupdates, sometime a few input events and finally a compositionend
  • the rule of thumb mostly is that you get visible produced characters in compositionupdate and in input events, meanwhile in keydown and keyup events you get key codes.
  • Key codes basically consist of a number, for example key code 36 is sent when pressing the Home key.
  • As far as I could tell, the numbers sort of follow the values defined at https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx but of course they vary across browsers and Operating Systems
  • the key codes are sort-of keyboard layout independ, except when they're not, because w3c thought it would be something too difficult for websites to deal with, so they dropped in this beauty -- http://www.w3.org/TR/DOM-Level-3-Events/#h-optionally-fixed-virtual-key-codes -- which for exampel Chromium has decided to implement, but only on macs!

The relevant code is at:

I am certain there must be things I've missed in how this works, so I would love PRs improving things, while keeping in mind that everything must work in Windows, Linux, OSX, on Chrome, on IE9-11, on Edge and on Firefox.

I bubble this up in two conceptual different ways:

  • keybindings.json: These are all interpreted on the keydown events, meaning they all work with key codes. So when you bind ctrl+] it really means ctrl+VK_OEM_6, in other words ctrl+0xDD. When the browser sends my way a keydown with the e.keyCode === 0xDD and the e.ctrlKey === true I will invoke your command.
  • type, compositionstart and compositionend - I have added these to our API for VIM emulation. They were not part of the API, but they are the only means to get visible input. If you are using these for something else than VIM, please don't. If necessary, I can throw in the registration of the command and check which extension registers these and have a whitelist. Most likely what you want needs to be a feature request that should be implemented through some other (possibly new) API.

@hastebrot
Copy link

hastebrot commented Sep 26, 2016

Indeed, would be nice if we could define this key binding in keybindings.json:

{ "key": "shift shift", "command": "workbench.action.quickOpen" }

@alexdima alexdima added the keybindings VS Code keybinding issues label Mar 1, 2017
@alexdima alexdima added this to the Backlog milestone Mar 1, 2017
@alexdima alexdima removed their assignment Mar 1, 2017
@deecewan
Copy link

deecewan commented Jun 4, 2017

Any progress on this?

@huy-lv
Copy link

huy-lv commented Oct 17, 2017

@hastebrot it does not work on vscode 1.17.1

@MrChriZ
Copy link

MrChriZ commented Oct 31, 2017

Would like to see this.

@jedlikowski
Copy link

Looks like Google Chrome now sends a keydown event when just the Shift key is pressed, with code 13. Should this be possible to implement now?

@gdbjohnson
Copy link

I'll just add my ditto to this.

@Varkal
Copy link

Varkal commented Mar 14, 2018

Any news on this ? (I'll wait relentlessly for this feature)

@MrBlaise
Copy link

I would love this as well!

@FlorisWarmenhoven
Copy link

I'm currently using a workaround, taking advantage of BetterTouchTools.

You can mimic this behaviour by adding a new shortcut, in BTT and selecting the 'Key Sequence' (inside the keyboard tab).

image

Then press 'Record New Key Sequence' and press shift > shift. This is what it will look like. I didn't touch any of the settings.

image

Save it, bind it to a key combination inside of VScode (I believe it's CMD + P by default). Et voilá! You have your desired functionality.

Of course, it would still be nicer if this was natively supported in VSCode without having to use third party software. Figured I'd share my solution anyway.

@leanderr
Copy link

leanderr commented Jul 26, 2018

Workaround for Windows:

  • Install Autohotkey
  • Paste this in an Autohotkey Script
  • Double Shift will result in CTRL+P being fired.

lastShift := 0
$Shift::
if ((A_TickCount - lastShift) <= 250)
Send ^p
else
Send {Shift}
lastShift := A_TickCount
return

@gulshan
Copy link

gulshan commented Oct 4, 2018

I proposed something similar in #29407 which was closed. Did not see this issue. My +1.

@PathToLife
Copy link
Contributor

PathToLife commented Jan 24, 2021

I spent only 1 hour at 2am on this. please only look at it as a starting point ;)

This is my first time cloning vscode and having a crack at the codebase.

After applying the hack here and rebuilding vscode:
PathToLife@5ed3c97

And using this shortcut chord:

    {
        "key": "shift+[ShiftLeft] shift+[ShiftLeft]",
        "command": "workbench.action.quickOpen"
    }

Will make it work.

Bugs (Known):

  • will cause ctrl+shift+p to trigger ctrl+shift as a seperate shortcut.

Cons:

  • will cause every shift press to activate as a cord

image

@PathToLife
Copy link
Contributor

PathToLife commented Jan 25, 2021

Complexities of implementing this:

I originally thought that using the Chord system, which is the double key combination detection system "Ctrl+K, Ctrl+D", might be doable... but it turns out, no, here's why

Shift+AlphaNumeric will not work if shift was in Chord mode:

  • If you press "shift" once, then press "shift+a" within 5 sec (to type uppercase A), then the quickOpen will trigger, typing A in the quickOpen window, instead of the editor window

Unwanted trigger when holding shift:

  • holding shift will fire off "shift shift" on the dispatch() function repeatedly, therefore quickOpen will trigger

VSCode's underlying keybinding system does not check the time between key presses.
Therefore it's impossible to differentiate between 'rapid presses' of key, vs slow press.

Need to do a architecture modification while making sure that there's no regressions with existing keybindings, and with cross-platform mac, linux, windows, and with different country keyboards. :)

PathToLife@caac951

@yume-chan
Copy link
Contributor

I think shift + shift can only be implemented as a special case, like how alt + mouse click works.

And only shift + nothing should count as the first shift hit, means it must be processed in keyup event. The second is maybe able to be dispatched in keydown event.

@trajano
Copy link

trajano commented Jan 25, 2021

Maybe we should have a +nothing capability doesn't have to be in the GUI I can/should be able to put it in keybindings.json as

{
    "key": "shift+nothing shift+nothing",
    "command": "workbench.action.quickOpen"
},

@yume-chan
Copy link
Contributor

Maybe we should have a +nothing capability doesn't have to be in the GUI I can/should be able to put it in keybindings.json

I still don't think this will fit into Code's keybinding system.

  1. +nothing is a special case that can only be detected in keyup (so you know there is no other keydown's in between), while all existing key bindings trigger at keydown
  2. shift+nothing shift+nothing means you can wait arbitrary time between two keys, but generally we want two shift presses to happen within a short time span: the chance of hitting shift by mistake is much higher than other key combos, in this case requiring pressing an extra (any) key to cancel the chord is not convenient. Not mention that Chinese IMEs all use shift key to switch between Chinese and English input mode, so you really don't want to care about a single shift press, only two.

But I still think a special case for shift + shift is feasible. Code already have many mouse shortcuts and keyboard + mouse shortcuts that are hardcoded (#3130). I think it possible to also hardcode shift + shift at first, and find better ways to generalize it in future. It maybe even simpler than modifying the already very complex keybinding system.

@PathToLife
Copy link
Contributor

PathToLife commented Jan 26, 2021

But I still think a special case for shift + shift is feasible.

Agreed, I'm thinking of implementing it as @hastebrot mentioned:
{ "key": "shift shift", "command": "workbench.action.quickOpen" }

I think it possible to also hardcode shift + shift at first, and find better ways to generalize it in future.

To keep keybinds somewhat intuitive, I think adding extra compatibility for other modifier keys, such as the below, will be a must.
{ "key": "ctrl ctrl", "command": "some other action" }
{ "key": "alt alt", "command": "some other action" }
perhaps even:
{ "key": "esc esc", "command": "workbench close project" }

Should make a timeout function that is started when a modifier key is pressed (perhaps modifiers only for now?); then if the same key is pressed within the time limit, it fires off a single resolve as usual...

Of importance is not to resolve the keybind database twice (the search for keybind functionality, mentioned in code as a resolver function), potential big performance / battery drain?

So probably put some sort of hardcode here?

const resolveResult = this._getResolver().resolve(contextValue, currentChord, firstPart);

Otherwise we could go up the tree and hardcode here?

const shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);

Is this the mouse hardcode?

@trajano
Copy link

trajano commented Jan 26, 2021

maybe have real specific cases that get fired rather than trying to parse it. Something like

  • DoubleLShift
  • DoubleRShift
  • DoubleLCtrl
  • DoubleRCtrl
  • DoubleLAlt
  • DoubleRAlt
  • DoubleLCmd
  • DoubleRCmd

I differentiated between L and R side so there can be an alternate mapping for the other side of the keyboard. Something like LShift LShift for QuickOpen and RShift RShift to search for commands

@PathToLife
Copy link
Contributor

PathToLife commented Jan 26, 2021

Good news,

Initial working implementation in this commit.

Feel free to give it a go!
Works fine on my mac.

PathToLife@ae426b3

after building give these a go:

// Place in your key bindings file
[
{
    "key": "shift shift",
    "command": "workbench.action.quickOpen"
},
{
    "key": "alt alt",
    "command": "workbench.action.quickOpen"
},
{
    "key": "ctrl ctrl",
    "command": "workbench.action.quickOpen"
}
]

I'll clean it up a bit later, in hindsight after getting a little more familiar with the codebase, I realised I've added a bunch of unecessary code regarding keybind -> dispatch string... maybe. Will need to see if there are indeed keycode differences between Mac/Win/Linux regarding Ctrl/Alt/Shift

haven't got around to running the keybind unit tests either - not too sure how to at this stage

@yume-chan
Copy link
Contributor

yume-chan commented Jan 26, 2021

@PathToLife Good job!

Looked a little bit into your changes, I think there are actual two parts: "allow dispatching single modifier key" and "time sensitive key chords".

For the second part I have some new ideas: allowing declare max allowed interval in keybindings.json, and even declaring multiple keybindings with same key chords, but different max allowed intervals, mapping to different commands. I think this will enable many more interesting usages.

The default value is Infinite if omitted, that's how all existing keybindings work. Dispatching single modifier key is not tied to time sensitive key chords, if a user very want that, they can still declare a shift shift with Infinite max allowed interval.

Dispatching single modifier key is only the part that dispatches modifier keys at keyup. Mixing single modifier key with a normal key combo in a single key chord should also be possible, considering current usage of keybindings.json.

I saw some performance concern. Maybe we can pre-parse keybindings.json to a Map between each key and all possible keybindings starting with it, so smashing any key will only need a O(1) search? I didn't know how keybindings resolve now.

@PathToLife
Copy link
Contributor

PathToLife commented Jan 27, 2021

Thanks for the suggestions @yume-chan

For the second part I have some new ideas: allowing declare max allowed interval in keybindings.json, and even declaring multiple keybindings with same key chords, but different max allowed intervals, mapping to different commands.
The default value is Infinite if omitted, that's how all existing keybindings work. Dispatching single modifier key is not tied to time sensitive key chords, if a user very want that, they can still declare a shift shift with Infinite max allowed interval.

To detect other duration, need to use dateTime().now() - lastSameKeyPressedDateTime to get the duration of every double keypress, then firing a resolver to check for keybind resolve(keybind, timeDoublePressed) In case of backspace, may lag?

Hopefully can have minimal impact on the codebase if we do implement this

Dispatching single modifier key is only the part that dispatches modifier keys at keyup. Mixing single modifier key with a normal key combo in a single key chord should also be possible, considering current usage of keybindings.json.

I think the current architecture's decision to use only one keyboard event? is cleaner, don't want to play with more events than usual

I saw some performance concern. Maybe we can pre-parse keybindings.json to a Map between each key and all possible keybindings starting with it, so smashing any key will only need a O(1) search? I didn't know how keybindings resolve now.

It is already O(1), keybindKeyCodeTokeyString[keycode: int]: keyCodeString[] // pseudo code, not ts

@yume-chan
Copy link
Contributor

I thought by default Code will wait for second chord forever, but that's not the case. Now there is a hardcoded 5s timeout

if (Date.now() - chordEnterTime > 5000) {
// 5 seconds elapsed => leave chord mode
this._leaveChordMode();
}

So adding time sensitive keybindings should be pretty easy. I will try that when I have time, and I believe this is the proper way toward dispatching single modifiers.

@yume-chan
Copy link
Contributor

yume-chan commented Jan 27, 2021

I have done my time sensitive key chords implementation at yume-chan@300dd01

The core part is only 5 lines long:

yume-chan@300dd01#diff-7bda82440de1deb3c14304313cd37fed2299a8778cef3a9dc2004543c8a3c76bR130-R134

  this._currentChord = {
    keypress: firstPart,
    label: keypressLabel
    label: keypressLabel,
+   enterTime: chordEnterTime,
  };

Record when did the first key chord was pressed.


yume-chan@300dd01#diff-1bea564a53de9969cb7e8758c4a6d68e3f57fbcd6302f4761338f63cd4fc1f42R286-R288

  if (candidate.keypressParts[1] === keypress && // Key matches
    KeybindingResolver.contextMatchesRules(context, candidate.when) && // When clause matches
+   timeElapsed <= candidate.timeout) { // Timeout matches

Compare time between two chords with declaration. These three lines used to be scattered, I rewrote them this way.

This check requires a first chord, so smashing Backspace won't get here unless you really have a keybinding for Backspace SomeOtherKey. It only adds one extra comparison if you don't use time sensitive key chords, and several more if you do use.


yume-chan@300dd01#diff-1bea564a53de9969cb7e8758c4a6d68e3f57fbcd6302f4761338f63cd4fc1f42R323

else if (candidate.keypressParts.length > 1 && candidate.keypressParts[1] !== null) {
  candidateChords.push(candidate);
  maxChordTimeout = Math.max(maxChordTimeout, candidate.timeout);
}

Gather all candidate second key chord when first chord was pressed. candidateChords is only used for logging and maxChordTimeout is only used to exit chord mode early, so these are not must have, I really need only the first two changes.

video.mp4

@kasecato
Copy link
Contributor Author

kasecato commented Feb 2, 2021

Thank you so much!

@andersonpem
Copy link

I tested it. It works like a charm. Thanks for everyone who supported this feature. Now I won't get crazy anymore trying to use PHPStorm shortcuts on VS Code and Code shortcuts on PHPStorm, since I use both very often.

@alexdima alexdima added the verification-needed Verification of issue is requested label Feb 23, 2021
@bpasero bpasero added the verification-steps-needed Steps to verify are needed for verification label Feb 24, 2021
@rzhao271 rzhao271 added verified Verification succeeded and removed verification-steps-needed Steps to verify are needed for verification labels Feb 24, 2021
@rzhao271
Copy link
Contributor

Verified, though it seems that the shortcuts must be set in the json file rather than using the keyboard shortcut editor.

@PathToLife
Copy link
Contributor

PathToLife commented Feb 24, 2021

Verified, though it seems that the shortcuts must be set in the json file rather than using the keyboard shortcut editor.

There are now two types of dispatchers in the keybinds resolver.

  1. The [NEW] modifier dispatcher, which listens to the keyup event, and is used solely for shift shift, alt alt, ctrl ctrl, + mac
  2. The [EXISTING] normal dispatcher, which listens to the keydown event, and is used solely for [something]+[something] or a chord: [something]+[something]&[something]+[something]

The decision to add the [NEW] modifier dispatcher is because the [EXISTING] normal dispatcher does not allow for rapid, single modifier shortcut events - this is because the chord system implementation interferes and gives unwanted behavior:

For example, after enabling single modifier key support on [EXISTING] normal dispatcher:

  • Pressing Shift once - Will activate a chord, any Shift keydown within 5 sec will activate the shortcut
  • Pressing Shift+a - will not type "A" as expected, the Chord system will activate and prevent dispatch of 'shift' and 'a'

This introduces the problem in that:
The shortcut editor relies on [EXISTING] normal dispatcher, thus it cannot detect the [NEW] modifier dispatcher

I did have a initial implementation that made it so that the shortcut editor did work, however a by product of this was that the aforementioned unwanted behavior.

Any changes that give support to shortcut editor may be - overly complex? and I feel would be better to have in a separate PR :)

Or even better - an overhaul of the keybinds system, including removal of hacks for the alt-select (mouse keys + keyboard keys).

@PathToLife
Copy link
Contributor

Has been released as of vscode version 1.54.1 :)

put this in your keybindings.json:

    {
        "key": "shift shift",
        "command": "workbench.action.quickOpen"
    },
    {
        "key": "alt alt",
        "command": "workbench.action.showCommands"
    }

Not sure what to do with ctrl ctrl, yet :)

@greg-arroyo
Copy link

This is nothing but awesome. Really appreciate the effort to add this!

@github-actions github-actions bot locked and limited conversation to collaborators Mar 20, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature-request Request for new features or functionality insiders-released Patch has been released in VS Code Insiders keybindings VS Code keybinding issues verification-needed Verification of issue is requested verified Verification succeeded
Projects
None yet