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

Implement conditional bindings #14

Merged
merged 27 commits into from
Sep 2, 2020
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
e184d3c
Support passing of "when" with key listener
stevenguh Aug 24, 2020
2ba3e9e
Implement conditionals type
stevenguh Aug 24, 2020
ba48ad4
Add ConfigSections type
stevenguh Aug 24, 2020
0cd6fb6
Add optional name in ConditionalBindingItem
stevenguh Aug 24, 2020
be2d670
Refactoring to a inheritance model for menu items
stevenguh Aug 25, 2020
610b20b
Traverse through override bindings with keys
stevenguh Aug 26, 2020
a6d6735
Remove useless statment
stevenguh Aug 26, 2020
7eecab6
Implement query-string like key as condition
stevenguh Aug 26, 2020
8899e23
Simplify menu selection code
stevenguh Aug 26, 2020
c920f82
Add async to onDidAccept
stevenguh Aug 26, 2020
e08aae7
Fix broken transient
stevenguh Aug 26, 2020
eeee24a
Use vscode api for binding this for callbacks
stevenguh Aug 26, 2020
84e8dea
Use ; instead , as separator of command
stevenguh Aug 27, 2020
d56d1bf
Small refactoring for error rasing
stevenguh Aug 28, 2020
34fa407
Refactoring the ConditionalMenuItem
stevenguh Aug 28, 2020
203a36d
Fix the bug where undefined key are not recognized
stevenguh Aug 28, 2020
ba32689
Show status bar msg when no conditionals matched
stevenguh Aug 28, 2020
d91efe8
Update doc for conditional bindings
stevenguh Aug 28, 2020
cd8398d
Remove an extra layer of await function
stevenguh Aug 31, 2020
533959a
Update wording
stevenguh Aug 31, 2020
ebb8491
Code cleanup
stevenguh Aug 31, 2020
62f2216
README: remove trailing whitespaces
marcoieni Sep 1, 2020
758a45e
README: add some missing new lines
marcoieni Sep 1, 2020
59a747d
README: fix grammar, improve readability
marcoieni Sep 1, 2020
a798b2b
Clarify conditional section of the README
stevenguh Sep 1, 2020
1d6b6b4
README: fix typo, simplify sentences
marcoieni Sep 2, 2020
11a0c62
README: hide experimental feature by default
marcoieni Sep 2, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
### Added
- Add color on the status bar message when key binding entered is not defined
- Add support for a new conditional type binding, which allows conditional binding execution. See README for more information on how to use it.

## [0.7.6] - 2020-08-03
### Added
Expand Down
164 changes: 161 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This extension can be used by itself or be called by other extension.

### Standalone

