Skip to content

Commit

Permalink
Merge branch 'master' into improve-foldfix
Browse files Browse the repository at this point in the history
  • Loading branch information
xconverge authored Jul 23, 2018
2 parents 540f2af + 4e1865c commit 71f5e4e
Show file tree
Hide file tree
Showing 10 changed files with 216 additions and 6 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -463,6 +463,13 @@ Configuration settings that have been copied from vim. Vim settings are loaded i
* What key should `<leader>` map to in key remappings?
* Type: string (Default: `\`)

#### `"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.
* Type: string (Default: ``)
* To wrap "everything", set this to `h,l,<,>,[,]`

## 🖱️ Multi-Cursor Mode

> :warning: Multi-Cursor mode is experimental. Please report issues in our [feedback thread.](https://github.com/VSCodeVim/Vim/issues/824)
Expand Down
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,11 @@
"vim.cursorStylePerMode": {
"type": "object",
"description": "Customize cursor style per mode"
},
"vim.whichwrap": {
"type": "string",
"description": "Comma-separated list of motion keys that should wrap to next/previous line.",
"default": ""
}
}
}
Expand Down
15 changes: 11 additions & 4 deletions src/actions/motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { CursorMoveByUnit, CursorMovePosition, TextEditor } from './../textEdito
import { BaseAction } from './base';
import { RegisterAction } from './base';
import { ChangeOperator, DeleteOperator, YankOperator } from './operator';
import { shouldWrapKey } from './wrapping';

export function isIMovement(o: IMovement | Position): o is IMovement {
return (o as IMovement).start !== undefined && (o as IMovement).stop !== undefined;
Expand Down Expand Up @@ -483,12 +484,14 @@ export class MarkMovement extends BaseMovement {
return mark.position;
}
}

@RegisterAction
export class MoveLeft extends BaseMovement {
keys = ['h'];

public async execAction(position: Position, vimState: VimState): Promise<Position> {
if (shouldWrapKey(vimState, this.keysPressed)) {
return position.getLeftThroughLineBreaks();
}
return position.getLeft();
}
}
Expand All @@ -514,7 +517,11 @@ class MoveRight extends BaseMovement {
keys = ['l'];

public async execAction(position: Position, vimState: VimState): Promise<Position> {
return new Position(position.line, position.character + 1);
if (shouldWrapKey(vimState, this.keysPressed)) {
const includeEol = vimState.currentMode === ModeName.Insert;
return position.getRightThroughLineBreaks(includeEol);
}
return position.getRight();
}
}

Expand Down Expand Up @@ -1890,10 +1897,10 @@ export class ArrowsInInsertMode extends BaseMovement {
newPosition = <Position>await new MoveDownArrow().execAction(position, vimState);
break;
case '<left>':
newPosition = await new MoveLeftArrow().execAction(position, vimState);
newPosition = await new MoveLeftArrow(this.keysPressed).execAction(position, vimState);
break;
case '<right>':
newPosition = await new MoveRightArrow().execAction(position, vimState);
newPosition = await new MoveRightArrow(this.keysPressed).execAction(position, vimState);
break;
default:
break;
Expand Down
26 changes: 26 additions & 0 deletions src/actions/wrapping.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { configuration } from './../configuration/configuration';
import { VimState } from './../state/vimState';
import { ModeName } from './../mode/mode';

const modes = {};

modes[ModeName.Normal] = {
'<left>': '<',
'<right>': '>',
};

modes[ModeName.Visual] = modes[ModeName.Normal];

modes[ModeName.Insert] = {
'<left>': '[',
'<right>': ']',
};

const translateMovementKey = (mode: ModeName, key: string) => {
return (modes[mode] || {})[key] || key;
};

export const shouldWrapKey = (vimState: VimState, keysPressed: string[]): boolean => {
const key = translateMovementKey(vimState.currentMode, keysPressed[0]);
return !!configuration.wrapKeys[key];
};
8 changes: 6 additions & 2 deletions src/common/motion/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -410,13 +410,17 @@ export class Position extends vscode.Position {
}
}

