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

fix(input/#382): Leader-key customization #2672

Merged
merged 12 commits into from
Nov 7, 2020
28 changes: 28 additions & 0 deletions docs/docs/configuration/key-bindings.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,31 @@ Examples:
{"key": "kk", "command": ":split", "when": "editorTextFocus"},
{"key": "<C-D>", "command": ":d 2", "when": "insertMode"}
```

### Leader Key

A leader key can be specified via the following configuration setting:

```
{ "vim.leader": "<space>" }
```
> NOTE: This setting is in `configuration.json`, not `keybindings.json`

Alternatively, the leader key can be specified via an `Ex` command:
```
:nmap <space> <Leader>
```

Once the leader key is defined, it may be used in both `keybindings.json` and via VimL map commands:

```
[
{ "key": "<Leader>p", "command": "workbench.action.quickOpen", "when": "editorTextFocus && normalMode" }
]
```

or, alternatively, in VimL:

```
:nnoremap <Leader>p <C-S-P>
```
4 changes: 4 additions & 0 deletions docs/docs/configuration/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,10 @@ The configuration file, `configuration.json` is in the Oni2 directory, whose loc

- `vim.highlightedyank.duration` __(_int_ default: `300`)__ - The time, in milliseconds, the yank highlight is visible.

### Input

- `vim.leader` __(_string_)__ - Specify a custom [leader key](./key-bindings#leader-key).

### Layout

- `workbench.editor.showTabs` __(_bool_ default: `true`)__ - When `false`, hides the editor tabs.
Expand Down
10 changes: 5 additions & 5 deletions integration_test/ExCommandKeybindingTest.re
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ runTest(
(dispatch, wait, _) => {
let input = key => {
let keyPress =
EditorInput.KeyPress.{
scancode: Sdl2.Scancode.ofName(key),
keycode: Sdl2.Keycode.ofName(key),
modifiers: EditorInput.Modifiers.none,
};
EditorInput.KeyPress.physicalKey(
~scancode=Sdl2.Scancode.ofName(key),
~keycode=Sdl2.Keycode.ofName(key),
~modifiers=EditorInput.Modifiers.none,
);
let time = Revery.Time.now();

dispatch(KeyDown(keyPress, time));
Expand Down
10 changes: 5 additions & 5 deletions integration_test/ExCommandKeybindingWithArgsTest.re
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ runTest(
(dispatch, wait, _) => {
let input = key => {
let keyPress =
EditorInput.KeyPress.{
scancode: Sdl2.Scancode.ofName(key),
keycode: Sdl2.Keycode.ofName(key),
modifiers: EditorInput.Modifiers.none,
};
EditorInput.KeyPress.physicalKey(
~scancode=Sdl2.Scancode.ofName(key),
~keycode=Sdl2.Keycode.ofName(key),
~modifiers=EditorInput.Modifiers.none,
);
let time = Revery.Time.now();

dispatch(KeyDown(keyPress, time));
Expand Down
3 changes: 2 additions & 1 deletion integration_test/KeySequenceJJTest.re
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ runTest(
let keycode = Sdl2.Keycode.ofName(key);
let modifiers = EditorInput.Modifiers.none;

let keyPress: EditorInput.KeyPress.t = {scancode, keycode, modifiers};
let keyPress: EditorInput.KeyPress.t =
EditorInput.KeyPress.physicalKey(~keycode, ~scancode, ~modifiers);
let time = Revery.Time.now();

dispatch(Model.Actions.KeyDown(keyPress, time));
Expand Down
3 changes: 2 additions & 1 deletion integration_test/VimSimpleRemapTest.re
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ runTest(~name="VimSimpleRemapTest", (dispatch, wait, runEffects) => {
let keycode = Sdl2.Keycode.ofName(key);
let modifiers = EditorInput.Modifiers.none;

let keyPress: EditorInput.KeyPress.t = {scancode, keycode, modifiers};
let keyPress: EditorInput.KeyPress.t =
EditorInput.KeyPress.physicalKey(~scancode, ~keycode, ~modifiers);
let time = Revery.Time.now();

dispatch(Model.Actions.KeyDown(keyPress, time));
Expand Down
2 changes: 2 additions & 0 deletions src/Core/Config.re
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ type key = Lookup.path;
type resolver = (~vimSetting: option(string), key) => rawValue;
type fileTypeResolver = (~fileType: string) => resolver;

let emptyResolver = (~vimSetting as _, _) => NotSet;

let key = Lookup.path;
let keyAsString = Lookup.key;

Expand Down
2 changes: 2 additions & 0 deletions src/Core/Config.rei
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ type rawValue =
type resolver = (~vimSetting: option(string), key) => rawValue;
type fileTypeResolver = (~fileType: string) => resolver;

let emptyResolver: resolver;

let key: string => key;
let keyAsString: key => string;

Expand Down
92 changes: 88 additions & 4 deletions src/Feature/Input/Feature_Input.re
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,67 @@ open Oni_Core;
open Utility;
module Log = (val Log.withNamespace("Oni2.Feature.Input"));

// CONFIGURATION
module Configuration = {
open Oni_Core;
open Config.Schema;

module CustomDecoders = {
let physicalKey =
custom(
~decode=
Json.Decode.(
string
|> and_then(keyString =>
if (keyString == "(none)") {
succeed(None);
} else {
switch (
EditorInput.KeyPress.parse(
~getKeycode,
~getScancode,
keyString,
)
) {
| Ok([]) =>
fail("Unable to parse key sequence: " ++ keyString)
| Ok([key]) =>
switch (EditorInput.KeyPress.toPhysicalKey(key)) {
| None => fail("Not a physical key: " ++ keyString)
| Some(physicalKey) => succeed(Some(physicalKey))
}
| Ok(_keys) =>
fail(
"Unable to parse key sequence - too many keys: "
++ keyString,
)
| Error(msg) =>
fail("Unable to parse key sequence: " ++ msg)
};
}
)
),
~encode=
Json.Encode.(
maybeKey => {
switch (maybeKey) {
| Some(key) =>
EditorInput.KeyPress.toString(
~keyCodeToString=Sdl2.Keycode.getName,
EditorInput.KeyPress.PhysicalKey(key),
)
|> string
| None => "(none)" |> string
};
}
),
);
};

let leaderKey =
setting("vim.leader", CustomDecoders.physicalKey, ~default=None);
};

// MSG

type outmsg =
Expand Down Expand Up @@ -73,6 +134,8 @@ type model = {
inputStateMachine: InputStateMachine.t,
};

type uniqueId = InputStateMachine.uniqueId;

let initial = keybindings => {
open Schema;
let inputStateMachine =
Expand Down Expand Up @@ -107,9 +170,10 @@ type effect =
| Unhandled(EditorInput.KeyPress.t)
| RemapRecursionLimitHit;

let keyDown = (~key, ~context, {inputStateMachine, _} as model) => {
let keyDown = (~config, ~key, ~context, {inputStateMachine, _} as model) => {
let leaderKey = Configuration.leaderKey.get(config);
let (inputStateMachine', effects) =
InputStateMachine.keyDown(~key, ~context, inputStateMachine);
InputStateMachine.keyDown(~leaderKey, ~key, ~context, inputStateMachine);
({...model, inputStateMachine: inputStateMachine'}, effects);
};

Expand All @@ -119,12 +183,31 @@ let text = (~text, {inputStateMachine, _} as model) => {
({...model, inputStateMachine: inputStateMachine'}, effects);
};

let keyUp = (~key, ~context, {inputStateMachine, _} as model) => {
let keyUp = (~config, ~key, ~context, {inputStateMachine, _} as model) => {
let leaderKey = Configuration.leaderKey.get(config);
let (inputStateMachine', effects) =
InputStateMachine.keyUp(~key, ~context, inputStateMachine);
InputStateMachine.keyUp(~leaderKey, ~key, ~context, inputStateMachine);
({...model, inputStateMachine: inputStateMachine'}, effects);
};

let addKeyBinding = (~binding, {inputStateMachine, _} as model) => {
open Schema;
let (inputStateMachine', uniqueId) =
InputStateMachine.addBinding(
binding.matcher,
binding.condition,
binding.command,
inputStateMachine,
);
({...model, inputStateMachine: inputStateMachine'}, uniqueId);
};

let remove = (uniqueId, {inputStateMachine, _} as model) => {
let inputStateMachine' =
InputStateMachine.remove(uniqueId, inputStateMachine);
{...model, inputStateMachine: inputStateMachine'};
};

// UPDATE
module Internal = {
let vimMapModeToWhenExpr = mode => {
Expand Down Expand Up @@ -253,4 +336,5 @@ module Commands = {

module Contributions = {
let commands = Commands.[showInputState];
let configuration = Configuration.[leaderKey.spec];
};
26 changes: 23 additions & 3 deletions src/Feature/Input/Feature_Input.rei
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,36 @@ type effect =
| RemapRecursionLimitHit;

let keyDown:
(~key: KeyPress.t, ~context: WhenExpr.ContextKeys.t, model) =>
(
~config: Config.resolver,
~key: KeyPress.t,
~context: WhenExpr.ContextKeys.t,
model
) =>
(model, list(effect));

let text: (~text: string, model) => (model, list(effect));
let keyUp:
(~key: KeyPress.t, ~context: WhenExpr.ContextKeys.t, model) =>
(
~config: Config.resolver,
~key: KeyPress.t,
~context: WhenExpr.ContextKeys.t,
model
) =>
(model, list(effect));

type uniqueId;

let addKeyBinding:
(~binding: Schema.resolvedKeybinding, model) => (model, uniqueId);

let remove: (uniqueId, model) => model;

// UPDATE

let update: (msg, model) => (model, outmsg);

module Contributions: {let commands: list(Command.t(msg));};
module Contributions: {
let commands: list(Command.t(msg));
let configuration: list(Config.Schema.spec);
};
45 changes: 25 additions & 20 deletions src/Input/Handler.re
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
module Log = (val Oni_Core.Log.withNamespace("Oni2.Input.Handler"));
module Zed_utf8 = Oni_Core.ZedBundled;

open Oni_Core.Utility;

module Internal = {
let keyCodeToVimString = (keycode, keyString) => {
let len = Zed_utf8.length(keyString);
Expand Down Expand Up @@ -98,25 +100,28 @@ module Internal = {
};
};

let keyPressToCommand =
(~isTextInputActive, {modifiers, keycode, _}: EditorInput.KeyPress.t) => {
let altGr = modifiers.altGr;
let shiftKey = modifiers.shift;
let altKey = modifiers.alt;
let ctrlKey = modifiers.control;
let superKey = modifiers.meta;
let keyPressToCommand = (~isTextInputActive, key) => {
let maybeKey = EditorInput.KeyPress.toPhysicalKey(key);
maybeKey
|> OptionEx.flatMap(({modifiers, keycode, _}: EditorInput.PhysicalKey.t) => {
let altGr = modifiers.altGr;
let shiftKey = modifiers.shift;
let altKey = modifiers.alt;
let ctrlKey = modifiers.control;
let superKey = modifiers.meta;

if (altGr && isTextInputActive) {
None;
// If AltGr is pressed, and we're in text input mode, we'll assume the text input handled it
} else {
Internal.keyPressToString(
~isTextInputActive,
~shiftKey,
~altKey,
~ctrlKey,
~superKey,
keycode,
);
};
if (altGr && isTextInputActive) {
None;
// If AltGr is pressed, and we're in text input mode, we'll assume the text input handled it
} else {
Internal.keyPressToString(
~isTextInputActive,
~shiftKey,
~altKey,
~ctrlKey,
~superKey,
keycode,
);
};
});
};
1 change: 1 addition & 0 deletions src/Model/State.re
Original file line number Diff line number Diff line change
Expand Up @@ -491,6 +491,7 @@ let initial =
Feature_AutoUpdate.Contributions.configuration,
Feature_Buffers.Contributions.configuration,
Feature_Editor.Contributions.configuration,
Feature_Input.Contributions.configuration,
Feature_SideBar.Contributions.configuration,
Feature_Syntax.Contributions.configuration,
Feature_Terminal.Contributions.configuration,
Expand Down
23 changes: 10 additions & 13 deletions src/Store/InputStoreConnector.re
Original file line number Diff line number Diff line change
Expand Up @@ -171,17 +171,11 @@ let start = (window: option(Revery.Window.t), runEffects) => {
};

Some(
EditorInput.KeyPress.{
scancode,
keycode,
modifiers: {
shift,
control,
alt,
meta,
altGr,
},
},
EditorInput.KeyPress.physicalKey(
~scancode,
~keycode,
~modifiers={shift, control, alt, meta, altGr},
),
);
};
};
Expand All @@ -199,8 +193,9 @@ let start = (window: option(Revery.Window.t), runEffects) => {
let handleKeyPress = (state: State.t, key) => {
let context = Model.ContextKeys.all(state);

let config = Model.Selectors.configResolver(state);
let (input, effects) =
Feature_Input.keyDown(~context, ~key, state.input);
Feature_Input.keyDown(~config, ~context, ~key, state.input);

let newState = {...state, input};

Expand All @@ -224,8 +219,10 @@ let start = (window: option(Revery.Window.t), runEffects) => {
let handleKeyUp = (state: State.t, key) => {
let context = Model.ContextKeys.all(state);

let config = Model.Selectors.configResolver(state);
//let inputKey = reveryKeyToEditorKey(key);
let (input, effects) = Feature_Input.keyUp(~context, ~key, state.input);
let (input, effects) =
Feature_Input.keyUp(~config, ~context, ~key, state.input);

let newState = {...state, input};

Expand Down
Loading