Allow extension keybindings to inherit from another command
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
## Feature request
Allow an extension-contributed keybinding to inherit the effective keybindings of another command while executing a different command in a different context.
For example:
```json
{
"contributes": {
"keybindings": [
{
"command": "markdown.editor.moveLinesUp",
"inheritFrom": {
"command": "editor.action.moveLinesUpAction",
"when": "editorTextFocus && !editorReadonly"
},
"when": "markdownEditorTextFocus && markdownEditorWritable"
}
]
}
}
```
## Motivation
Alternative editors can implement the same editing operation as the code editor but need a different command because their implementation and focus context differ. Today they must copy the code editor's physical default keybindings into their own extension manifest.
This has two problems:
- User customizations of the code editor command do not apply to the alternative editor command.
- Platform-specific defaults, secondary bindings, chords, arguments, and future changes must be duplicated and kept synchronized.
The experimental Markdown editor is a concrete example. It has host-routable `markdown.editor.*` commands and a generated copy of the corresponding keybindings. It should be possible for one customized keybinding to apply to both the code editor and Markdown editor while each retains its own command implementation.
Related: microsoft/vscode-packages#292 and #329414.
## Proposed semantics
A keybinding rule is currently resolved approximately as:
```text
keybinding + context -> command + args
```
Inheritance would project the effective bindings in the other direction:
```text
source command + source context -> keybinding rules + args
```
and then create target rules:
```text
inherited keybinding + target context -> target command + inherited args
```
`inheritFrom.when` identifies the source command's relevant keybinding family. This is necessary because one command can have multiple bindings with different context expressions and arguments. The outer `when` controls where the projected target bindings apply.
The projection should include all matching effective source bindings rather than selecting only one preferred binding. This includes primary and secondary bindings, chords, platform-specific variants, and user-added bindings.
### Arguments
Inherited bindings should preserve their source `args` by default. This is important for commands where multiple keybindings target one command ID and arguments distinguish the operation.
An explicit outer `args` could replace the inherited arguments:
```json
{
"command": "markdown.editor.move",
"inheritFrom": {
"command": "coreEditor.move",
"when": "editorTextFocus"
},
"args": {
"direction": "up",
"by": "logicalMarkdownLine"
},
"when": "markdownEditorTextFocus"
}
```
Initial semantics should replace arguments rather than deep-merge them.
### User customizations
The expected ordering is:
```text
source defaults
+ source user bindings
- source removals
-> inherited projection
+ explicit target bindings
- target removals
```
Therefore:
- Rebinding the source command also rebinds the target command.
- Removing a source binding removes its inherited projection.
- Users can still add or remove target-only bindings.
- Explicit target bindings take precedence over inherited ones.
## Additional considerations
- Detect and reject inheritance cycles.
- Preserve the source binding's arguments and provenance.
- Expose inherited bindings in the Keyboard Shortcuts editor, including the source command.
- Recompute projections when user keybindings or extension keybindings change.
- Keep command identity and execution separate: invoking the source command programmatically should not invoke the target command.
- Consider whether a future context-key mapping form is needed to preserve residual source conditions instead of replacing the source `when` entirely.
Contributor guide
Assessment
This issue has not been assessed yet.