Skip to content

Commit

Permalink
Merge pull request #160 from tienshiao/e-end-of-big-word
Browse files Browse the repository at this point in the history
Add E (end of WORD), and fix up e (end of word).
  • Loading branch information
johnfn committed Mar 1, 2016
2 parents bc43daf + 2716820 commit 663f3b5
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 82 deletions.
4 changes: 2 additions & 2 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
// "runtimeArgs": ["--harmony-default-parameters", "--harmony-rest-parameters"],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "out/src",
"outDir": "${workspaceRoot}/out/src",
"preLaunchTask": "npm"
},
{
Expand All @@ -23,7 +23,7 @@
// "runtimeArgs": ["--js-flags=\"--harmony --harmony-default-parameters\""],
"stopOnEntry": false,
"sourceMaps": true,
"outDir": "out/test",
"outDir": "${workspaceRoot}/out/test",
"preLaunchTask": "npm"
}
]
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ Status | Key | Description
:white_check_mark: | w | words forward
:white_check_mark: | W | N blank-separated WORDS forward
:white_check_mark: | e | forward to the end of the word
| E | forward to the end of the Nth blank-separated WORD
:white_check_mark: | E | forward to the end of the Nth blank-separated WORD
:white_check_mark: | b | words backward
:white_check_mark: | B | N blank-separated WORDS backward
| ge | backward to the end of the Nth word
Expand Down
1 change: 1 addition & 0 deletions src/mode/modeNormal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export class NormalMode extends Mode {
"w" : async (c) => { return c.wordRight().move(); },
"W" : async (c) => { return c.bigWordRight().move(); },
"e" : async (c) => { return c.goToEndOfCurrentWord().move(); },
"E" : async (c) => { return c.goToEndOfCurrentBigWord().move(); },
"b" : async (c) => { return c.wordLeft().move(); },
"B" : async (c) => { return c.bigWordLeft().move(); },
"}" : async (c) => { return c.goToEndOfCurrentParagraph().move(); },
Expand Down
6 changes: 6 additions & 0 deletions src/motion/motion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,12 @@ export class Motion implements vscode.Disposable {
return this;
}

public goToEndOfCurrentBigWord(): Motion {
this._position = this.position.getCurrentBigWordEnd();
this._desiredColumn = this._position.character;
return this;
}

public goToEndOfCurrentParagraph(): Motion {
this._position = this.position.getCurrentParagraphEnd();
this._desiredColumn = this.position.character;
Expand Down
137 changes: 62 additions & 75 deletions src/motion/position.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,6 @@ export enum PositionOptions {
export class Position extends vscode.Position {
private static NonWordCharacters = "/\\()\"':,.;<>~!@#$%^&*|+=[]{}`?-";
private static NonBigWordCharacters = "";
private static WordDelimiters: string[] = ["(", ")", "[", "]", "{", "}", ":", " ",
"=", "<", ">", "|", "/", "'", "\"", "~", "`", "@", "*", "+", "-", "?", ",", ".", ";"];

private _nonWordCharRegex : RegExp;
private _nonBigWordCharRegex : RegExp;
Expand Down Expand Up @@ -95,25 +93,11 @@ export class Position extends vscode.Position {
}

public getCurrentWordEnd(): Position {
if (!TextEditor.isLastLine(this) && this.character === this.getLineEnd().character) {
// go to next line
let line = TextEditor.getLineAt(this.translate(1));
return new Position(line.lineNumber, line.firstNonWhitespaceCharacterIndex, this.positionOptions);
}

let line = TextEditor.getLineAt(this);

if (Position.WordDelimiters.indexOf(line.text.charAt(this.character)) !== -1) {
return new Position(this.line, this.character + 1, this.positionOptions);
}

for (var index = this.character; index < line.text.length; index++) {
if (Position.WordDelimiters.indexOf(line.text.charAt(index)) !== -1) {
return new Position(this.line, index, this.positionOptions);
}
}
return this.getCurrentWordEndWithRegex(this._nonWordCharRegex);
}

return this.getLineEnd();
public getCurrentBigWordEnd(): Position {
return this.getCurrentWordEndWithRegex(this._nonBigWordCharRegex);
}

/**
Expand Down Expand Up @@ -233,82 +217,85 @@ export class Position extends vscode.Position {

private makeWordRegex(characterSet: string) : RegExp {
let escaped = characterSet && _.escapeRegExp(characterSet);
let segments = ["(^[\t ]*$)"];
let segments = [];
segments.push(`([^\\s${escaped}]+)`);
segments.push(`[${escaped}]+`);
return new RegExp(segments.join("|"), "g");
}
segments.push(`$^`);
let result = new RegExp(segments.join("|"), "g");

private getWordLeftWithRegex(regex: RegExp) : Position {
var workingPosition = new Position(this.line, this.character, this.positionOptions);
var currentLine = TextEditor.getLineAt(this);
var currentCharacter = this.character;

if (!TextEditor.isFirstLine(this) && this.character <= currentLine.firstNonWhitespaceCharacterIndex) {
// perform search from very end of previous line (after last character)
workingPosition = new Position(this.line - 1, this.character, this.positionOptions);
currentLine = TextEditor.getLineAt(workingPosition);
currentCharacter = workingPosition.getLineEnd().character + 1;
}
return result;
}

let positions = [];
private getAllPositions(line: string, regex: RegExp): number[] {
let positions: number[] = [];
let result = regex.exec(line);

regex.lastIndex = 0;
while (true) {
let result = regex.exec(currentLine.text);
if (result === null) {
break;
}
while (result) {
positions.push(result.index);

// Handles the case where an empty string match causes lastIndex not to advance,
// which gets us in an infinite loop.
if (result.index === regex.lastIndex) { regex.lastIndex++; }
result = regex.exec(line);
}

for (var index = 0; index < positions.length; index++) {
let position = positions[positions.length - 1 - index];
if (currentCharacter > position) {
return new Position(workingPosition.line, position, workingPosition.positionOptions);
return positions;
}

private getAllEndPositions(line: string, regex: RegExp): number[] {
let positions: number[] = [];
let result = regex.exec(line);

while (result) {
if (result[0].length) {
positions.push(result.index + result[0].length - 1);
}
}

if (this.line === 0) {
return this.getLineBegin();
} else {
let prevLine = new Position(this.line - 1, 0, this.positionOptions);
return prevLine.getLineEnd();
// Handles the case where an empty string match causes lastIndex not to advance,
// which gets us in an infinite loop.
if (result.index === regex.lastIndex) { regex.lastIndex++; }
result = regex.exec(line);
}

return positions;
}

private getWordRightWithRegex(regex: RegExp) : Position {
if (!TextEditor.isLastLine(this) && this.character >= this.getLineEnd().character) {
// go to next line
let line = TextEditor.getLineAt(this.translate(1));
return new Position(line.lineNumber, line.firstNonWhitespaceCharacterIndex, this.positionOptions);
private getWordLeftWithRegex(regex: RegExp) : Position {
for (let currentLine = this.line; currentLine >= 0; currentLine--) {
let positions = this.getAllPositions(TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, regex);
let newCharacter = _.find(positions.reverse(), index => index < this.character || currentLine !== this.line);

if (newCharacter !== undefined) {
return new Position(currentLine, newCharacter, this.positionOptions);
}
}

let currentLine = TextEditor.getLineAt(this);
let positions = [];
return new Position(0, 0, this.positionOptions).getLineBegin();
}

private getWordRightWithRegex(regex: RegExp): Position {
for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) {
let positions = this.getAllPositions(TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, regex);
let newCharacter = _.find(positions, index => index > this.character || currentLine !== this.line);

regex.lastIndex = 0;
while (true) {
let result = regex.exec(currentLine.text);
if (result === null) {
break;
if (newCharacter !== undefined) {
return new Position(currentLine, newCharacter, this.positionOptions);
}
positions.push(result.index);
}

for (var index = 0; index < positions.length; index++) {
let position = positions[index];
if (this.character < position) {
return new Position(this.line, position, this.positionOptions);
return new Position(TextEditor.getLineCount() - 1, 0, this.positionOptions).getLineEnd();
}

private getCurrentWordEndWithRegex(regex: RegExp) : Position {
for (let currentLine = this.line; currentLine < TextEditor.getLineCount(); currentLine++) {
let positions = this.getAllEndPositions(TextEditor.getLineAt(new vscode.Position(currentLine, 0)).text, regex);
let newCharacter = _.find(positions, index => index > this.character || currentLine !== this.line);

if (newCharacter !== undefined) {
return new Position(currentLine, newCharacter, this.positionOptions);
}
}

if (this.line === this.getDocumentEnd().line) {
return this.getLineEnd();
} else {
// go to next line
let line = TextEditor.getLineAt(this.translate(1));
return new Position(line.lineNumber, line.firstNonWhitespaceCharacterIndex, this.positionOptions);
}
return new Position(TextEditor.getLineCount() - 1, 0, this.positionOptions).getLineEnd();
}
}
120 changes: 116 additions & 4 deletions test/motion.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,7 +212,9 @@ suite("word motion", () => {
"if (true) {",
" return true;",
"} else {",
"",
" return false;",
" ",
"} // endif"
];

Expand All @@ -237,9 +239,21 @@ suite("word motion", () => {
assert.equal(motion.position.character, 2);
});

test("last word should move to next line stops on empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(2, 7).wordRight();
assert.equal(motion.position.line, 3);
assert.equal(motion.position.character, 0);
});

test("last word should move to next line skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 14).wordRight();
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 0);
});

test("last word on last line should go to end of document (special case!)", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 6).wordRight();
assert.equal(motion.position.line, 4);
let motion = new Motion(MotionMode.Caret).moveTo(6, 6).wordRight();
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 9);
});

Expand All @@ -264,6 +278,17 @@ suite("word motion", () => {
assert.equal(motion.position.character, 10);
});

test("first word should move to previous line, stops on empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 2).wordLeft();
assert.equal(motion.position.line, 3);
assert.equal(motion.position.character, 0);
});

test("first word should move to previous line, skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(6, 0).wordLeft();
assert.equal(motion.position.line, 4);
assert.equal(motion.position.character, 14);
});
});

suite("WORD right", () => {
Expand All @@ -278,6 +303,18 @@ suite("word motion", () => {
assert.equal(motion.position.line, 2);
assert.equal(motion.position.character, 0);
});

test("last WORD should move to next line stops on empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(2, 7).bigWordRight();
assert.equal(motion.position.line, 3);
assert.equal(motion.position.character, 0);
});

test("last WORD should move to next line skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 12).bigWordRight();
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 0);
});
});

suite("WORD left", () => {
Expand All @@ -298,18 +335,93 @@ suite("word motion", () => {
assert.equal(motion.position.line, 1);
assert.equal(motion.position.character, 9);
});

test("first WORD should move to previous line, stops on empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 2).bigWordLeft();
assert.equal(motion.position.line, 3);
assert.equal(motion.position.character, 0);
});

test("first WORD should move to previous line, skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(6, 0).bigWordLeft();
assert.equal(motion.position.line, 4);
assert.equal(motion.position.character, 9);
});
});

suite("end of word right", () => {
test("move to end of current word right", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 4).goToEndOfCurrentWord();
assert.equal(motion.position.line, 0);
assert.equal(motion.position.character, 7);
});

test("move to end of next word right", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 7).goToEndOfCurrentWord();
assert.equal(motion.position.line, 0);
assert.equal(motion.position.character, 8);
});

test("end of last word should move to next line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 10).goToEndOfCurrentWord();
assert.equal(motion.position.line, 1);
assert.equal(motion.position.character, 7);
});

test("end of last word should move to next line skips empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(2, 7).goToEndOfCurrentWord();
assert.equal(motion.position.line, 4);
assert.equal(motion.position.character, 7);
});

test("end of last word should move to next line skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 14).goToEndOfCurrentWord();
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 0);
});
});

suite("end of WORD right", () => {
test("move to end of current WORD right", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 4).goToEndOfCurrentBigWord();
assert.equal(motion.position.line, 0);
assert.equal(motion.position.character, 8);
});

test("move to end of next WORD right", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 8).goToEndOfCurrentBigWord();
assert.equal(motion.position.line, 0);
assert.equal(motion.position.character, 10);
});

test("end of last WORD should move to next line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 10).goToEndOfCurrentBigWord();
assert.equal(motion.position.line, 1);
assert.equal(motion.position.character, 7);
});

test("end of last WORD should move to next line skips empty line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(2, 7).goToEndOfCurrentWord();
assert.equal(motion.position.line, 4);
assert.equal(motion.position.character, 7);
});

test("end of last WORD should move to next line skips whitespace only line", () => {
let motion = new Motion(MotionMode.Caret).moveTo(4, 14).goToEndOfCurrentWord();
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 0);
});
});

test("line begin cursor on first non-blank character", () => {
let motion = new Motion(MotionMode.Caret).moveTo(3, 3).firstLineNonBlankChar();
let motion = new Motion(MotionMode.Caret).moveTo(4, 3).firstLineNonBlankChar();
assert.equal(motion.position.line, 0);
assert.equal(motion.position.character, 0);
});

test("last line begin cursor on first non-blank character", () => {
let motion = new Motion(MotionMode.Caret).moveTo(0, 0).lastLineNonBlankChar();
assert.equal(motion.position.line, 4);
assert.equal(motion.position.line, 6);
assert.equal(motion.position.character, 0);
});
});
Expand Down

0 comments on commit 663f3b5

Please sign in to comment.