public getRightThroughLineBreaks(): Position {
public getRightThroughLineBreaks(includeEol = false): Position {
if (this.isAtDocumentEnd()) {
// TODO(bell)
return this;
}

if (this.getRight().isLineEnd()) {
if (this.isLineEnd()) {
return this.getDown(0);
}

if (!includeEol && this.getRight().isLineEnd()) {
return this.getDown(0);
}

Expand Down
8 changes: 8 additions & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,12 @@ class Configuration implements IConfiguration {
}
}

this.wrapKeys = {};

for (const wrapKey of this.whichwrap.split(',')) {
this.wrapKeys[wrapKey] = true;
}

// read package.json for bound keys
this.boundKeyCombinations = [];
for (let keybinding of packagejson.contributes.keybindings) {
Expand Down Expand Up @@ -302,6 +308,8 @@ class Configuration implements IConfiguration {
neovimPath = 'nvim';

substituteGlobalFlag = false;
whichwrap = '';
wrapKeys = {};

private cursorStylePerMode: IModeSpecificStrings<string> = {
normal: undefined,
Expand Down
5 changes: 5 additions & 0 deletions src/configuration/iconfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -220,4 +220,9 @@ export interface IConfiguration {
normalModeKeyBindingsNonRecursive: IKeyRemapping[];
visualModeKeyBindings: IKeyRemapping[];
visualModeKeyBindingsNonRecursive: IKeyRemapping[];

/**
* emulate whichwrap
*/
whichwrap: string;
}
12 changes: 12 additions & 0 deletions test/configuration/configuration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ suite('Configuration', () => {
},
];

configuration.whichwrap = 'h,l';

Globals.mockConfiguration = configuration;
reloadConfiguration();
});
Expand All @@ -28,4 +30,14 @@ suite('Configuration', () => {
assert.deepEqual(keybindings[0].before, [' ', 'o']);
assert.deepEqual(keybindings[0].after, ['o', '<Esc>', 'k']);
});

test('whichwrap is parsed into wrapKeys', async () => {
let configuration = srcConfiguration.configuration;

const h = 'h';
const j = 'j';

assert.equal(configuration.wrapKeys[h], true);
assert.equal(configuration.wrapKeys[j], undefined);
});
});
135 changes: 135 additions & 0 deletions test/motionLineWrapping.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Configuration } from './testConfiguration';
import { getTestingFunctions } from './testSimplifier';
import { cleanUpWorkspace, setupWorkspace } from './testUtils';

suite('motion line wrapping', () => {
let { newTest, newTestOnly } = getTestingFunctions();

teardown(cleanUpWorkspace);

suite('whichwrap enabled', () => {
setup(async () => {
let configuration = new Configuration();
configuration.tabstop = 4;
configuration.expandtab = false;
configuration.whichwrap = 'h,l,<,>,[,]';

await setupWorkspace(configuration);
});

suite('normal mode', () => {
newTest({
title: 'h wraps to previous line',
start: ['line 1', '|line 2'],
keysPressed: 'h',
end: ['line |1', 'line 2'],
});

newTest({
title: 'l wraps to next line',
start: ['line |1', 'line 2'],
keysPressed: 'l',
end: ['line 1', '|line 2'],
});

newTest({
title: '<left> wraps to previous line',
start: ['line 1', '|line 2'],
keysPressed: '<left>',
end: ['line |1', 'line 2'],
});

newTest({
title: '<right> wraps to next line',
start: ['line |1', 'line 2'],
keysPressed: '<right>',
end: ['line 1', '|line 2'],
});
});

suite('insert mode', () => {
newTest({
title: '<left> wraps to previous line',
start: ['line 1', '|line 2'],
// insert mode moves cursor one space to the left,
// but not at beginning of line
keysPressed: 'i<left>',
end: ['line 1|', 'line 2'],
});

newTest({
title: '<right> once goes to end of line',
start: ['line |1', 'line 2'],
// insert mode moves cursor one space to the left
// so <right> once should go to eol
keysPressed: 'i<right>',
end: ['line 1|', 'line 2'],
});

newTest({
title: '<right> twice wraps to next line',
start: ['line |1', 'line 2'],
// insert mode moves cursor one space to the left
// so need to go right twice to wrap
keysPressed: 'i<right><right>',
end: ['line 1', '|line 2'],
});
});
});

suite('whichwrap disabled', () => {
setup(async () => {
let configuration = new Configuration();
configuration.tabstop = 4;
configuration.expandtab = false;

await setupWorkspace(configuration);
});

suite('normal mode', () => {
newTest({
title: 'h does not wrap to previous line',
start: ['line 1', '|line 2'],
keysPressed: 'h',
end: ['line 1', '|line 2'],
});

newTest({
title: 'l does not wrap to next line',
start: ['line |1', 'line 2'],
keysPressed: 'l',
end: ['line |1', 'line 2'],
});

newTest({
title: '<left> does not wrap to previous line',
start: ['line 1', '|line 2'],
keysPressed: '<left>',
end: ['line 1', '|line 2'],
});

newTest({
title: '<right> does not wrap to next line',
start: ['line |1', 'line 2'],
keysPressed: '<right>',
end: ['line |1', 'line 2'],
});
});

suite('insert mode', () => {
newTest({
title: '<left> does not wrap to previous line',
start: ['line 1', '|line 2'],
keysPressed: 'i<left>',
end: ['line 1', '|line 2'],
});

newTest({
title: '<right> does not wrap to next line',
start: ['line |1', 'line 2'],
keysPressed: 'i<right><right>',
end: ['line 1|', 'line 2'],
});
});
});
});
1 change: 1 addition & 0 deletions test/testConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,4 +72,5 @@ export class Configuration implements IConfiguration {
normalModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
visualModeKeyBindings: IKeyRemapping[] = [];
visualModeKeyBindingsNonRecursive: IKeyRemapping[] = [];
whichwrap = '';
}

0 comments on commit 71f5e4e

Please sign in to comment.