Skip to content

Commit

Permalink
Merge pull request #993 from Metamist/easymotion
Browse files Browse the repository at this point in the history
Implemented EasyMotion plugin functionality
  • Loading branch information
johnfn authored Nov 1, 2016
2 parents f2140b7 + d261014 commit 19deb1e
Show file tree
Hide file tree
Showing 7 changed files with 676 additions and 2 deletions.
5 changes: 5 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,11 @@
"description": "Override the 'ignorecase' option if the search pattern contains upper case characters.",
"default": true
},
"vim.easymotion": {
"type": "boolean",
"description": "Enabled functionality of the EasyMotion plugin for Vim.",
"default": false
},
"vim.hlsearch": {
"type": "boolean",
"description": "When there is a previous search pattern, highlight all its matches.",
Expand Down
268 changes: 267 additions & 1 deletion src/actions/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { Tab, TabCommand } from './../cmd_line/commands/tab';
import { Configuration } from './../configuration/configuration';
import { allowVSCodeToPropagateCursorUpdatesAndReturnThem } from '../util';
import { isTextTransformation } from './../transformations/transformations';
import { EasyMotion } from './../easymotion/easymotion';
import * as vscode from 'vscode';
import * as clipboard from 'copy-paste';

Expand Down Expand Up @@ -813,6 +814,7 @@ class CommandEsc extends BaseCommand {
ModeName.VisualBlock,
ModeName.Normal,
ModeName.SearchInProgressMode,
ModeName.EasyMotionMode
];
keys = [
["<Esc>"],
Expand All @@ -828,7 +830,8 @@ class CommandEsc extends BaseCommand {
}

if (vimState.currentMode !== ModeName.Visual &&
vimState.currentMode !== ModeName.VisualLine) {
vimState.currentMode !== ModeName.VisualLine &&
vimState.currentMode !== ModeName.EasyMotionMode) {

// Normally, you don't have to iterate over all cursors,
// as that is handled for you by the state machine. ESC is
Expand All @@ -851,6 +854,12 @@ class CommandEsc extends BaseCommand {
vimState.isMultiCursor = false;
}

if (vimState.currentMode === ModeName.EasyMotionMode) {
// Escape or other termination keys were pressed, exit mode
vimState.easyMotion.clearDecorations();
vimState.currentMode = ModeName.Normal;
}

vimState.currentMode = ModeName.Normal;

if (!vimState.isMultiCursor) {
Expand Down Expand Up @@ -5308,3 +5317,260 @@ class ActionOverrideCmdAltUp extends BaseCommand {
return vimState;
}
}


abstract class BaseEasyMotionCommand extends BaseCommand {
public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
throw new Error("Not implemented!");
}

public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position {
return match.position;
}

public processMarkers(matches: EasyMotion.Match[], position: Position, vimState: VimState) {
// Clear existing markers, just in case
vimState.easyMotion.clearMarkers();

var index = 0;
for (var j = 0; j < matches.length; j++) {
var match = matches[j];
var pos = this.getMatchPosition(match, position, vimState);

if (match.position.isEqual(position)) {
continue;
}

vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, pos));
}
}

public async exec(position: Position, vimState: VimState): Promise<VimState> {
// Only execute the action if the configuration is set
if (!Configuration.getInstance().easymotion) {
return vimState;
}

// Search all occurences of the character pressed
let matches = this.getMatches(position, vimState);

// Stop if there are no matches
if (matches.length === 0) {
return vimState;
}

// Enter the EasyMotion mode and await further keys
vimState.currentMode = ModeName.EasyMotionMode;
vimState.easyMotion = new EasyMotion();

this.processMarkers(matches, position, vimState);

return vimState;
}
}

@RegisterAction
class ActionEasyMotionSearchCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "s", "<character>"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
const searchChar = this.keysPressed[3];

// Search all occurences of the character pressed
if (searchChar === " ") { // Searching for space should only find the first space
return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"));
} else {
return vimState.easyMotion.sortedSearch(position, searchChar);
}
}
}