This extension comes with a default that didn't have any third-party dependencies.
This extension comes with a default that didn't have any third-party dependencies.
#### Setup: I am using VSCode Vim
If you want a better default behavior design for VSCode Vim, checkout [VSpaceCode](https://github.com/VSpaceCode/VSpaceCode).

Expand Down Expand Up @@ -46,7 +46,6 @@ You can also bind a customize menu with Vim directly
{
"before": ["<space>"],
"commands": ["whichkey.show"],

}
]
```
Expand Down Expand Up @@ -101,7 +100,7 @@ The following example will replace/append the whole `<SPC> g` menu with one bind
"key": "s",
"name": "Go to",
"type": "command",
"command": "workbench.action.gotoLine",
"command": "workbench.action.gotoLine",
}
]
}
Expand Down Expand Up @@ -241,6 +240,7 @@ This section config extra settings that pertain to both Standalone or With exten
This section describes a way to use non-character keys in which-key menu like `<tab>` or `Control+D`. `<tab>` is supported out of the box. Follow the following instruction to add support for keys other than `<tab>`.

Merge the following json to your `keybindings.json`.

```json
{
"key": "ctrl+x",
Expand All @@ -249,6 +249,7 @@ Merge the following json to your `keybindings.json`.
"when": "whichkeyActive"
}
```

Once you've done that, you can use `C-x` in the `key` value of the which-key config. Effectively, the above keybinding will enter `C-x` in the QuickPick input box when `ctrl+x` is pressed when the which key is focused.

### Display menu with a delay
Expand All @@ -259,12 +260,169 @@ You can set `whichkey.sortOrder` in `settings.json` to `alphabetically` to alway

### Unclear selection
Selected text can be hard to see when which-key menu is active. This could be due to the `inactiveSelectionBackground` config of your current theme. You can selectively override that color in your `settings.json` like the following example.

```json
"workbench.colorCustomizations": {
"editor.inactiveSelectionBackground": "color that works better",
},
```

### Conditional bindings (experimental)

> This is marked as experimental and the config is subject to change.

This allows conditional execution of bindings. Currently, it only supports conditions on the `when` passed from shortcut and `languageId` of the active editor.

- It reuses the similar structure to the `bindings` type.
- The property `key` in a binding item is reused to represent the condition.
- The condition can be thought of as a key-value pair serialized into a string.

As an example, a condition in json like

```json
{
"languageId": "javascript",
"when": "sideBarVisible"
}
```

can be serialized into `languageId:javascript;when:sideBarVisible`.
The string representation can then be used as the value of the binding key.

A concrete example of a binding with that condition is as follow:

```json
{
"whichkey.bindings": [
{
"key": "m",
"name": "Major...",
"type": "conditional",
"bindings": [
{
"key": "languageId:javascript;when:sideBarVisible",
"name": "Open file",
"type": "command",
"command": "workbench.action.files.openFileFolder"
},
{
"key": "",
"name": "Buffers",
"type": "bindings",
"bindings": [
{
"key": "b",
"name": "Show all buffers/editors",
"type": "command",
"command": "workbench.action.showAllEditors"
}
]
},
]
}
]
}
```

In this example, when `m` is pressed, it will find the first binding that matches the current condition.
If no configured key matches the current condition, a default item showing a buffer menu will be used.
Any item that has an invalid key will be used as default item.

Therefore, in this example, if the language is javascript and the sidebar is visible, `m` will open
the file browser, otherwise it will show the "buffers" menu.

#### Overrides

This is again similar with the `bindings` type.

For example, the following config will override the `m` binding completely:

```json
{
"whichkey.bindingOverrides": [
{
"keys": "m",
"name": "Major",
"type": "conditional",
"bindings": [
{
"key": "languageId:javascript",
"name": "Go to",
"type": "command",
"command": "workbench.action.gotoLine",
}
]
}
]
}
```

You can also choose to add or remove conditions to existing conditional bindings.
For example, the following will add a key of `languageId:javascript` to the conditional binding if `languageId:javascript` doesn't already exist.

```json
{
"whichkey.bindingOverrides": [
{
"keys": ["m", "languageId:javascript"],
"name": "Go to",
"type": "command",
"command": "workbench.action.gotoLine",
}
]
}
```

Negative `position` property can also be used to remove conditional bindings.

#### when

Since VSCode doesn't allow reading of the context, which is the condition used in the `when` in shortcuts,
for every `when` condition, you will need to set up a shortcut to evaluate that specific condition.

For example, the following keybindings will pass both `key` and `when` for which-key handle for key `t`.
`keybindings.json`

```json
{
"key": "t",
"command": "whichkey.triggerKey",
"args": {
"key": "t",
"when": "sideBarVisible && explorerViewletVisible"
},
"when": "whichkeyVisible && sideBarVisible && explorerViewletVisible"
}
```

You can then define the follow bindings that uses that specific `key` and `when`.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This and the following json are not very clear to me.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, let me know the updated version is clearer :)


```json
{
"key": "t",
"name": "Show tree/explorer view",
"type": "conditional",
"bindings": [
{
"key": "",
"name": "default",
"type": "command",
"command": "workbench.view.explorer"
},
{
"key": "when:sideBarVisible && explorerViewletVisible",
"name": "Hide explorer",
"type": "command",
"command": "workbench.action.toggleSidebarVisibility"
}
]
}
```

#### languageId

This is language id of the active editor. The language id can be found in language selection menu inside the parenthesis next to the language name.

## Release Notes

See [CHANGELOG.md](CHANGELOG.md)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@
"key": "tab",
"command": "whichkey.triggerKey",
"args": "\t",
"when": "whichkeyActive"
"when": "whichkeyVisible"
}
],
"commands": [
Expand Down
1 change: 1 addition & 0 deletions src/bindingItem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ export const enum ActionType {
Commands = "commands",
Bindings = "bindings",
Transient = "transient",
Conditional = "conditional",
}

export interface BindingItem {
Expand Down
5 changes: 4 additions & 1 deletion src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@ export enum SortOrder {
}

export enum ContextKey {
Active = 'whichkeyActive'
Active = 'whichkeyActive',
Visible = 'whichkeyVisible'
}
export const whichKeyShow = `${contributePrefix}.${CommandKey.Show}`;
export const whichKeyRegister = `${contributePrefix}.${CommandKey.Register}`;
export const whichKeyTrigger = `${contributePrefix}.${CommandKey.Trigger}`;

export const defaultStatusBarTimeout = 5000;
14 changes: 11 additions & 3 deletions src/keyListener.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
import { EventEmitter } from "vscode";

export interface KeybindingArgs {
key: string,
when?: string,
}

export default class KeyListener {
emitter: EventEmitter<string>;
emitter: EventEmitter<KeybindingArgs>;
constructor() {
this.emitter = new EventEmitter<string>();
this.emitter = new EventEmitter<KeybindingArgs>();
}

trigger(key: string) {
trigger(key: string | KeybindingArgs) {
if (typeof key === "string") {
key = { key };
}
this.emitter.fire(key);
}

Expand Down
Loading