VS Code’s keybinding system provides a flexible, context-aware way to bind keyboard shortcuts to commands. The platform handles cross-platform differences, chord sequences, and context-based activation.
Overview
The keybinding system consists of:
KeybindingsRegistry : Stores all default and extension-provided keybindings
IKeybindingService : Runtime service that resolves keybindings and dispatches keyboard events
ResolvedKeybinding : Platform-specific representation of keybindings
KeybindingsRegistry
The registry stores all built-in and extension-provided keybindings (but not user-defined ones):
export interface IKeybindingsRegistry {
registerKeybindingRule ( rule : IKeybindingRule ) : IDisposable ;
setExtensionKeybindings ( rules : IExtensionKeybindingRule []) : void ;
registerCommandAndKeybindingRule < Args extends unknown [] = unknown []>(
desc : ICommandAndKeybindingRule < Args >
) : IDisposable ;
getDefaultKeybindings () : IKeybindingItem [];
}
Access the global registry:
import { KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry' ;
Registering Keybindings
Basic Keybinding Rule
import { KeybindingsRegistry , KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry' ;
import { KeyCode , KeyMod } from 'vs/base/common/keyCodes' ;
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey' ;
KeybindingsRegistry . registerKeybindingRule ({
id: 'editor.action.deleteLines' ,
weight: KeybindingWeight . EditorContrib ,
when: ContextKeyExpr . equals ( 'editorTextFocus' , true ),
primary: KeyMod . CtrlCmd | KeyMod . Shift | KeyCode . KeyK ,
secondary: [ KeyMod . CtrlCmd | KeyCode . KeyL ]
});
Keybinding Rule Interface
export interface IKeybindingRule extends IKeybindings {
id : string ;
weight : number ;
args ?: any ;
/**
* Keybinding is disabled if expression returns false
*/
when ?: ContextKeyExpression | null | undefined ;
}
export interface IKeybindings {
primary ?: number ;
secondary ?: number [];
// Platform-specific overrides
win ?: {
primary : number ;
secondary ?: number [];
};
linux ?: {
primary : number ;
secondary ?: number [];
};
mac ?: {
primary : number ;
secondary ?: number [];
};
}
Keybinding Weights
Weights determine priority when multiple keybindings conflict:
export const enum KeybindingWeight {
EditorCore = 0 , // Core editor bindings
EditorContrib = 100 , // Editor contributions
WorkbenchContrib = 200 , // Workbench contributions
BuiltinExtension = 300 , // Built-in extensions
ExternalExtension = 400 // External extensions
}
Higher weights take precedence. User-defined keybindings always win.
Key Codes and Modifiers
Key Modifiers
import { KeyMod } from 'vs/base/common/keyCodes' ;
KeyMod . CtrlCmd // Ctrl on Windows/Linux, Cmd on Mac
KeyMod . WinCtrl // Ctrl on all platforms
KeyMod . Alt // Alt key
KeyMod . Shift // Shift key
Key Codes
import { KeyCode } from 'vs/base/common/keyCodes' ;
// Letters
KeyCode . KeyA , KeyCode . KeyB , ... , KeyCode . KeyZ
// Numbers
KeyCode . Digit0 , KeyCode . Digit1 , ... , KeyCode . Digit9
// Function keys
KeyCode . F1 , KeyCode . F2 , ... , KeyCode . F12
// Special keys
KeyCode . Enter
KeyCode . Escape
KeyCode . Space
KeyCode . Backspace
KeyCode . Delete
KeyCode . Tab
KeyCode . UpArrow , KeyCode . DownArrow , KeyCode . LeftArrow , KeyCode . RightArrow
// Numpad
KeyCode . Numpad0 , KeyCode . Numpad1 , ... , KeyCode . Numpad9
KeyCode . NumpadAdd , KeyCode . NumpadSubtract , KeyCode . NumpadMultiply , KeyCode . NumpadDivide
Combining Keys
Combine modifiers and keys using the bitwise OR operator:
// Ctrl+K (Cmd+K on Mac)
KeyMod . CtrlCmd | KeyCode . KeyK
// Ctrl+Shift+P (Cmd+Shift+P on Mac)
KeyMod . CtrlCmd | KeyMod . Shift | KeyCode . KeyP
// Alt+F4
KeyMod . Alt | KeyCode . F4
Define different keybindings per platform:
Cross-Platform
Platform Overrides
KeybindingsRegistry . registerKeybindingRule ({
id: 'workbench.action.files.save' ,
weight: KeybindingWeight . WorkbenchContrib ,
primary: KeyMod . CtrlCmd | KeyCode . KeyS // Adapts automatically
});
Chord Keybindings
Chord keybindings are two-key sequences (like Ctrl+K Ctrl+C):
import { KeyChord } from 'vs/base/common/keyCodes' ;
KeybindingsRegistry . registerKeybindingRule ({
id: 'editor.action.commentLine' ,
weight: KeybindingWeight . EditorContrib ,
when: ContextKeyExpr . equals ( 'editorTextFocus' , true ),
primary: KeyChord (
KeyMod . CtrlCmd | KeyCode . KeyK , // First key
KeyMod . CtrlCmd | KeyCode . KeyC // Second key
)
});
The user must press both sequences in order. VS Code enters “chord mode” after the first key.
Context-Aware Keybindings
Use when clauses to activate keybindings only in specific contexts:
// Editor contexts
'editorTextFocus' // Editor has focus
'editorReadonly' // Editor is readonly
'editorHasSelection' // Text is selected
'editorHasMultipleSelections' // Multiple cursors
'editorLangId' // Editor language ID
// View contexts
'sideBarVisible' // Sidebar is visible
'panelVisible' // Panel is visible
'terminalFocus' // Terminal has focus
'searchViewletFocus' // Search view has focus
// Resource contexts
'resourceExtname' // File extension
'resourceFilename' // File name
'resourceScheme' // URI scheme
// Configuration
'config.editor.minimap.enabled' // Config values
Context Expression Examples
// Single condition
when : ContextKeyExpr . equals ( 'editorTextFocus' , true )
// Multiple conditions (AND)
when : ContextKeyExpr . and (
ContextKeyExpr . equals ( 'editorTextFocus' , true ),
ContextKeyExpr . equals ( 'editorLangId' , 'typescript' ),
ContextKeyExpr . not ( 'editorReadonly' )
)
// Alternative conditions (OR)
when : ContextKeyExpr . or (
ContextKeyExpr . equals ( 'terminalFocus' , true ),
ContextKeyExpr . equals ( 'searchViewletFocus' , true )
)
// Negation
when : ContextKeyExpr . not ( 'editorReadonly' )
// Greater than / less than
when : ContextKeyExpr . greater ( 'editorCursorLine' , 0 )
// Regular expression
when : ContextKeyExpr . regex ( 'resourceFilename' , / \. test \. ts $ / )
IKeybindingService
The keybinding service resolves and dispatches keybindings at runtime:
export const IKeybindingService = createDecorator < IKeybindingService >( 'keybindingService' );
export interface IKeybindingService {
readonly _serviceBrand : undefined ;
readonly inChordMode : boolean ;
readonly onDidUpdateKeybindings : Event < void >;
/**
* Returns none, one or many (depending on keyboard layout)
*/
resolveKeybinding ( keybinding : Keybinding ) : ResolvedKeybinding [];
resolveKeyboardEvent ( keyboardEvent : IKeyboardEvent ) : ResolvedKeybinding ;
resolveUserBinding ( userBinding : string ) : ResolvedKeybinding [];
/**
* Resolve and dispatch `keyboardEvent` and invoke the command
*/
dispatchEvent ( e : IKeyboardEvent , target : IContextKeyServiceTarget ) : boolean ;
/**
* Look up keybindings for a command
*/
lookupKeybindings ( commandId : string ) : ResolvedKeybinding [];
/**
* Look up the preferred keybinding for a command
*/
lookupKeybinding (
commandId : string ,
context ?: IContextKeyService ,
enforceContextCheck ?: boolean
) : ResolvedKeybinding | undefined ;
/**
* Get all keybindings
*/
getKeybindings () : readonly ResolvedKeybindingItem [];
/**
* Given a UI label and command, appends the keybinding if any
*/
appendKeybinding (
label : string ,
commandId : string | undefined | null ,
context ?: IContextKeyService ,
enforceContextCheck ?: boolean
) : string ;
}
Using the Keybinding Service
Looking Up Keybindings
class MyComponent {
constructor (
@ IKeybindingService private readonly keybindingService : IKeybindingService
) {}
showKeybindingInfo () : void {
// Get all keybindings for a command
const keybindings = this . keybindingService . lookupKeybindings ( 'editor.action.formatDocument' );
keybindings . forEach ( kb => {
console . log ( kb . getLabel ()); // "Shift+Alt+F"
});
// Get preferred keybinding
const preferred = this . keybindingService . lookupKeybinding ( 'editor.action.formatDocument' );
if ( preferred ) {
console . log ( `Format Document: ${ preferred . getLabel () } ` );
}
}
}
Appending Keybindings to Labels
// Add keybinding to a button label
const label = this . keybindingService . appendKeybinding (
'Format Document' ,
'editor.action.formatDocument'
);
// Result: "Format Document (Shift+Alt+F)"
Dispatching Keyboard Events
class MyEditor {
constructor (
@ IKeybindingService private readonly keybindingService : IKeybindingService
) {}
private onKeyDown ( e : IKeyboardEvent ) : void {
// Let the keybinding service handle it
const handled = this . keybindingService . dispatchEvent ( e , this . contextKeyService );
if ( ! handled ) {
// No keybinding matched, handle manually
this . handleKeyDown ( e );
}
}
}
Registering with Action2
The recommended way to register keybindings is through Action2:
import { Action2 , registerAction2 } from 'vs/platform/actions/common/actions' ;
import { KeyCode , KeyMod } from 'vs/base/common/keyCodes' ;
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry' ;
class MyAction extends Action2 {
constructor () {
super ({
id: 'myExtension.myAction' ,
title: { value: 'My Action' , original: 'My Action' },
f1: true ,
keybinding: {
primary: KeyMod . CtrlCmd | KeyMod . Shift | KeyCode . KeyM ,
secondary: [ KeyMod . Alt | KeyCode . KeyM ],
when: ContextKeyExpr . equals ( 'editorTextFocus' , true ),
weight: KeybindingWeight . WorkbenchContrib ,
// Platform-specific
win: {
primary: KeyMod . CtrlCmd | KeyCode . KeyM
}
}
});
}
run ( accessor : ServicesAccessor ) : void {
// Action implementation
}
}
registerAction2 ( MyAction );
Real-World Examples
KeybindingsRegistry . registerCommandAndKeybindingRule ({
id: 'workbench.action.files.save' ,
weight: KeybindingWeight . WorkbenchContrib ,
when: undefined ,
primary: KeyMod . CtrlCmd | KeyCode . KeyS ,
handler : ( accessor ) => {
const editorService = accessor . get ( IEditorService );
return editorService . save ({ editor: editorService . activeEditor });
}
});
KeybindingsRegistry . registerKeybindingRule ({
id: 'workbench.action.quickOpen' ,
weight: KeybindingWeight . WorkbenchContrib ,
when: undefined ,
primary: KeyMod . CtrlCmd | KeyCode . KeyP ,
secondary: [ KeyMod . CtrlCmd | KeyCode . KeyE ],
mac: {
primary: KeyMod . CtrlCmd | KeyCode . KeyP ,
secondary: []
}
});
Context-Specific Keybinding
KeybindingsRegistry . registerKeybindingRule ({
id: 'editor.action.triggerSuggest' ,
weight: KeybindingWeight . EditorContrib ,
when: ContextKeyExpr . and (
ContextKeyExpr . equals ( 'editorTextFocus' , true ),
ContextKeyExpr . not ( 'editorReadonly' ),
ContextKeyExpr . not ( 'suggestWidgetVisible' )
),
primary: KeyMod . CtrlCmd | KeyCode . Space ,
secondary: [ KeyMod . CtrlCmd | KeyCode . KeyI ],
mac: {
primary: KeyMod . WinCtrl | KeyCode . Space ,
secondary: [ KeyMod . Alt | KeyCode . Escape ]
}
});
Best Practices
Use KeyMod.CtrlCmd : Always use KeyMod.CtrlCmd instead of KeyMod.WinCtrl for better cross-platform behavior. It automatically maps to Cmd on Mac and Ctrl elsewhere.
Avoid conflicts : Check existing keybindings before registering new ones. Use appropriate weights and context expressions to avoid conflicts.
Provide alternatives : Consider providing secondary keybindings for discoverability and user preference.
Context awareness : Always use when clauses to scope keybindings to appropriate contexts. This prevents unintended activation.
Document chord sequences : Chord keybindings are powerful but less discoverable. Document them clearly in your extension’s README.
Testing Keybindings
import { TestInstantiationService } from 'vs/platform/instantiation/test/common/instantiationServiceMock' ;
import { KeybindingService } from 'vs/platform/keybinding/browser/keybindingService' ;
suite ( 'Keybindings' , () => {
let instantiationService : TestInstantiationService ;
let keybindingService : KeybindingService ;
setup (() => {
instantiationService = new TestInstantiationService ();
keybindingService = instantiationService . createInstance ( KeybindingService );
});
test ( 'should resolve keybinding' , () => {
const keybindings = keybindingService . lookupKeybindings ( 'editor.action.formatDocument' );
assert . ok ( keybindings . length > 0 );
});
test ( 'should dispatch keybinding' , () => {
let commandExecuted = false ;
CommandsRegistry . registerCommand ( 'test.command' , () => {
commandExecuted = true ;
});
keybindingService . dispatchEvent ( createKeyboardEvent ( KeyCode . KeyA ), target );
assert . ok ( commandExecuted );
});
});
See Also