@RegisterAction
class ActionEasyMotionFindForwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "f", "<character>"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
const searchChar = this.keysPressed[3];

// Search all occurences of the character pressed after the cursor
if (searchChar === " ") { // Searching for space should only find the first space
return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), {
min: position
});
} else {
return vimState.easyMotion.sortedSearch(position, searchChar, {
min: position
});
}
}
}

@RegisterAction
class ActionEasyMotionFindBackwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "F", "<character>"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
const searchChar = this.keysPressed[3];

// Search all occurences of the character pressed after the cursor
if (searchChar === " ") { // Searching for space should only find the first space
return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), {
max: position
});
} else {
return vimState.easyMotion.sortedSearch(position, searchChar, {
max: position
});
}
}
}

@RegisterAction
class ActionEasyMotionTilForwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "t", "<character>"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
const searchChar = this.keysPressed[3];

// Search all occurences of the character pressed after the cursor
if (searchChar === " ") { // Searching for space should only find the first space
return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), {
min: position
});
} else {
return vimState.easyMotion.sortedSearch(position, searchChar, {
min: position
});
}
}

public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position {
return new Position(match.position.line, Math.max(0, match.position.character - 1));
}
}

@RegisterAction
class ActionEasyMotionTilBackwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "T", "<character>"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
const searchChar = this.keysPressed[3];

// Search all occurences of the character pressed after the cursor
if (searchChar === " ") { // Searching for space should only find the first space
return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}"), {
max: position
});
} else {
return vimState.easyMotion.sortedSearch(position, searchChar, {
max: position
});
}
}

public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position {
return new Position(match.position.line, Math.max(0, match.position.character + 1));
}
}

@RegisterAction
class ActionEasyMotionWordCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "w"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
// Search for the beginning of all words after the cursor
return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), {
min: position
});
}
}

@RegisterAction
class ActionEasyMotionEndForwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "e"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
// Search for the end of all words after the cursor
return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), {
min: position
});
}

public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position {
return new Position(match.position.line, match.position.character + match.text.length - 1);
}
}

@RegisterAction
class ActionEasyMotionEndBackwardCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "g", "e"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
// Search for the beginning of all words before the cursor
return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), {
max: position,
});
}

public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position {
return new Position(match.position.line, match.position.character + match.text.length - 1);
}
}

@RegisterAction
class ActionEasyMotionBeginningWordCommand extends BaseEasyMotionCommand {
modes = [ModeName.Normal];
keys = ["\\", "\\", "b"];

public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] {
// Search for the beginning of all words before the cursor
return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), {
max: position,
});
}
}

@RegisterAction
class MoveEasyMotion extends BaseMovement {
modes = [ModeName.EasyMotionMode];
keys = ["<character>"];

public async execAction(position: Position, vimState: VimState): Promise<Position> {
var key = this.keysPressed[0];
if (!key) {
return position;
}

// "nail" refers to the accumulated depth keys
var nail = vimState.easyMotion.accumulation + key;
vimState.easyMotion.accumulation = nail;

// Find markers starting with "nail"
var markers = vimState.easyMotion.findMarkers(nail);
if (markers.length === 1) { // Only one found, navigate to it
var marker = markers[0];

vimState.easyMotion.clearDecorations();
vimState.currentMode = ModeName.Normal;

return marker.position;
} else {
if (markers.length === 0) { // None found, exit mode
vimState.easyMotion.clearDecorations();
vimState.currentMode = ModeName.Normal;

return position;
}
}

return position;
}
}
1 change: 1 addition & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export class Configuration {
ignorecase: boolean = true;
smartcase: boolean = true;
autoindent: boolean = true;
easymotion: boolean = false;

@overlapSetting({ codeName: "tabSize", default: 8})
tabstop: number | undefined = undefined;
Expand Down
Loading

0 comments on commit 19deb1e

Please sign in to comment.