microsoft / microsoft/vscode

Allow extension keybindings to inherit from another command

Open
#336,545 0 comments 0 reactions 2 assignees Claimed by @ulugbekna View on GitHub
api-proposal context-keys extensions feature-request keybindings
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.