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

Refactor Remapper #4735

Merged
merged 63 commits into from
Aug 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
3b22581
[PoC] Refactor Remapper
Apr 13, 2020
9b66bfe
Merge branch 'master' into remapRefactor_PoC
J-Fields May 3, 2020
73d2968
Fix bug with actions that 'mustBeFirstKey'
berknam May 5, 2020
09beebb
Rename variables, bug fixes and add debug logs:
berknam May 5, 2020
520d252
Merge branch 'master' into remapRefactor_PoC
berknam May 5, 2020
1f9c505
Fix timeout being called twice on ambiguous remaps
berknam May 6, 2020
417752a
Fix potentialRemaps infinite loop when using count
berknam May 6, 2020
a942c6c
Merge branch 'master' into remapRefactor_PoC
J-Fields May 7, 2020
06d82d0
Merge branch 'master' into remapRefactor_PoC
berknam May 9, 2020
5a769eb
Fix timeout waiting twice on remapping
berknam May 8, 2020
15998d9
Add test for this new remapper
berknam May 9, 2020
7104ddf
Fix bug in test
berknam May 9, 2020
b886cdf
Increase timeout offset in test
berknam May 9, 2020
08c0742
another try at fixing the test
berknam May 9, 2020
b705b55
Merge branch 'master' into remapRefactor_PoC
berknam May 9, 2020
8952b29
finally fix the test for the new remapper
berknam May 10, 2020
964803e
Fix another bug with actions that 'mustBeFirstKey'
berknam May 16, 2020
acede5d
Fix remapper and showCmd
berknam May 16, 2020
d9342a8
Implement OperatorPendingMode Remappings
berknam May 16, 2020
7b9e354
Merge branch 'master' into remapRefactor_PoC
berknam May 16, 2020
5a1fa75
Merge branch 'master' into remapRefactor_PoC
J-Fields May 17, 2020
85bc42c
Apply the review changes
berknam May 18, 2020
1d6f896
Merge branch 'master' into remapRefactor_PoC
berknam May 19, 2020
1507df8
Use updateShowCmd
berknam May 19, 2020
5c63426
Update .vimrc Remapping Regexes
berknam May 19, 2020
e38605e
Implement special case in recursive remaps
berknam May 20, 2020
a1c41bf
Implement special case for '0' key to enable remap
berknam May 23, 2020
6fbaf12
Prevent remapping on the second char of actions
berknam May 23, 2020
a8bb0a6
Merge branch 'master' into remapRefactor_PoC
berknam May 23, 2020
6d12d31
Fix showCmd to now include pending keys
berknam May 23, 2020
0d9bbff
Fix commandString for registers and macros
berknam May 26, 2020
73ac14d
Implement unmap and mapclear from the .vimrc files
berknam May 26, 2020
0e15611
Merge branch 'master' into remapRefactor_PoC
berknam May 26, 2020
1759997
Add NonRecursive remaps to unmap and mapclear
berknam May 28, 2020
cf4b97f
Fix showCmd for <Del> key after count
berknam May 28, 2020
76add02
Implement maxmapdepth and Stoping remaps on error
berknam Jun 7, 2020
01f6585
Create new tests and test implementation
berknam Jun 7, 2020
4d08ee1
Merge branch 'master' into remapRefactor_PoC
berknam Jun 7, 2020
83b163a
Prevent multiple checks of the same keyslice
berknam Jun 7, 2020
a39d5f8
Allow user to press key to cancel infinite remaps
berknam Jun 8, 2020
a369ef6
Remove old code that is no longer needed
berknam Jun 8, 2020
4f314e4
Merge branch 'master' into remapRefactor_PoC
berknam Jun 12, 2020
7cde12f
Change name of timeout key, add SpecialKeys util
berknam Jun 12, 2020
158d760
Implement InsertMode virtualKeys
berknam Jun 17, 2020
fb36443
Merge branch 'master' into remapRefactor_PoC
berknam Jun 17, 2020
8142fe1
Fix bug with ambiguous remaps on different
berknam Jun 20, 2020
22b3ebb
Turn OperatorPendingMode into a pseudo mode
berknam Jun 20, 2020
574a7f6
Fix bug when the TimeoutFinished key was the last
berknam Jun 25, 2020
f48583d
Merge branch 'master' into remapRefactor_PoC
berknam Jun 30, 2020
72c0267
Fix bug with remaps after failed movement
berknam Jul 2, 2020
cae93ac
Remappings only create an undo point at the end
berknam Jul 6, 2020
853dcdc
Merge branch 'master' into remapRefactor_PoC
berknam Jul 6, 2020
4db87a6
Add `config` option on tests with remaps as well
berknam Jul 6, 2020
c682999
Merge branch 'master' into remapRefactor_PoC
berknam Jul 9, 2020
1ac20c3
Implement operatorPendingMode half cursor
berknam Jul 2, 2020
4347d4f
Add virtual key and showCmd to replaceMode
berknam Jul 16, 2020
dfef523
Merge branch 'master' into remapRefactor_PoC
berknam Jul 25, 2020
1aec4e5
Merge branch 'master' into remapRefactor_PoC
berknam Aug 14, 2020
e3c13a9
Remove unnecessary type assertions
berknam Aug 14, 2020
d038bc4
Merge branch 'master' into remapRefactor_PoC
J-Fields Aug 15, 2020
a41276e
Fix code reviews
berknam Aug 15, 2020
ff2f924
Prevent double updateView calls
berknam Aug 15, 2020
f71aced
Merge branch 'master' into remapRefactor_PoC
J-Fields Aug 16, 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
88 changes: 65 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -151,9 +151,9 @@ Here's some ideas on what you can do with neovim integration:

Custom remappings are defined on a per-mode basis.

#### `"vim.insertModeKeyBindings"`/`"vim.normalModeKeyBindings"`/`"vim.visualModeKeyBindings"`
#### `"vim.insertModeKeyBindings"`/`"vim.normalModeKeyBindings"`/`"vim.visualModeKeyBindings"`/`"vim.operatorPendingModeKeyBindings"`

- Keybinding overrides to use for insert, normal, and visual modes.
- Keybinding overrides to use for insert, normal, operatorPending and visual modes.
- Bind `jj` to `<Esc>` in insert mode:

```json
Expand All @@ -179,7 +179,7 @@ Custom remappings are defined on a per-mode basis.
- Bind `:` to show the command palette:

```json
"vim.normalModeKeyBindingsNonRecursive": [
"vim.normalModeKeyBindings": [
{
"before": [":"],
"commands": [
Expand All @@ -192,7 +192,7 @@ Custom remappings are defined on a per-mode basis.
- Bind `<leader>m` to add a bookmark and `<leader>b` to open the list of all bookmarks (using the [Bookmarks](https://marketplace.visualstudio.com/items?itemName=alefragnani.Bookmarks) extension):

```json
"vim.normalModeKeyBindingsNonRecursive": [
"vim.normalModeKeyBindings": [
{
"before": ["<leader>", "m"],
"commands": [
Expand All @@ -211,7 +211,7 @@ Custom remappings are defined on a per-mode basis.
- Bind `ZZ` to the vim command `:wq` (save and close the current file):

```json
"vim.normalModeKeyBindingsNonRecursive": [
"vim.normalModeKeyBindings": [
{
"before": ["Z", "Z"],
"commands": [
Expand All @@ -224,7 +224,7 @@ Custom remappings are defined on a per-mode basis.
- Bind `ctrl+n` to turn off search highlighting and `<leader>w` to save the current file:

```json
"vim.normalModeKeyBindingsNonRecursive": [
"vim.normalModeKeyBindings": [
{
"before":["<C-n>"],
"commands": [
Expand All @@ -240,28 +240,36 @@ Custom remappings are defined on a per-mode basis.
]
```

- Bind `p` in visual mode to paste without overriding the current register
- Bind `{` to `w` in operator pending mode makes `y{` and `d{` work like `yw` and `dw` respectively.

```json
"vim.visualModeKeyBindingsNonRecursive": [
"vim.operatorPendingModeKeyBindings": [
{
"before": [
"p",
],
"after": [
"p",
"g",
"v",
"y"
]
"before": ["{"],
"after": ["w"]
}
],
]
```

- Bind `L` to `$` and `H` to `^` in operator pending mode makes `yL` and `dH` work like `yL` and `d^` respectively.

```json
"vim.operatorPendingModeKeyBindings": [
{
"before": ["L"],
"after": ["$"]
},
{
"before": ["H"],
"after": ["^"]
}
]
```

- Bind `>` and `<` in visual mode to indent/outdent lines (repeatable)

```json
"vim.visualModeKeyBindingsNonRecursive": [
"vim.visualModeKeyBindings": [
{
"before": [
">"
Expand All @@ -284,7 +292,7 @@ Custom remappings are defined on a per-mode basis.
- Bind `<leader>vim` to clone this repository to the selected location.

```json
"vim.visualModeKeyBindingsNonRecursive": [
"vim.visualModeKeyBindings": [
{
"before": [
"<leader>", "v", "i", "m"
Expand All @@ -299,20 +307,53 @@ Custom remappings are defined on a per-mode basis.
]
```

#### `"vim.insertModeKeyBindingsNonRecursive"`/`"normalModeKeyBindingsNonRecursive"`/`"visualModeKeyBindingsNonRecursive"`
#### `"vim.insertModeKeyBindingsNonRecursive"`/`"normalModeKeyBindingsNonRecursive"`/`"visualModeKeyBindingsNonRecursive"`/`"operatorPendingModeKeyBindingsNonRecursive"`

- Non-recursive keybinding overrides to use for insert, normal, and visual modes
- _Example:_ Bind `j` to `gj`. Notice that if you attempted this binding normally, the j in gj would be expanded into gj, on and on forever. Stop this recursive expansion using insertModeKeyBindingsNonRecursive and/or normalModeKeyBindingNonRecursive.
- _Example:_ Exchange the meaning of two keys like `j` to `k` and `k` to `j` to exchange the cursor up and down commands. Notice that if you attempted this binding normally, the `j` would be replaced with `k` and the `k` would be replaced with `j`, on and on forever. When this happens 'maxmapdepth' times (default 1000) the error message 'E223 Recursive Mapping' will be thrown. Stop this recursive expansion using the NonRecursive variation of the keybindings.

```json
"vim.normalModeKeyBindingsNonRecursive": [
{
"before": ["j"],
"after": ["g", "j"]
"after": ["k"]
},
{
"before": ["k"],
"after": ["j"]
}
]
```

- Bind `(` to 'i(' in operator pending mode makes 'y(' and 'c(' work like 'yi(' and 'ci(' respectively.

```json
"vim.operatorPendingModeKeyBindingsNonRecursive": [
{
"before": ["("],
"after": ["i("]
}
]
```

- Bind `p` in visual mode to paste without overriding the current register

```json
"vim.visualModeKeyBindingsNonRecursive": [
{
"before": [
"p",
],
"after": [
"p",
"g",
"v",
"y"
]
}
],
```

#### Debugging Remappings

1. Are your configurations correct?
Expand Down Expand Up @@ -361,6 +402,7 @@ Configuration settings that have been copied from vim. Vim settings are loaded i
| vim.smartcase | Override the 'ignorecase' setting if search pattern contains uppercase characters | Boolean | true |
| vim.textwidth | Width to word-wrap when using `gq` | Number | 80 |
| vim.timeout | Timeout in milliseconds for remapped commands | Number | 1000 |
| vim.maxmapdepth | Maximum number of times a mapping is done without resulting in a character to be used. This normally catches endless mappings, like ":map x y" with ":map y x". It still does not catch ":map g wg", because the 'w' is used before the next mapping is done. | Number | 1000 |
| vim.whichwrap | Controls wrapping at beginning and end of line. Comma-separated set of keys that should wrap to next/previous line. Arrow keys are represented by `[` and `]` in insert mode, `<` and `>` in normal and visual mode. To wrap "everything", set this to `h,l,<,>,[,]`. | String | `` |
| vim.report | Threshold for reporting number of lines changed. | Number | 2 |

Expand Down
22 changes: 19 additions & 3 deletions extensionBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { configuration } from './src/configuration/configuration';
import { globalState } from './src/state/globalState';
import { taskQueue } from './src/taskQueue';
import { Register } from './src/register/register';
import { SpecialKeys } from './src/util/specialKeys';

let extensionContext: vscode.ExtensionContext;
let previousActiveEditorId: EditorIdentity | undefined = undefined;
Expand Down Expand Up @@ -434,7 +435,13 @@ export async function activate(
});

for (const boundKey of configuration.boundKeyCombinations) {
registerCommand(context, boundKey.command, () => handleKeyEvent(`${boundKey.key}`));
registerCommand(context, boundKey.command, () => {
if (['<Esc>', '<C-c>'].includes(boundKey.key)) {
checkIfRecursiveRemapping(`${boundKey.key}`);
} else {
handleKeyEvent(`${boundKey.key}`);
}
});
}

// Initialize mode handler for current active Text Editor at startup.
Expand Down Expand Up @@ -468,11 +475,11 @@ async function toggleExtension(isDisabled: boolean, compositionState: Compositio
}
let mh = await getAndUpdateModeHandler();
if (isDisabled) {
await mh.handleKeyEvent('<ExtensionDisable>');
await mh.handleKeyEvent(SpecialKeys.ExtensionDisable);
compositionState.reset();
ModeHandlerMap.clear();
} else {
await mh.handleKeyEvent('<ExtensionEnable>');
await mh.handleKeyEvent(SpecialKeys.ExtensionEnable);
}
}

Expand Down Expand Up @@ -546,6 +553,15 @@ async function handleKeyEvent(key: string): Promise<void> {
});
}

async function checkIfRecursiveRemapping(key: string): Promise<void> {
const mh = await getAndUpdateModeHandler();
if (mh.vimState.isCurrentlyPerformingRecursiveRemapping) {
mh.vimState.forceStopRecursiveRemapping = true;
} else {
handleKeyEvent(key);
}
}

function handleContentChangedFromDisk(document: vscode.TextDocument): void {
ModeHandlerMap.getAll()
.filter((modeHandler) => modeHandler.vimState.identity.fileName === document.fileName)
Expand Down
14 changes: 14 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,14 @@
"type": "array",
"markdownDescription": "Non-recursive remapped keys in Normal mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details."
},
"vim.operatorPendingModeKeyBindings": {
"type": "array",
"markdownDescription": "Remapped keys in OperatorPending mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details."
},
"vim.operatorPendingModeKeyBindingsNonRecursive": {
"type": "array",
"markdownDescription": "Non-recursive remapped keys in OperatorPending mode. Allows mapping to Vim commands or VS Code actions. See [README](https://github.com/VSCodeVim/Vim/#key-remapping) for details."
},
"vim.useCtrlKeys": {
"type": "boolean",
"markdownDescription": "Enable some Vim Ctrl key commands that override otherwise common operations, like `Ctrl+C`.",
Expand Down Expand Up @@ -500,6 +508,12 @@
"default": 1000,
"minimum": 0
},
"vim.maxmapdepth": {
"type": "number",
"description": "Maximum number of times a mapping is done without resulting in a character to be used.",
"default": 1000,
"minimum": 0
},
"vim.scroll": {
"type": "number",
"markdownDescription": "Number of lines to scroll with `Ctrl-U` and `Ctrl-D` commands. Set to 0 to use a half page scroll.",
Expand Down
6 changes: 4 additions & 2 deletions src/actions/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ export abstract class BaseAction {
public doesActionApply(vimState: VimState, keysPressed: string[]): boolean {
if (
this.mustBeFirstKey &&
vimState.recordedState.commandWithoutCountPrefix.length > keysPressed.length
(vimState.recordedState.commandWithoutCountPrefix.length > keysPressed.length ||
vimState.recordedState.operator)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is needed because the actions that must be first key can't come after an operator and since I'm resetting the commandList on modeHandler runAction function the commandWithoutCountPrefix might still have the same length so we need to check if there is no operator.

) {
return false;
}
Expand All @@ -80,7 +81,8 @@ export abstract class BaseAction {

if (
this.mustBeFirstKey &&
vimState.recordedState.commandWithoutCountPrefix.length > keysPressed.length
(vimState.recordedState.commandWithoutCountPrefix.length > keysPressed.length ||
vimState.recordedState.operator)
) {
return false;
}
Expand Down
13 changes: 8 additions & 5 deletions src/actions/commands/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import { StatusBar } from '../../statusBar';
import { reportFileInfo, reportSearch } from '../../util/statusBarTextUtils';
import { globalState } from '../../state/globalState';
import { VimError, ErrorCode } from '../../error';
import { SpecialKeys } from '../../util/specialKeys';
import _ = require('lodash');

export class DocumentContentChangeAction extends BaseAction {
Expand Down Expand Up @@ -184,7 +185,7 @@ class DisableExtension extends BaseCommand {
Mode.EasyMotionInputMode,
Mode.SurroundInputMode,
];
keys = ['<ExtensionDisable>'];
keys = [SpecialKeys.ExtensionDisable];

public async exec(position: Position, vimState: VimState): Promise<VimState> {
await vimState.setCurrentMode(Mode.Disabled);
Expand All @@ -195,7 +196,7 @@ class DisableExtension extends BaseCommand {
@RegisterAction
class EnableExtension extends BaseCommand {
modes = [Mode.Disabled];
keys = ['<ExtensionEnable>'];
keys = [SpecialKeys.ExtensionEnable];

public async exec(position: Position, vimState: VimState): Promise<VimState> {
await vimState.setCurrentMode(Mode.Normal);
Expand Down Expand Up @@ -1176,7 +1177,7 @@ export class CommandShowSearchHistory extends BaseCommand {
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
if (vimState.recordedState.commandList.includes('?')) {
if (this.keysPressed.includes('?')) {
this.direction = SearchDirection.Backward;
}
vimState.recordedState.transformations.push({
Expand Down Expand Up @@ -2458,8 +2459,10 @@ export class ActionDeleteCharWithDeleteKey extends BaseCommand {
// http://vimdoc.sourceforge.net/htmldoc/change.html#<Del>
if (vimState.recordedState.count !== 0) {
vimState.recordedState.count = Math.floor(vimState.recordedState.count / 10);
vimState.recordedState.actionKeys = vimState.recordedState.count.toString().split('');
vimState.recordedState.commandList = vimState.recordedState.count.toString().split('');

// Change actionsRunPressedKeys so that showCmd updates correctly
vimState.recordedState.actionsRunPressedKeys =
vimState.recordedState.count > 0 ? vimState.recordedState.count.toString().split('') : [];
this.isCompleteAction = false;
return vimState;
}
Expand Down
2 changes: 1 addition & 1 deletion src/cmd_line/commands/substitute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ export class SubstituteCommand extends node.CommandBase {
];

vimState.editor.revealRange(new vscode.Range(line, 0, line, 0));
vimState.editor.setDecorations(decoration.SearchHighlight, searchRanges);
vimState.editor.setDecorations(decoration.searchHighlight, searchRanges);

const prompt = `Replace with ${replacement} (${validSelections.join('/')})?`;
await vscode.window.showInputBox(
Expand Down
20 changes: 16 additions & 4 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ class Configuration implements IConfiguration {

this.leader = Notation.NormalizeKey(this.leader, this.leaderDefault);

this.clearKeyBindingsMaps();

const validatorResults = await configurationValidator.validate(configuration);

// wrap keys
Expand Down Expand Up @@ -160,6 +162,15 @@ class Configuration implements IConfiguration {
return this.cursorTypeMap[cursorStyle];
}

clearKeyBindingsMaps() {
// Clear the KeyBindingsMaps so that the previous configuration maps don't leak to this one
this.normalModeKeyBindingsMap = new Map<string, IKeyRemapping>();
this.insertModeKeyBindingsMap = new Map<string, IKeyRemapping>();
this.visualModeKeyBindingsMap = new Map<string, IKeyRemapping>();
this.commandLineModeKeyBindingsMap = new Map<string, IKeyRemapping>();
this.operatorPendingModeKeyBindingsMap = new Map<string, IKeyRemapping>();
}

handleKeys: IHandleKeys[] = [];

useSystemClipboard = false;
Expand Down Expand Up @@ -220,6 +231,8 @@ class Configuration implements IConfiguration {

timeout = 1000;

maxmapdepth = 1000;

showcmd = true;

showmodename = true;
Expand Down Expand Up @@ -368,19 +381,18 @@ class Configuration implements IConfiguration {
insertModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
normalModeKeyBindings: IKeyRemapping[] = [];
normalModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
operatorPendingModeKeyBindings: IKeyRemapping[] = [];
operatorPendingModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
visualModeKeyBindings: IKeyRemapping[] = [];
visualModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
commandLineModeKeyBindings: IKeyRemapping[] = [];
commandLineModeKeyBindingsNonRecursive: IKeyRemapping[] = [];

insertModeKeyBindingsMap: Map<string, IKeyRemapping>;
insertModeKeyBindingsNonRecursiveMap: Map<string, IKeyRemapping>;
normalModeKeyBindingsMap: Map<string, IKeyRemapping>;
normalModeKeyBindingsNonRecursiveMap: Map<string, IKeyRemapping>;
operatorPendingModeKeyBindingsMap: Map<string, IKeyRemapping>;
visualModeKeyBindingsMap: Map<string, IKeyRemapping>;
visualModeKeyBindingsNonRecursiveMap: Map<string, IKeyRemapping>;
commandLineModeKeyBindingsMap: Map<string, IKeyRemapping>;
commandLineModeKeyBindingsNonRecursiveMap: Map<string, IKeyRemapping>;

private static unproxify(obj: Object): Object {
let result = {};
Expand Down
Loading