From 0ef40491ed693a0877a3c4972a4b01220ae56d2a Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 21:14:59 +0200 Subject: [PATCH 01/22] Initial EasyMotion functionality --- package.json | 5 + src/actions/actions.ts | 184 ++++++++++++++++ src/configuration/configuration.ts | 1 + src/easymotion/easymotion.ts | 324 +++++++++++++++++++++++++++++ src/mode/mode.ts | 1 + src/mode/modeEasyMotion.ts | 13 ++ src/mode/modeHandler.ts | 8 +- 7 files changed, 535 insertions(+), 1 deletion(-) create mode 100644 src/easymotion/easymotion.ts create mode 100644 src/mode/modeEasyMotion.ts diff --git a/package.json b/package.json index 5647d782ee9..323c75302aa 100644 --- a/package.json +++ b/package.json @@ -266,6 +266,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.", diff --git a/src/actions/actions.ts b/src/actions/actions.ts index cbe80b5a1eb..cf4da3a9ad0 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -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'; @@ -5292,3 +5293,186 @@ class ActionOverrideCmdAltUp extends BaseCommand { return vimState; } } + + +if (Configuration.getInstance().easymotion) { + @RegisterAction + class ActionEasyMotionCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "s", ""]; + + public async exec(position: Position, vimState: VimState): Promise { + const searchChar = this.keysPressed[3]; + let matches = vimState.easyMotion.sortedSearch(position, searchChar); + + vimState.easyMotion.clearMarkers(); + + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); + vimState.easyMotion.addMarker(marker); + } + + vimState.easyMotion.updateDecorations(position); + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionWordCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "w"]; + + public async exec(position: Position, vimState: VimState): Promise { + let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + min: position + }); + + vimState.easyMotion.clearMarkers(); + + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); + vimState.easyMotion.addMarker(marker); + } + + vimState.easyMotion.updateDecorations(position); + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionEndCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "e"]; + + public async exec(position: Position, vimState: VimState): Promise { + let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + min: position, + useEnd: true + }); + + vimState.easyMotion.clearMarkers(); + + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); + vimState.easyMotion.addMarker(marker); + } + + vimState.easyMotion.updateDecorations(position); + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionBeginningWordCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "b"]; + + public async exec(position: Position, vimState: VimState): Promise { + let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + max: position + }); + + vimState.easyMotion.clearMarkers(); + + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); + vimState.easyMotion.addMarker(marker); + } + + vimState.easyMotion.updateDecorations(position); + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class CommandEscEasyMotionMode extends BaseCommand { + modes = [ + ModeName.EasyMotionMode + ]; + keys = [ + [""], + [""], + [""], + ]; + + public async exec(position: Position, vimState: VimState): Promise { + vimState.currentMode = ModeName.Normal; + vimState.easyMotion.exitMode(); + + return vimState; + } + } + + @RegisterAction + class MoveEasyMotion extends BaseMovement { + modes = [ModeName.EasyMotionMode]; + keys = [""]; + + public async execAction(position: Position, vimState: VimState): Promise { + var key = this.keysPressed[0]; + if (!key) { + return position; + } + + var nail = vimState.easyMotion.accumulation + key; + vimState.easyMotion.accumulation = nail; + + var markers = vimState.easyMotion.findMarkers(nail); + if (markers.length === 1) { + var marker = markers[0]; + + vimState.easyMotion.exitMode(); + + return marker.position; + } else { + if (markers.length === 0) { + vimState.easyMotion.exitMode(); + return position; + } + + vimState.easyMotion.updateDecorations(position); + } + + return position; + } + } +} \ No newline at end of file diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 84faf6c93d1..1423cac7d1a 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -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; diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts new file mode 100644 index 00000000000..deb4d324eed --- /dev/null +++ b/src/easymotion/easymotion.ts @@ -0,0 +1,324 @@ +import * as vscode from "vscode"; + +import { Position } from './../motion/position'; +import { VimState } from './../mode/modeHandler'; +import { ModeName } from './../mode/mode'; +import { Configuration } from '../../src/configuration/configuration'; +import { TextEditor } from './../textEditor'; + +export class EasyMotion { + private _vimState: VimState; + + public accumulation = ""; + + private markers: EasyMotion.Marker[]; + private decorations: any[][] = []; + + private static specialCharactersRegex: RegExp = /[\-\[\]{}()*+?.,\\\^$|#\s]/g; + private static decorationTypeCache: vscode.TextEditorDecorationType[] = []; + private static svgCache: { [code: string] : vscode.Uri } = {}; + + public static keyTable = [ + "a", "s", "d", "g", "h", "k", "l", "q", "w", "e", + "r", "t", "y", "u", "i", "o", "p", "z", "x", "c", + "v", "b", "n", "m", "f", "j" + ]; + + constructor(vimState: VimState) { + this._vimState = vimState; + } + + public static generateMarker(index: number, length: number, position: Position, markerPosition: Position): EasyMotion.Marker { + let keyTable = EasyMotion.keyTable; + var availableKeyTable = keyTable.slice(); + var keyDepthTable = [";"]; + var totalSteps = 0; + + if (length >= keyTable.length) { + var totalRemainder = Math.max(length - keyTable.length, 0); + totalSteps = Math.floor(totalRemainder / keyTable.length); + + for (var i = 0; i < totalSteps; i++) { + keyDepthTable.push(availableKeyTable.pop()); + } + } + + var prefix = ""; + if (index >= availableKeyTable.length) { + var oldLength = availableKeyTable.length; + var remainder = index - availableKeyTable.length; + + availableKeyTable = keyTable.slice(); + availableKeyTable.push(";"); + + var inverted = (length - oldLength - 1 - remainder); + var steps = Math.floor((inverted) / availableKeyTable.length); + + prefix += keyDepthTable[steps]; + + if (steps >= totalSteps) { + return new EasyMotion.Marker(prefix + availableKeyTable[remainder % availableKeyTable.length], markerPosition); + } + + var num = (availableKeyTable.length - 1 - inverted % availableKeyTable.length) % availableKeyTable.length; + return new EasyMotion.Marker(prefix + availableKeyTable[num], markerPosition); + } + + return new EasyMotion.Marker(prefix + availableKeyTable[index % availableKeyTable.length], markerPosition); + } + + public static getDecorationType(length: number): vscode.TextEditorDecorationType { + var cache = this.decorationTypeCache[length]; + if (cache) { + return cache; + } + + var width = length * 8; + var type = vscode.window.createTextEditorDecorationType({ + after: { + margin: `0 0 0 -${width}px`, + height: `14px`, + width: `${width}px` + } + }); + + this.decorationTypeCache[length] = type; + + return type; + } + + private static getSvgDataUri(code: string, backgroundColor: string, fontColor: string): vscode.Uri { + var cache = this.svgCache[code]; + if (cache) { + return cache; + } + + const width = code.length * 8 + 1; + var uri = vscode.Uri.parse( + `data:image/svg+xml;utf8,${code}`); + + this.svgCache[code] = uri; + + return uri; + } + + + public enterMode() { + this.accumulation = ""; + this._vimState.currentMode = ModeName.EasyMotionMode; + } + + public exitMode() { + this._vimState.currentMode = ModeName.Normal; + + this.accumulation = ""; + this.clearMarkers(); + this.clearDecorations(); + } + + public clearDecorations() { + var editor = vscode.window.activeTextEditor; + for (var i = 1; i <= this.decorations.length; i++) { + editor.setDecorations(EasyMotion.getDecorationType(i), []); + } + } + + public clearMarkers() { + this.markers = []; + } + + public addMarker(marker: EasyMotion.Marker) { + this.markers.push(marker); + } + + public getMarker(index: number): EasyMotion.Marker { + return this.markers[index]; + } + + public findMarkers(nail: string): EasyMotion.Marker[] { + var markers: EasyMotion.Marker[] = []; + for (var i = 0; i < this.markers.length; i++) { + var marker = this.getMarker(i); + + if (marker.name.startsWith(nail)) { + markers.push(this.getMarker(i)); + } + } + + return markers; + } + + public sortedSearch(position: Position, searchString = "", options: EasyMotion.SearchOptions = {}): EasyMotion.Match[] { + let searchRE = searchString; + if (!options.isRegex) { + searchRE = searchString.replace(EasyMotion.specialCharactersRegex, "\\$&"); + } + + const regexFlags = "g"; + let regex: RegExp; + try { + regex = new RegExp(searchRE, regexFlags); + } catch (err) { + // Couldn't compile the regexp, try again with special characters escaped + searchRE = searchString.replace(EasyMotion.specialCharactersRegex, "\\$&"); + regex = new RegExp(searchRE, regexFlags); + } + + var matches: EasyMotion.Match[] = []; + + var cursorIndex = position.character; + var prevMatch: EasyMotion.Match | undefined; + + var lineCount = TextEditor.getLineCount(); + var lineMin = options.min ? Math.max(options.min.line, 0) : 0; + var lineMax = options.max ? Math.min(options.max.line + 1, lineCount) : lineCount; + + outer: + for (let lineIdx = lineMin; lineIdx < lineMax; lineIdx++) { + const line = TextEditor.getLineAt(new Position(lineIdx, 0)).text; + var result: RegExpExecArray; + + while (result = regex.exec(line)) { + if (matches.length >= 100) { + break outer; + } + + var matchIndex = result.index; + if (options.useEnd) { + matchIndex += result[0].length - 1; + } + let pos = new Position(lineIdx, matchIndex); + + if ((options.min && pos.isBefore(options.min)) || + (options.max && pos.isAfter(options.max)) || + Math.abs(pos.line - position.line) > 100) { + + continue; + } + + if (!prevMatch || prevMatch.position.isBefore(position)) { + cursorIndex = matches.length; + } + + prevMatch = new EasyMotion.Match( + pos, + matches.length + ); + + if (pos.isEqual(position)) { + continue; + } + + matches.push(prevMatch); + } + } + + matches.sort((a: EasyMotion.Match, b: EasyMotion.Match): number => { + var diffA = cursorIndex - a.index; + var diffB = cursorIndex - b.index; + + var absDiffA = Math.abs(diffA); + var absDiffB = Math.abs(diffB); + + if (a.index < cursorIndex) absDiffA -= 0.5; + if (b.index < cursorIndex) absDiffB -= 0.5; + + return absDiffA - absDiffB; + }); + + return matches; + } + + + public updateDecorations(position: Position) { + this.clearDecorations(); + + this.decorations = []; + for (var i = 0; i < this.markers.length; i++) { + var marker = this.getMarker(i); + + if (!marker.name.startsWith(this.accumulation)) { + continue; + } + + var pos = marker.position; + var keystroke = marker.name.substr(this.accumulation.length); + + let fontColor = keystroke.length > 1 ? "orange" : "red"; + + if (!this.decorations[keystroke.length]) { + this.decorations[keystroke.length] = []; + } + + var charPos = pos.character + 1 + (keystroke.length - 1); + this.decorations[keystroke.length].push({ + range: new vscode.Range(pos.line, charPos, pos.line, charPos), + renderOptions: { + dark: { + after: { + contentIconPath: EasyMotion.getSvgDataUri(keystroke, "black", fontColor) + } + }, + light: { + after: { + contentIconPath: EasyMotion.getSvgDataUri(keystroke, "black", "white") + } + } + } + }); + } + + var editor = vscode.window.activeTextEditor; + for (var j = 1; j < this.decorations.length; j++) { + editor.setDecorations(EasyMotion.getDecorationType(j), this.decorations[j]); + } + } +} + +export module EasyMotion { + export class Marker { + private _name: string; + private _position: Position; + + constructor(name: string, position: Position) { + this._name = name; + this._position = position; + } + + public get name(): string { + return this._name; + } + + public get position(): Position { + return this._position; + } + } + + export class Match { + private _position: Position; + private _index: number; + + constructor(position: Position, index: number) { + this._position = position; + this._index = index; + } + + public get position(): Position { + return this._position; + } + + public get index(): number { + return this._index; + } + } + + export interface SearchOptions { + min?: Position; + max?: Position; + isRegex?: boolean; + useEnd?: boolean; + } +} \ No newline at end of file diff --git a/src/mode/mode.ts b/src/mode/mode.ts index 2bc427f937e..245cefa02db 100644 --- a/src/mode/mode.ts +++ b/src/mode/mode.ts @@ -9,6 +9,7 @@ export enum ModeName { VisualBlockInsertMode, SearchInProgressMode, Replace, + EasyMotionMode, } export enum VSCodeVimCursorType { diff --git a/src/mode/modeEasyMotion.ts b/src/mode/modeEasyMotion.ts new file mode 100644 index 00000000000..67623b239b2 --- /dev/null +++ b/src/mode/modeEasyMotion.ts @@ -0,0 +1,13 @@ +"use strict"; + +import { ModeName, Mode } from './mode'; +import { VSCodeVimCursorType } from './mode'; + +export class EasyMotionMode extends Mode { + public text = "EasyMotion Mode"; + public cursorType = VSCodeVimCursorType.Native; + + constructor() { + super(ModeName.EasyMotionMode); + } +} diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index b6d37257c60..d54b0c44e74 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -19,10 +19,12 @@ import { InsertVisualBlockMode } from './modeInsertVisualBlock'; import { VisualMode } from './modeVisual'; import { taskQueue } from './../taskQueue'; import { ReplaceMode } from './modeReplace'; +import { EasyMotionMode } from './modeEasyMotion'; import { SearchInProgressMode } from './modeSearchInProgress'; import { TextEditor } from './../textEditor'; import { VisualLineMode } from './modeVisualLine'; import { HistoryTracker } from './../history/historyTracker'; +import { EasyMotion } from './../easymotion/easymotion'; import { BaseMovement, BaseCommand, Actions, BaseAction, BaseOperator, DocumentContentChangeAction, CommandInsertInInsertMode, CommandInsertPreviousText, CommandQuitRecordMacro, @@ -70,6 +72,8 @@ export class VimState { public historyTracker: HistoryTracker; + public easyMotion: EasyMotion; + /** * Are multiple cursors currently present? */ @@ -447,6 +451,7 @@ export class ModeHandler implements vscode.Disposable { new ReplaceMode(), ]; this.vimState.historyTracker = new HistoryTracker(); + this.vimState.easyMotion = new EasyMotion(this.vimState); this._vimState.currentMode = ModeName.Normal; @@ -495,7 +500,8 @@ export class ModeHandler implements vscode.Disposable { } if (this.currentModeName === ModeName.VisualBlock || - this.currentModeName === ModeName.VisualBlockInsertMode) { + this.currentModeName === ModeName.VisualBlockInsertMode || + this.currentModeName === ModeName.EasyMotionMode) { // AArrgghhhh - johnfn return; From 2d3bf33cc3391f3f8dcc6a453365f9fdf6f80794 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 21:38:33 +0200 Subject: [PATCH 02/22] Fixed EasyMotion mode not added to list of modes --- src/mode/modeHandler.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index d54b0c44e74..ab0068f4923 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -449,6 +449,7 @@ export class ModeHandler implements vscode.Disposable { new VisualLineMode(), new SearchInProgressMode(), new ReplaceMode(), + new EasyMotionMode(), ]; this.vimState.historyTracker = new HistoryTracker(); this.vimState.easyMotion = new EasyMotion(this.vimState); From 20d1cc41d70194397f9c3fbd4841d9238cf99a1a Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 21:38:46 +0200 Subject: [PATCH 03/22] Made TSLint happy --- src/easymotion/easymotion.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index deb4d324eed..4cf054d93cd 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -3,7 +3,6 @@ import * as vscode from "vscode"; import { Position } from './../motion/position'; import { VimState } from './../mode/modeHandler'; import { ModeName } from './../mode/mode'; -import { Configuration } from '../../src/configuration/configuration'; import { TextEditor } from './../textEditor'; export class EasyMotion { @@ -179,9 +178,9 @@ export class EasyMotion { outer: for (let lineIdx = lineMin; lineIdx < lineMax; lineIdx++) { const line = TextEditor.getLineAt(new Position(lineIdx, 0)).text; - var result: RegExpExecArray; + var result = regex.exec(line); - while (result = regex.exec(line)) { + while (result) { if (matches.length >= 100) { break outer; } @@ -196,6 +195,7 @@ export class EasyMotion { (options.max && pos.isAfter(options.max)) || Math.abs(pos.line - position.line) > 100) { + result = regex.exec(line); continue; } @@ -209,10 +209,13 @@ export class EasyMotion { ); if (pos.isEqual(position)) { + result = regex.exec(line); continue; } matches.push(prevMatch); + + result = regex.exec(line); } } @@ -223,8 +226,12 @@ export class EasyMotion { var absDiffA = Math.abs(diffA); var absDiffB = Math.abs(diffB); - if (a.index < cursorIndex) absDiffA -= 0.5; - if (b.index < cursorIndex) absDiffB -= 0.5; + if (a.index < cursorIndex) { + absDiffA -= 0.5; + } + if (b.index < cursorIndex) { + absDiffB -= 0.5; + } return absDiffA - absDiffB; }); From 16a1c7457046b3d61c54c8da08fb930999cccfa2 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 21:41:01 +0200 Subject: [PATCH 04/22] Fixed some code indentation mistakes --- src/actions/actions.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index cf4da3a9ad0..fad75fbe27b 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5335,7 +5335,7 @@ if (Configuration.getInstance().easymotion) { let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, min: position - }); + }); vimState.easyMotion.clearMarkers(); @@ -5368,7 +5368,7 @@ if (Configuration.getInstance().easymotion) { isRegex: true, min: position, useEnd: true - }); + }); vimState.easyMotion.clearMarkers(); @@ -5400,7 +5400,8 @@ if (Configuration.getInstance().easymotion) { let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, max: position - }); + }); + vimState.easyMotion.clearMarkers(); From ffa8c7555bc92389c882e0eae615c6993a7195b4 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 21:42:06 +0200 Subject: [PATCH 05/22] Added check if no matches are found --- src/actions/actions.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index fad75fbe27b..ac8efa74b8d 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5305,6 +5305,8 @@ if (Configuration.getInstance().easymotion) { const searchChar = this.keysPressed[3]; let matches = vimState.easyMotion.sortedSearch(position, searchChar); + if (matches.length == 0) return vimState; + vimState.easyMotion.clearMarkers(); var index = 0; @@ -5337,6 +5339,8 @@ if (Configuration.getInstance().easymotion) { min: position }); + if (matches.length == 0) return vimState; + vimState.easyMotion.clearMarkers(); var index = 0; @@ -5370,6 +5374,8 @@ if (Configuration.getInstance().easymotion) { useEnd: true }); + if (matches.length == 0) return vimState; + vimState.easyMotion.clearMarkers(); var index = 0; @@ -5402,6 +5408,7 @@ if (Configuration.getInstance().easymotion) { max: position }); + if (matches.length == 0) return vimState; vimState.easyMotion.clearMarkers(); From f40e3eb0827be9c6ca28f0c0383dbbdbaa4ec4c3 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 22:10:52 +0200 Subject: [PATCH 06/22] Added comments --- src/actions/actions.ts | 45 ++++++++++++++++++++++++------- src/easymotion/easymotion.ts | 51 +++++++++++++++++++++++++++++++----- 2 files changed, 79 insertions(+), 17 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index ac8efa74b8d..b3131078cdb 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5295,6 +5295,7 @@ class ActionOverrideCmdAltUp extends BaseCommand { } +// Only register the EasyMotion actions if the configuration is set if (Configuration.getInstance().easymotion) { @RegisterAction class ActionEasyMotionCommand extends BaseCommand { @@ -5303,12 +5304,16 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed let matches = vimState.easyMotion.sortedSearch(position, searchChar); + // Stop if there are no matches if (matches.length == 0) return vimState; + // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); + // TODO: Implement this in EasyMotion class to help out with DRY var index = 0; for (var j = 0; j < matches.length; j++) { var match = matches[j]; @@ -5317,11 +5322,12 @@ if (Configuration.getInstance().easymotion) { continue; } - let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); - vimState.easyMotion.addMarker(marker); + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); } + // Let EasyMotion update all decorations vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys vimState.easyMotion.enterMode(); return vimState; @@ -5334,15 +5340,19 @@ if (Configuration.getInstance().easymotion) { keys = ["\\", "\\", "w"]; public async exec(position: Position, vimState: VimState): Promise { + // Search for the beginning of all words after the cursor let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, min: position }); + // Stop if there are no matches if (matches.length == 0) return vimState; + // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); + // TODO: Implement this in EasyMotion class to help out with DRY var index = 0; for (var j = 0; j < matches.length; j++) { var match = matches[j]; @@ -5351,11 +5361,12 @@ if (Configuration.getInstance().easymotion) { continue; } - let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); - vimState.easyMotion.addMarker(marker); + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); } + // Let EasyMotion update all decorations vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys vimState.easyMotion.enterMode(); return vimState; @@ -5368,16 +5379,20 @@ if (Configuration.getInstance().easymotion) { keys = ["\\", "\\", "e"]; public async exec(position: Position, vimState: VimState): Promise { + // Search for the end of all words after the cursor let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, min: position, useEnd: true }); + // Stop if there are no matches if (matches.length == 0) return vimState; + // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); + // TODO: Implement this in EasyMotion class to help out with DRY var index = 0; for (var j = 0; j < matches.length; j++) { var match = matches[j]; @@ -5386,11 +5401,12 @@ if (Configuration.getInstance().easymotion) { continue; } - let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); - vimState.easyMotion.addMarker(marker); + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); } + // Let EasyMotion update all decorations vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys vimState.easyMotion.enterMode(); return vimState; @@ -5403,15 +5419,19 @@ if (Configuration.getInstance().easymotion) { keys = ["\\", "\\", "b"]; public async exec(position: Position, vimState: VimState): Promise { + // Search for the beginning of all words before the cursor let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, max: position }); + // Stop if there are no matches if (matches.length == 0) return vimState; + // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); + // TODO: Implement this in EasyMotion class to help out with DRY var index = 0; for (var j = 0; j < matches.length; j++) { var match = matches[j]; @@ -5420,11 +5440,12 @@ if (Configuration.getInstance().easymotion) { continue; } - let marker = EasyMotion.generateMarker(index++, matches.length, position, match.position); - vimState.easyMotion.addMarker(marker); + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); } + // Let EasyMotion update all decorations vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys vimState.easyMotion.enterMode(); return vimState; @@ -5443,6 +5464,7 @@ if (Configuration.getInstance().easymotion) { ]; public async exec(position: Position, vimState: VimState): Promise { + // Escape or other termination keys were pressed, exit mode vimState.currentMode = ModeName.Normal; vimState.easyMotion.exitMode(); @@ -5461,22 +5483,25 @@ if (Configuration.getInstance().easymotion) { 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) { + if (markers.length === 1) { // Only one found, navigate to it var marker = markers[0]; vimState.easyMotion.exitMode(); return marker.position; } else { - if (markers.length === 0) { + if (markers.length === 0) { // None found, exit mode vimState.easyMotion.exitMode(); return position; } + // Update decorations with new markers at a different depth level vimState.easyMotion.updateDecorations(position); } diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index 4cf054d93cd..2dbf13b6607 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -8,15 +8,21 @@ import { TextEditor } from './../textEditor'; export class EasyMotion { private _vimState: VimState; + // Refers to the accumulated keys for depth navigation public accumulation = ""; + // Array of all markers and decorations private markers: EasyMotion.Marker[]; private decorations: any[][] = []; + // TODO: For future motions. private static specialCharactersRegex: RegExp = /[\-\[\]{}()*+?.,\\\^$|#\s]/g; + + // Caches for decorations private static decorationTypeCache: vscode.TextEditorDecorationType[] = []; private static svgCache: { [code: string] : vscode.Uri } = {}; + // The key sequence for marker name generation public static keyTable = [ "a", "s", "d", "g", "h", "k", "l", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "z", "x", "c", @@ -27,9 +33,12 @@ export class EasyMotion { this._vimState = vimState; } + // Generate a marker following a sequence for the name and depth levels public static generateMarker(index: number, length: number, position: Position, markerPosition: Position): EasyMotion.Marker { let keyTable = EasyMotion.keyTable; var availableKeyTable = keyTable.slice(); + + // Depth table should always include a ; var keyDepthTable = [";"]; var totalSteps = 0; @@ -44,28 +53,38 @@ export class EasyMotion { var prefix = ""; if (index >= availableKeyTable.length) { + // Length of available keys before reset and ";" var oldLength = availableKeyTable.length; + // The index that remains after taking away the first-level depth markers var remainder = index - availableKeyTable.length; + // ";" can be used as the last marker key, when inside a marker with depth. Reset to available keys and add ";" availableKeyTable = keyTable.slice(); availableKeyTable.push(";"); + // Depth index counts down instead of up var inverted = (length - oldLength - 1 - remainder); var steps = Math.floor((inverted) / availableKeyTable.length); + // Add the key to the prefix prefix += keyDepthTable[steps]; + // Check if we're on the last depth level if (steps >= totalSteps) { + // Return the proper key for this index return new EasyMotion.Marker(prefix + availableKeyTable[remainder % availableKeyTable.length], markerPosition); } + // Return the proper index for depths earlier than the last one, including prefix var num = (availableKeyTable.length - 1 - inverted % availableKeyTable.length) % availableKeyTable.length; return new EasyMotion.Marker(prefix + availableKeyTable[num], markerPosition); } + // Return the last key in the marker, including prefix return new EasyMotion.Marker(prefix + availableKeyTable[index % availableKeyTable.length], markerPosition); } + // Create and cache decoration types for different marker lengths public static getDecorationType(length: number): vscode.TextEditorDecorationType { var cache = this.decorationTypeCache[length]; if (cache) { @@ -86,6 +105,7 @@ export class EasyMotion { return type; } + // Create and cache the SVG data URI for different marker codes and colors private static getSvgDataUri(code: string, backgroundColor: string, fontColor: string): vscode.Uri { var cache = this.svgCache[code]; if (cache) { @@ -104,12 +124,13 @@ export class EasyMotion { return uri; } - + // Enter EasyMotion mode public enterMode() { this.accumulation = ""; this._vimState.currentMode = ModeName.EasyMotionMode; } + // Exit EasyMotion mode and clean up public exitMode() { this._vimState.currentMode = ModeName.Normal; @@ -118,6 +139,7 @@ export class EasyMotion { this.clearDecorations(); } + // Clear all decorations public clearDecorations() { var editor = vscode.window.activeTextEditor; for (var i = 1; i <= this.decorations.length; i++) { @@ -125,6 +147,7 @@ export class EasyMotion { } } + // Clear all markers public clearMarkers() { this.markers = []; } @@ -137,6 +160,7 @@ export class EasyMotion { return this.markers[index]; } + // Find markers beginning with a string public findMarkers(nail: string): EasyMotion.Marker[] { var markers: EasyMotion.Marker[] = []; for (var i = 0; i < this.markers.length; i++) { @@ -150,8 +174,10 @@ export class EasyMotion { return markers; } + // Search and sort using the index of a match compared to the index of position (usually the cursor) public sortedSearch(position: Position, searchString = "", options: EasyMotion.SearchOptions = {}): EasyMotion.Match[] { let searchRE = searchString; + // Regex needs to be escaped if (!options.isRegex) { searchRE = searchString.replace(EasyMotion.specialCharactersRegex, "\\$&"); } @@ -168,9 +194,11 @@ export class EasyMotion { var matches: EasyMotion.Match[] = []; + // Cursor index refers to the index of the marker that is on or to the right of the cursor var cursorIndex = position.character; var prevMatch: EasyMotion.Match | undefined; + // Calculate the min/max bounds for the search var lineCount = TextEditor.getLineCount(); var lineMin = options.min ? Math.max(options.min.line, 0) : 0; var lineMax = options.max ? Math.min(options.max.line + 1, lineCount) : lineCount; @@ -186,19 +214,21 @@ export class EasyMotion { } var matchIndex = result.index; - if (options.useEnd) { + if (options.useEnd) { // Return the end of the result rather than the start matchIndex += result[0].length - 1; } let pos = new Position(lineIdx, matchIndex); + // Check if match is within bounds if ((options.min && pos.isBefore(options.min)) || (options.max && pos.isAfter(options.max)) || - Math.abs(pos.line - position.line) > 100) { + Math.abs(pos.line - position.line) > 100) { // Stop searching after 100 lines in both directions result = regex.exec(line); continue; } + // Update cursor index to the marker on the right side of the cursor if (!prevMatch || prevMatch.position.isBefore(position)) { cursorIndex = matches.length; } @@ -208,6 +238,7 @@ export class EasyMotion { matches.length ); + // Matches on the cursor position should be ignored if (pos.isEqual(position)) { result = regex.exec(line); continue; @@ -219,6 +250,7 @@ export class EasyMotion { } } + // Sort by the index distance from the cursor index matches.sort((a: EasyMotion.Match, b: EasyMotion.Match): number => { var diffA = cursorIndex - a.index; var diffB = cursorIndex - b.index; @@ -226,6 +258,7 @@ export class EasyMotion { var absDiffA = Math.abs(diffA); var absDiffB = Math.abs(diffB); + // Prioritize the matches on the right side of the cursor index if (a.index < cursorIndex) { absDiffA -= 0.5; } @@ -247,11 +280,13 @@ export class EasyMotion { for (var i = 0; i < this.markers.length; i++) { var marker = this.getMarker(i); + // Ignore markers that do not start with the accumulated depth level if (!marker.name.startsWith(this.accumulation)) { continue; } var pos = marker.position; + // Get keys after the depth we're at var keystroke = marker.name.substr(this.accumulation.length); let fontColor = keystroke.length > 1 ? "orange" : "red"; @@ -260,6 +295,7 @@ export class EasyMotion { this.decorations[keystroke.length] = []; } + // Position should be offsetted by the length of the keystroke to prevent hiding behind the gutter var charPos = pos.character + 1 + (keystroke.length - 1); this.decorations[keystroke.length].push({ range: new vscode.Range(pos.line, charPos, pos.line, charPos), @@ -278,6 +314,7 @@ export class EasyMotion { }); } + // Set the decorations for all the different marker lengths var editor = vscode.window.activeTextEditor; for (var j = 1; j < this.decorations.length; j++) { editor.setDecorations(EasyMotion.getDecorationType(j), this.decorations[j]); @@ -323,9 +360,9 @@ export module EasyMotion { } export interface SearchOptions { - min?: Position; - max?: Position; - isRegex?: boolean; - useEnd?: boolean; + min?: Position; // The minimum bound of the search + max?: Position; // The maximum bound of the search + isRegex?: boolean; // Is the search string a regular expression? + useEnd?: boolean; // Use the end of the match rather than start } } \ No newline at end of file From 474d971c26a3fae8b5fe9a2e65c3db2cd24b24cc Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 22:14:12 +0200 Subject: [PATCH 07/22] Made TSLint happy --- src/actions/actions.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index b3131078cdb..514a7bd860f 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5308,7 +5308,9 @@ if (Configuration.getInstance().easymotion) { let matches = vimState.easyMotion.sortedSearch(position, searchChar); // Stop if there are no matches - if (matches.length == 0) return vimState; + if (matches.length === 0) { + return vimState; + } // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); @@ -5347,7 +5349,9 @@ if (Configuration.getInstance().easymotion) { }); // Stop if there are no matches - if (matches.length == 0) return vimState; + if (matches.length === 0) { + return vimState; + } // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); @@ -5387,7 +5391,9 @@ if (Configuration.getInstance().easymotion) { }); // Stop if there are no matches - if (matches.length == 0) return vimState; + if (matches.length === 0) { + return vimState; + } // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); @@ -5426,7 +5432,9 @@ if (Configuration.getInstance().easymotion) { }); // Stop if there are no matches - if (matches.length == 0) return vimState; + if (matches.length === 0) { + return vimState; + } // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); From be172e791892cb5fb8f137950d433937b5b72b66 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 22:25:20 +0200 Subject: [PATCH 08/22] Implemented f, F, t, T motions --- src/actions/actions.ts | 166 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 514a7bd860f..a732fc73890 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5336,6 +5336,172 @@ if (Configuration.getInstance().easymotion) { } } + @RegisterAction + class ActionEasyMotionFindForwardCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "f", ""]; + + public async exec(position: Position, vimState: VimState): Promise { + const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed + let matches = vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); + + // Stop if there are no matches + if (matches.length === 0) { + return vimState; + } + + // Clear existing markers, just in case + vimState.easyMotion.clearMarkers(); + + // TODO: Implement this in EasyMotion class to help out with DRY + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); + } + + // Let EasyMotion update all decorations + vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionFindBackwardCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "F", ""]; + + public async exec(position: Position, vimState: VimState): Promise { + const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed + let matches = vimState.easyMotion.sortedSearch(position, searchChar, { + max: position + }); + + // Stop if there are no matches + if (matches.length === 0) { + return vimState; + } + + // Clear existing markers, just in case + vimState.easyMotion.clearMarkers(); + + // TODO: Implement this in EasyMotion class to help out with DRY + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + + if (match.position.isEqual(position)) { + continue; + } + + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); + } + + // Let EasyMotion update all decorations + vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionTilForwardCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "t", ""]; + + public async exec(position: Position, vimState: VimState): Promise { + const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed + let matches = vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); + + // Stop if there are no matches + if (matches.length === 0) { + return vimState; + } + + // Clear existing markers, just in case + vimState.easyMotion.clearMarkers(); + + // TODO: Implement this in EasyMotion class to help out with DRY + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + var matchPos = new Position(match.position.line, Math.max(0, match.position.character - 1)); + + if (match.position.isEqual(position)) { + continue; + } + + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, matchPos)); + } + + // Let EasyMotion update all decorations + vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys + vimState.easyMotion.enterMode(); + + return vimState; + } + } + + @RegisterAction + class ActionEasyMotionTilBackwardCommand extends BaseCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "T", ""]; + + public async exec(position: Position, vimState: VimState): Promise { + const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed + let matches = vimState.easyMotion.sortedSearch(position, searchChar, { + max: position + }); + + // Stop if there are no matches + if (matches.length === 0) { + return vimState; + } + + // Clear existing markers, just in case + vimState.easyMotion.clearMarkers(); + + // TODO: Implement this in EasyMotion class to help out with DRY + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + var matchPos = new Position(match.position.line, Math.max(0, match.position.character + 1)); + + if (match.position.isEqual(position)) { + continue; + } + + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, matchPos)); + } + + // Let EasyMotion update all decorations + vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys + vimState.easyMotion.enterMode(); + + return vimState; + } + } + @RegisterAction class ActionEasyMotionWordCommand extends BaseCommand { modes = [ModeName.Normal]; From d3c4440fb17907baa94cd392d854b34340d290ef Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 22:29:46 +0200 Subject: [PATCH 09/22] Changed up the comments --- src/easymotion/easymotion.ts | 56 +++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 14 deletions(-) diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index 2dbf13b6607..f05edfd0c3e 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -8,21 +8,31 @@ import { TextEditor } from './../textEditor'; export class EasyMotion { private _vimState: VimState; - // Refers to the accumulated keys for depth navigation + /** + * Refers to the accumulated keys for depth navigation + */ public accumulation = ""; - // Array of all markers and decorations + /** + * Array of all markers and decorations + */ private markers: EasyMotion.Marker[]; private decorations: any[][] = []; - // TODO: For future motions. + /** + * TODO: For future motions + */ private static specialCharactersRegex: RegExp = /[\-\[\]{}()*+?.,\\\^$|#\s]/g; - // Caches for decorations + /** + * Caches for decorations + */ private static decorationTypeCache: vscode.TextEditorDecorationType[] = []; private static svgCache: { [code: string] : vscode.Uri } = {}; - // The key sequence for marker name generation + /** + * The key sequence for marker name generation + */ public static keyTable = [ "a", "s", "d", "g", "h", "k", "l", "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "z", "x", "c", @@ -33,7 +43,9 @@ export class EasyMotion { this._vimState = vimState; } - // Generate a marker following a sequence for the name and depth levels + /** + * Generate a marker following a sequence for the name and depth levels + */ public static generateMarker(index: number, length: number, position: Position, markerPosition: Position): EasyMotion.Marker { let keyTable = EasyMotion.keyTable; var availableKeyTable = keyTable.slice(); @@ -84,7 +96,9 @@ export class EasyMotion { return new EasyMotion.Marker(prefix + availableKeyTable[index % availableKeyTable.length], markerPosition); } - // Create and cache decoration types for different marker lengths + /** + * Create and cache decoration types for different marker lengths + */ public static getDecorationType(length: number): vscode.TextEditorDecorationType { var cache = this.decorationTypeCache[length]; if (cache) { @@ -105,7 +119,9 @@ export class EasyMotion { return type; } - // Create and cache the SVG data URI for different marker codes and colors + /** + * Create and cache the SVG data URI for different marker codes and colors + */ private static getSvgDataUri(code: string, backgroundColor: string, fontColor: string): vscode.Uri { var cache = this.svgCache[code]; if (cache) { @@ -124,13 +140,17 @@ export class EasyMotion { return uri; } - // Enter EasyMotion mode + /** + * Enter EasyMotion mode + */ public enterMode() { this.accumulation = ""; this._vimState.currentMode = ModeName.EasyMotionMode; } - // Exit EasyMotion mode and clean up + /** + * Exit EasyMotion mode and clean up + */ public exitMode() { this._vimState.currentMode = ModeName.Normal; @@ -139,7 +159,9 @@ export class EasyMotion { this.clearDecorations(); } - // Clear all decorations + /** + * Clear all decorations + */ public clearDecorations() { var editor = vscode.window.activeTextEditor; for (var i = 1; i <= this.decorations.length; i++) { @@ -147,7 +169,9 @@ export class EasyMotion { } } - // Clear all markers + /** + * Clear all markers + */ public clearMarkers() { this.markers = []; } @@ -160,7 +184,9 @@ export class EasyMotion { return this.markers[index]; } - // Find markers beginning with a string + /** + * Find markers beginning with a string + */ public findMarkers(nail: string): EasyMotion.Marker[] { var markers: EasyMotion.Marker[] = []; for (var i = 0; i < this.markers.length; i++) { @@ -174,7 +200,9 @@ export class EasyMotion { return markers; } - // Search and sort using the index of a match compared to the index of position (usually the cursor) + /** + * Search and sort using the index of a match compared to the index of position (usually the cursor) + */ public sortedSearch(position: Position, searchString = "", options: EasyMotion.SearchOptions = {}): EasyMotion.Match[] { let searchRE = searchString; // Regex needs to be escaped From 4c22a4a0c6fb3dccb65bffd92092c503db777767 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 22:59:28 +0200 Subject: [PATCH 10/22] Increased max matches to 1000 --- src/easymotion/easymotion.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index f05edfd0c3e..c0c5149cef2 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -237,7 +237,7 @@ export class EasyMotion { var result = regex.exec(line); while (result) { - if (matches.length >= 100) { + if (matches.length >= 1000) { break outer; } @@ -345,7 +345,9 @@ export class EasyMotion { // Set the decorations for all the different marker lengths var editor = vscode.window.activeTextEditor; for (var j = 1; j < this.decorations.length; j++) { - editor.setDecorations(EasyMotion.getDecorationType(j), this.decorations[j]); + if (this.decorations[j]) { + editor.setDecorations(EasyMotion.getDecorationType(j), this.decorations[j]); + } } } } From 67e1a2855f521de6c7e0273e9e677051cc44b567 Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 23:00:54 +0200 Subject: [PATCH 11/22] Fixed searching for space should only match first --- src/actions/actions.ts | 68 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 55 insertions(+), 13 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index a732fc73890..28d1ff41450 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5305,7 +5305,13 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches = vimState.easyMotion.sortedSearch(position, searchChar); + let matches: EasyMotion.Match[]; + if (searchChar === " ") { // Searching for space should only find the first space + matches = vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); + } + else { + matches = vimState.easyMotion.sortedSearch(position, searchChar); + } // Stop if there are no matches if (matches.length === 0) { @@ -5344,9 +5350,18 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches = vimState.easyMotion.sortedSearch(position, searchChar, { - min: position - }); + let matches: EasyMotion.Match[]; + if (searchChar === " ") { // Searching for space should only find the first space + matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + isRegex: true, + min: position + }); + } + else { + matches = vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); + } // Stop if there are no matches if (matches.length === 0) { @@ -5385,9 +5400,18 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches = vimState.easyMotion.sortedSearch(position, searchChar, { - max: position - }); + let matches: EasyMotion.Match[]; + if (searchChar === " ") { // Searching for space should only find the first space + matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + isRegex: true, + max: position + }); + } + else { + matches = vimState.easyMotion.sortedSearch(position, searchChar, { + max: position + }); + } // Stop if there are no matches if (matches.length === 0) { @@ -5426,9 +5450,18 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches = vimState.easyMotion.sortedSearch(position, searchChar, { - min: position - }); + let matches: EasyMotion.Match[]; + if (searchChar === " ") { // Searching for space should only find the first space + matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + isRegex: true, + min: position + }); + } + else { + matches = vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); + } // Stop if there are no matches if (matches.length === 0) { @@ -5468,9 +5501,18 @@ if (Configuration.getInstance().easymotion) { public async exec(position: Position, vimState: VimState): Promise { const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches = vimState.easyMotion.sortedSearch(position, searchChar, { - max: position - }); + let matches: EasyMotion.Match[]; + if (searchChar === " ") { // Searching for space should only find the first space + matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + isRegex: true, + min: position + }); + } + else { + matches = vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); + } // Stop if there are no matches if (matches.length === 0) { From 2d995f10f4f3bc01f07920ce2f06713445794d1d Mon Sep 17 00:00:00 2001 From: Metamist Date: Wed, 26 Oct 2016 23:02:52 +0200 Subject: [PATCH 12/22] Made TSLint happy --- src/actions/actions.ts | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 28d1ff41450..347c2226a30 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5308,8 +5308,7 @@ if (Configuration.getInstance().easymotion) { let matches: EasyMotion.Match[]; if (searchChar === " ") { // Searching for space should only find the first space matches = vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); - } - else { + } else { matches = vimState.easyMotion.sortedSearch(position, searchChar); } @@ -5356,8 +5355,7 @@ if (Configuration.getInstance().easymotion) { isRegex: true, min: position }); - } - else { + } else { matches = vimState.easyMotion.sortedSearch(position, searchChar, { min: position }); @@ -5406,8 +5404,7 @@ if (Configuration.getInstance().easymotion) { isRegex: true, max: position }); - } - else { + } else { matches = vimState.easyMotion.sortedSearch(position, searchChar, { max: position }); @@ -5456,8 +5453,7 @@ if (Configuration.getInstance().easymotion) { isRegex: true, min: position }); - } - else { + } else { matches = vimState.easyMotion.sortedSearch(position, searchChar, { min: position }); @@ -5507,8 +5503,7 @@ if (Configuration.getInstance().easymotion) { isRegex: true, min: position }); - } - else { + } else { matches = vimState.easyMotion.sortedSearch(position, searchChar, { min: position }); From 0380881f84883bdca8b27497b35fb01f0f820d2c Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 14:49:17 +0200 Subject: [PATCH 13/22] Abstracted out EasyMotion actions --- src/actions/actions.ts | 332 +++++++++++------------------------------ 1 file changed, 85 insertions(+), 247 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 347c2226a30..d43156f1d1b 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -124,6 +124,8 @@ export class BaseAction { * Is this action valid in the current Vim state? */ public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { + console.log(this); + if (this.modes.indexOf(vimState.currentMode) === -1) { return false; } if (!compareKeypressSequence(this.keys, keysPressed)) { return false; } if (vimState.recordedState.actionsRun.length > 0 && @@ -5297,20 +5299,32 @@ class ActionOverrideCmdAltUp extends BaseCommand { // Only register the EasyMotion actions if the configuration is set if (Configuration.getInstance().easymotion) { - @RegisterAction - class ActionEasyMotionCommand extends BaseCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "s", ""]; + 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) { + 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 { - const searchChar = this.keysPressed[3]; // Search all occurences of the character pressed - let matches: EasyMotion.Match[]; - if (searchChar === " ") { // Searching for space should only find the first space - matches = vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); - } else { - matches = vimState.easyMotion.sortedSearch(position, searchChar); - } + let matches = this.getMatches(position, vimState); // Stop if there are no matches if (matches.length === 0) { @@ -5320,17 +5334,7 @@ if (Configuration.getInstance().easymotion) { // Clear existing markers, just in case vimState.easyMotion.clearMarkers(); - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } + this.processMarkers(matches, position, vimState); // Let EasyMotion update all decorations vimState.easyMotion.updateDecorations(position); @@ -5342,324 +5346,158 @@ if (Configuration.getInstance().easymotion) { } @RegisterAction - class ActionEasyMotionFindForwardCommand extends BaseCommand { + class ActionEasyMotionSearchCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; - keys = ["\\", "\\", "f", ""]; + keys = ["\\", "\\", "s", ""]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { const searchChar = this.keysPressed[3]; + // Search all occurences of the character pressed - let matches: EasyMotion.Match[]; if (searchChar === " ") { // Searching for space should only find the first space - matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); + } else { + return vimState.easyMotion.sortedSearch(position, searchChar); + } + } + } + + @RegisterAction + class ActionEasyMotionFindForwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "f", ""]; + + 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, " {1,}", { isRegex: true, min: position }); } else { - matches = vimState.easyMotion.sortedSearch(position, searchChar, { + return vimState.easyMotion.sortedSearch(position, searchChar, { min: position }); } - - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; } } @RegisterAction - class ActionEasyMotionFindBackwardCommand extends BaseCommand { + class ActionEasyMotionFindBackwardCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "F", ""]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { const searchChar = this.keysPressed[3]; - // Search all occurences of the character pressed - let matches: EasyMotion.Match[]; + + // Search all occurences of the character pressed after the cursor if (searchChar === " ") { // Searching for space should only find the first space - matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true, max: position }); } else { - matches = vimState.easyMotion.sortedSearch(position, searchChar, { + return vimState.easyMotion.sortedSearch(position, searchChar, { max: position }); } - - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; } } @RegisterAction - class ActionEasyMotionTilForwardCommand extends BaseCommand { + class ActionEasyMotionTilForwardCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "t", ""]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { const searchChar = this.keysPressed[3]; - // Search all occurences of the character pressed - let matches: EasyMotion.Match[]; + + // Search all occurences of the character pressed after the cursor if (searchChar === " ") { // Searching for space should only find the first space - matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true, min: position }); } else { - matches = vimState.easyMotion.sortedSearch(position, searchChar, { + return vimState.easyMotion.sortedSearch(position, searchChar, { min: position }); } + } - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - var matchPos = new Position(match.position.line, Math.max(0, match.position.character - 1)); - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, matchPos)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; + 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 BaseCommand { + class ActionEasyMotionTilBackwardCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "T", ""]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { const searchChar = this.keysPressed[3]; - // Search all occurences of the character pressed - let matches: EasyMotion.Match[]; + + // Search all occurences of the character pressed after the cursor if (searchChar === " ") { // Searching for space should only find the first space - matches = vimState.easyMotion.sortedSearch(position, " {1,}", { + return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true, - min: position + max: position }); } else { - matches = vimState.easyMotion.sortedSearch(position, searchChar, { - min: position + return vimState.easyMotion.sortedSearch(position, searchChar, { + max: position }); } + } - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - var matchPos = new Position(match.position.line, Math.max(0, match.position.character + 1)); - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, matchPos)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; + 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 BaseCommand { + class ActionEasyMotionWordCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "w"]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the beginning of all words after the cursor - let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, min: position }); - - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; } } @RegisterAction - class ActionEasyMotionEndCommand extends BaseCommand { + class ActionEasyMotionEndCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "e"]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the end of all words after the cursor - let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, min: position, useEnd: true }); - - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; } } @RegisterAction - class ActionEasyMotionBeginningWordCommand extends BaseCommand { + class ActionEasyMotionBeginningWordCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "b"]; - public async exec(position: Position, vimState: VimState): Promise { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the beginning of all words before the cursor - let matches = vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, - max: position + max: position, }); - - // Stop if there are no matches - if (matches.length === 0) { - return vimState; - } - - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); - - // TODO: Implement this in EasyMotion class to help out with DRY - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - - if (match.position.isEqual(position)) { - continue; - } - - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, match.position)); - } - - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); - - return vimState; } } From ac0d55a93b56052271f272f4c50d707733bd0950 Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 15:08:21 +0200 Subject: [PATCH 14/22] Removed debug log --- src/actions/actions.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index d43156f1d1b..5786e476faf 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -124,8 +124,6 @@ export class BaseAction { * Is this action valid in the current Vim state? */ public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { - console.log(this); - if (this.modes.indexOf(vimState.currentMode) === -1) { return false; } if (!compareKeypressSequence(this.keys, keysPressed)) { return false; } if (vimState.recordedState.actionsRun.length > 0 && From a8065338c5874027fbeae5e6f6323086e956217e Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 15:09:13 +0200 Subject: [PATCH 15/22] Small optimizations when finding markers --- src/easymotion/easymotion.ts | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index c0c5149cef2..cc083205cba 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -17,6 +17,7 @@ export class EasyMotion { * Array of all markers and decorations */ private markers: EasyMotion.Marker[]; + private visibleMarkers: EasyMotion.Marker[]; // Array of currently showing markers private decorations: any[][] = []; /** @@ -41,6 +42,8 @@ export class EasyMotion { constructor(vimState: VimState) { this._vimState = vimState; + + this.visibleMarkers = []; } /** @@ -174,6 +177,7 @@ export class EasyMotion { */ public clearMarkers() { this.markers = []; + this.visibleMarkers = []; } public addMarker(marker: EasyMotion.Marker) { @@ -187,13 +191,14 @@ export class EasyMotion { /** * Find markers beginning with a string */ - public findMarkers(nail: string): EasyMotion.Marker[] { + public findMarkers(nail: string, visible = true): EasyMotion.Marker[] { + var arr = visible ? this.visibleMarkers : this.markers; var markers: EasyMotion.Marker[] = []; - for (var i = 0; i < this.markers.length; i++) { - var marker = this.getMarker(i); + for (var i = 0; i < arr.length; i++) { + var marker = arr[i]; if (marker.name.startsWith(nail)) { - markers.push(this.getMarker(i)); + markers.push(marker); } } @@ -304,6 +309,7 @@ export class EasyMotion { public updateDecorations(position: Position) { this.clearDecorations(); + this.visibleMarkers = []; this.decorations = []; for (var i = 0; i < this.markers.length; i++) { var marker = this.getMarker(i); @@ -340,6 +346,8 @@ export class EasyMotion { } } }); + + this.visibleMarkers.push(marker); } // Set the decorations for all the different marker lengths From 9739bdc1c52dc626e241105f4c2e4bb3875b63bf Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 15:16:49 +0200 Subject: [PATCH 16/22] EasyMotion.Match now contains the matched text --- src/actions/actions.ts | 7 +++++-- src/easymotion/easymotion.ts | 16 +++++++++------- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 5786e476faf..ea3ee9bcc52 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5479,10 +5479,13 @@ if (Configuration.getInstance().easymotion) { // Search for the end of all words after the cursor return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { isRegex: true, - min: position, - useEnd: true + 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 diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index cc083205cba..8b4da0ec5d2 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -246,11 +246,7 @@ export class EasyMotion { break outer; } - var matchIndex = result.index; - if (options.useEnd) { // Return the end of the result rather than the start - matchIndex += result[0].length - 1; - } - let pos = new Position(lineIdx, matchIndex); + let pos = new Position(lineIdx, result.index); // Check if match is within bounds if ((options.min && pos.isBefore(options.min)) || @@ -268,6 +264,7 @@ export class EasyMotion { prevMatch = new EasyMotion.Match( pos, + result[0], matches.length ); @@ -381,10 +378,12 @@ export module EasyMotion { export class Match { private _position: Position; + private _text: string; private _index: number; - constructor(position: Position, index: number) { + constructor(position: Position, text: string, index: number) { this._position = position; + this._text = text; this._index = index; } @@ -392,6 +391,10 @@ export module EasyMotion { return this._position; } + public get text(): string { + return this._text; + } + public get index(): number { return this._index; } @@ -401,6 +404,5 @@ export module EasyMotion { min?: Position; // The minimum bound of the search max?: Position; // The maximum bound of the search isRegex?: boolean; // Is the search string a regular expression? - useEnd?: boolean; // Use the end of the match rather than start } } \ No newline at end of file From 3a032ddaa26b88b880ad99e3238f03109a9c7812 Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 15:18:43 +0200 Subject: [PATCH 17/22] Implemented ge motion --- src/actions/actions.ts | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index ea3ee9bcc52..c1aa625dab3 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5471,7 +5471,7 @@ if (Configuration.getInstance().easymotion) { } @RegisterAction - class ActionEasyMotionEndCommand extends BaseEasyMotionCommand { + class ActionEasyMotionEndForwardCommand extends BaseEasyMotionCommand { modes = [ModeName.Normal]; keys = ["\\", "\\", "e"]; @@ -5488,6 +5488,24 @@ if (Configuration.getInstance().easymotion) { } } + @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, "\\w{1,}", { + isRegex: true, + 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]; From 120e70e7290caa7a4f303045465cb30c1320cf48 Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 22:50:15 +0200 Subject: [PATCH 18/22] Moved escape functionality to CommandEsc --- src/actions/actions.ts | 419 ++++++++++++++++++++--------------------- 1 file changed, 204 insertions(+), 215 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index c1aa625dab3..8d8e15e60ad 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -814,6 +814,7 @@ class CommandEsc extends BaseCommand { ModeName.VisualBlock, ModeName.Normal, ModeName.SearchInProgressMode, + ModeName.EasyMotionMode ]; keys = [ [""], @@ -829,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 @@ -852,6 +854,11 @@ class CommandEsc extends BaseCommand { vimState.isMultiCursor = false; } + if (vimState.currentMode === ModeName.EasyMotionMode) { + // Escape or other termination keys were pressed, exit mode + vimState.easyMotion.exitMode(); + } + vimState.currentMode = ModeName.Normal; if (!vimState.isMultiCursor) { @@ -5295,285 +5302,267 @@ class ActionOverrideCmdAltUp extends BaseCommand { } -// Only register the EasyMotion actions if the configuration is set -if (Configuration.getInstance().easymotion) { - 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) { - var index = 0; - for (var j = 0; j < matches.length; j++) { - var match = matches[j]; - var pos = this.getMatchPosition(match, position, vimState); +abstract class BaseEasyMotionCommand extends BaseCommand { + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + throw new Error("Not implemented!"); + } - if (match.position.isEqual(position)) { - continue; - } + public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position { + return match.position; + } - vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, pos)); - } - } + public processMarkers(matches: EasyMotion.Match[], position: Position, vimState: VimState) { + // Clear existing markers, just in case + vimState.easyMotion.clearMarkers(); - public async exec(position: Position, vimState: VimState): Promise { - // Search all occurences of the character pressed - let matches = this.getMatches(position, vimState); + var index = 0; + for (var j = 0; j < matches.length; j++) { + var match = matches[j]; + var pos = this.getMatchPosition(match, position, vimState); - // Stop if there are no matches - if (matches.length === 0) { - return vimState; + if (match.position.isEqual(position)) { + continue; } - // Clear existing markers, just in case - vimState.easyMotion.clearMarkers(); + vimState.easyMotion.addMarker(EasyMotion.generateMarker(index++, matches.length, position, pos)); + } + } - this.processMarkers(matches, position, vimState); + public async exec(position: Position, vimState: VimState): Promise { + // Only execute the action if the configuration is set + if (!Configuration.getInstance().easymotion) { + return vimState; + } - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); + // 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; } - } - @RegisterAction - class ActionEasyMotionSearchCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "s", ""]; + this.processMarkers(matches, position, vimState); - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - const searchChar = this.keysPressed[3]; + // Let EasyMotion update all decorations + vimState.easyMotion.updateDecorations(position); + // Enter the EasyMotion mode and await further keys + vimState.easyMotion.enterMode(); - // Search all occurences of the character pressed - if (searchChar === " ") { // Searching for space should only find the first space - return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); - } else { - return vimState.easyMotion.sortedSearch(position, searchChar); - } - } + return vimState; } +} - @RegisterAction - class ActionEasyMotionFindForwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "f", ""]; +@RegisterAction +class ActionEasyMotionSearchCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "s", ""]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - const searchChar = this.keysPressed[3]; + 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, " {1,}", { - isRegex: true, - min: position - }); - } else { - return vimState.easyMotion.sortedSearch(position, searchChar, { - min: position - }); - } + // Search all occurences of the character pressed + if (searchChar === " ") { // Searching for space should only find the first space + return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); + } else { + return vimState.easyMotion.sortedSearch(position, searchChar); } } +} - @RegisterAction - class ActionEasyMotionFindBackwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "F", ""]; +@RegisterAction +class ActionEasyMotionFindForwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "f", ""]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - const searchChar = this.keysPressed[3]; + 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, " {1,}", { - isRegex: true, - max: position - }); - } else { - return vimState.easyMotion.sortedSearch(position, searchChar, { - max: position - }); - } + // 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, " {1,}", { + isRegex: true, + min: position + }); + } else { + return vimState.easyMotion.sortedSearch(position, searchChar, { + min: position + }); } } +} - @RegisterAction - class ActionEasyMotionTilForwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "t", ""]; +@RegisterAction +class ActionEasyMotionFindBackwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "F", ""]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - const searchChar = this.keysPressed[3]; + 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, " {1,}", { - isRegex: true, - 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)); + // 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, " {1,}", { + isRegex: true, + max: position + }); + } else { + return vimState.easyMotion.sortedSearch(position, searchChar, { + max: position + }); } } +} - @RegisterAction - class ActionEasyMotionTilBackwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "T", ""]; +@RegisterAction +class ActionEasyMotionTilForwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "t", ""]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - const searchChar = this.keysPressed[3]; + 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, " {1,}", { - isRegex: true, - max: position - }); - } else { - return vimState.easyMotion.sortedSearch(position, searchChar, { - max: position - }); - } + // 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, " {1,}", { + isRegex: true, + 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)); - } + 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"]; +@RegisterAction +class ActionEasyMotionTilBackwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "T", ""]; + + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + const searchChar = this.keysPressed[3]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - // Search for the beginning of all words after the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + // 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, " {1,}", { isRegex: true, - min: position + max: position + }); + } else { + return vimState.easyMotion.sortedSearch(position, searchChar, { + max: position }); } } - @RegisterAction - class ActionEasyMotionEndForwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "e"]; + public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position { + return new Position(match.position.line, Math.max(0, match.position.character + 1)); + } +} - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - // Search for the end of all words after the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, - min: position - }); - } +@RegisterAction +class ActionEasyMotionWordCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "w"]; - public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position { - return new Position(match.position.line, match.position.character + match.text.length - 1); - } + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + // Search for the beginning of all words after the cursor + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + min: position + }); } +} - @RegisterAction - class ActionEasyMotionEndBackwardCommand extends BaseEasyMotionCommand { - modes = [ModeName.Normal]; - keys = ["\\", "\\", "g", "e"]; +@RegisterAction +class ActionEasyMotionEndForwardCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "e"]; - public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { - // Search for the beginning of all words before the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, - max: position, - }); - } + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + // Search for the end of all words after the cursor + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + 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); - } + 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"]; +@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, "\\w{1,}", { - isRegex: true, - max: position, - }); - } + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + // Search for the beginning of all words before the cursor + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + max: position, + }); } - @RegisterAction - class CommandEscEasyMotionMode extends BaseCommand { - modes = [ - ModeName.EasyMotionMode - ]; - keys = [ - [""], - [""], - [""], - ]; + public getMatchPosition(match: EasyMotion.Match, position: Position, vimState: VimState): Position { + return new Position(match.position.line, match.position.character + match.text.length - 1); + } +} - public async exec(position: Position, vimState: VimState): Promise { - // Escape or other termination keys were pressed, exit mode - vimState.currentMode = ModeName.Normal; - vimState.easyMotion.exitMode(); +@RegisterAction +class ActionEasyMotionBeginningWordCommand extends BaseEasyMotionCommand { + modes = [ModeName.Normal]; + keys = ["\\", "\\", "b"]; - return vimState; - } + public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { + // Search for the beginning of all words before the cursor + return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { + isRegex: true, + max: position, + }); } +} - @RegisterAction - class MoveEasyMotion extends BaseMovement { - modes = [ModeName.EasyMotionMode]; - keys = [""]; - - public async execAction(position: Position, vimState: VimState): Promise { - var key = this.keysPressed[0]; - if (!key) { - return position; - } +@RegisterAction +class MoveEasyMotion extends BaseMovement { + modes = [ModeName.EasyMotionMode]; + keys = [""]; - // "nail" refers to the accumulated depth keys - var nail = vimState.easyMotion.accumulation + key; - vimState.easyMotion.accumulation = nail; + public async execAction(position: Position, vimState: VimState): Promise { + var key = this.keysPressed[0]; + if (!key) { + return position; + } - // 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]; + // "nail" refers to the accumulated depth keys + var nail = vimState.easyMotion.accumulation + key; + vimState.easyMotion.accumulation = nail; - vimState.easyMotion.exitMode(); + // 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]; - return marker.position; - } else { - if (markers.length === 0) { // None found, exit mode - vimState.easyMotion.exitMode(); - return position; - } + vimState.easyMotion.exitMode(); - // Update decorations with new markers at a different depth level - vimState.easyMotion.updateDecorations(position); + return marker.position; + } else { + if (markers.length === 0) { // None found, exit mode + vimState.easyMotion.exitMode(); + return position; } - return position; + // Update decorations with new markers at a different depth level + vimState.easyMotion.updateDecorations(position); } + + return position; } } \ No newline at end of file From ca34a30a72aa66ebcc21233c3aff516f9a577759 Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 22:56:54 +0200 Subject: [PATCH 19/22] Added status bar text to show current depth --- src/mode/modeHandler.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index ab0068f4923..b17e9c1b0f6 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -1513,6 +1513,8 @@ export class ModeHandler implements vscode.Disposable { if (this.currentMode.name === ModeName.SearchInProgressMode) { this.setupStatusBarItem(`Searching for: ${ this.vimState.searchState!.searchString }`); + } else if (this.currentMode.name === ModeName.EasyMotionMode) { + this.setupStatusBarItem(`Current depth: ${ this.vimState.easyMotion.accumulation }`); } else { this.setupStatusBarItem( `-- ${this.currentMode.text.toUpperCase()} ${this._vimState.isMultiCursor ? 'MULTI CURSOR' : ''} -- ` + From d42001fabb97d24ef1935c02857c93cf65f45ca3 Mon Sep 17 00:00:00 2001 From: Metamist Date: Thu, 27 Oct 2016 23:55:55 +0200 Subject: [PATCH 20/22] Moved EasyMotion updateDecorations to updateView --- src/actions/actions.ts | 5 ----- src/easymotion/easymotion.ts | 2 +- src/mode/modeHandler.ts | 3 +++ 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 8d8e15e60ad..881df58295e 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5344,8 +5344,6 @@ abstract class BaseEasyMotionCommand extends BaseCommand { this.processMarkers(matches, position, vimState); - // Let EasyMotion update all decorations - vimState.easyMotion.updateDecorations(position); // Enter the EasyMotion mode and await further keys vimState.easyMotion.enterMode(); @@ -5558,9 +5556,6 @@ class MoveEasyMotion extends BaseMovement { vimState.easyMotion.exitMode(); return position; } - - // Update decorations with new markers at a different depth level - vimState.easyMotion.updateDecorations(position); } return position; diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index 8b4da0ec5d2..8a49841c363 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -303,7 +303,7 @@ export class EasyMotion { } - public updateDecorations(position: Position) { + public updateDecorations() { this.clearDecorations(); this.visibleMarkers = []; diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index b17e9c1b0f6..2ff4e994128 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -1514,6 +1514,9 @@ export class ModeHandler implements vscode.Disposable { if (this.currentMode.name === ModeName.SearchInProgressMode) { this.setupStatusBarItem(`Searching for: ${ this.vimState.searchState!.searchString }`); } else if (this.currentMode.name === ModeName.EasyMotionMode) { + // Update all EasyMotion decorations + this._vimState.easyMotion.updateDecorations(); + this.setupStatusBarItem(`Current depth: ${ this.vimState.easyMotion.accumulation }`); } else { this.setupStatusBarItem( From d911ec0de29a859edf475ad6414ceeb1f8d7f1fb Mon Sep 17 00:00:00 2001 From: Metamist Date: Fri, 28 Oct 2016 00:14:04 +0200 Subject: [PATCH 21/22] Remove isRegex and use string | RegExp instead --- src/actions/actions.ts | 26 +++++++++----------------- src/easymotion/easymotion.ts | 21 ++++++--------------- 2 files changed, 15 insertions(+), 32 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 53b276ed5ca..2e195d126d9 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -5356,7 +5356,7 @@ class ActionEasyMotionSearchCommand extends BaseEasyMotionCommand { // Search all occurences of the character pressed if (searchChar === " ") { // Searching for space should only find the first space - return vimState.easyMotion.sortedSearch(position, " {1,}", { isRegex: true }); + return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g")); } else { return vimState.easyMotion.sortedSearch(position, searchChar); } @@ -5373,8 +5373,7 @@ class ActionEasyMotionFindForwardCommand extends BaseEasyMotionCommand { // 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, " {1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), { min: position }); } else { @@ -5395,8 +5394,7 @@ class ActionEasyMotionFindBackwardCommand extends BaseEasyMotionCommand { // 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, " {1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), { max: position }); } else { @@ -5417,8 +5415,7 @@ class ActionEasyMotionTilForwardCommand extends BaseEasyMotionCommand { // 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, " {1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}", "g"), { min: position }); } else { @@ -5443,8 +5440,7 @@ class ActionEasyMotionTilBackwardCommand extends BaseEasyMotionCommand { // 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, " {1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp(" {1,}"), { max: position }); } else { @@ -5466,8 +5462,7 @@ class ActionEasyMotionWordCommand extends BaseEasyMotionCommand { public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the beginning of all words after the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), { min: position }); } @@ -5480,8 +5475,7 @@ class ActionEasyMotionEndForwardCommand extends BaseEasyMotionCommand { public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the end of all words after the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), { min: position }); } @@ -5498,8 +5492,7 @@ class ActionEasyMotionEndBackwardCommand extends BaseEasyMotionCommand { public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the beginning of all words before the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), { max: position, }); } @@ -5516,8 +5509,7 @@ class ActionEasyMotionBeginningWordCommand extends BaseEasyMotionCommand { public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { // Search for the beginning of all words before the cursor - return vimState.easyMotion.sortedSearch(position, "\\w{1,}", { - isRegex: true, + return vimState.easyMotion.sortedSearch(position, new RegExp("\\w{1,}", "g"), { max: position, }); } diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index 8a49841c363..5bd3c4ee948 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -208,21 +208,13 @@ export class EasyMotion { /** * Search and sort using the index of a match compared to the index of position (usually the cursor) */ - public sortedSearch(position: Position, searchString = "", options: EasyMotion.SearchOptions = {}): EasyMotion.Match[] { - let searchRE = searchString; - // Regex needs to be escaped - if (!options.isRegex) { - searchRE = searchString.replace(EasyMotion.specialCharactersRegex, "\\$&"); - } - - const regexFlags = "g"; + public sortedSearch(position: Position, search: string | RegExp = "", options: EasyMotion.SearchOptions = {}): EasyMotion.Match[] { let regex: RegExp; - try { - regex = new RegExp(searchRE, regexFlags); - } catch (err) { - // Couldn't compile the regexp, try again with special characters escaped - searchRE = searchString.replace(EasyMotion.specialCharactersRegex, "\\$&"); - regex = new RegExp(searchRE, regexFlags); + if (typeof search === "string") { + // Regex needs to be escaped + regex = new RegExp(search.replace(EasyMotion.specialCharactersRegex, "\\$&"), "g"); + } else { + regex = search; } var matches: EasyMotion.Match[] = []; @@ -403,6 +395,5 @@ export module EasyMotion { export interface SearchOptions { min?: Position; // The minimum bound of the search max?: Position; // The maximum bound of the search - isRegex?: boolean; // Is the search string a regular expression? } } \ No newline at end of file From d2610147970bb1da27cf456794e6491594e3fda5 Mon Sep 17 00:00:00 2001 From: Metamist Date: Tue, 1 Nov 2016 20:22:38 +0100 Subject: [PATCH 22/22] EasyMotion object is now recreated when reset --- src/actions/actions.ts | 17 +++++++++++------ src/easymotion/easymotion.ts | 31 ++++--------------------------- src/mode/modeHandler.ts | 2 +- 3 files changed, 16 insertions(+), 34 deletions(-) diff --git a/src/actions/actions.ts b/src/actions/actions.ts index 2e195d126d9..633a9332d04 100644 --- a/src/actions/actions.ts +++ b/src/actions/actions.ts @@ -856,7 +856,8 @@ class CommandEsc extends BaseCommand { if (vimState.currentMode === ModeName.EasyMotionMode) { // Escape or other termination keys were pressed, exit mode - vimState.easyMotion.exitMode(); + vimState.easyMotion.clearDecorations(); + vimState.currentMode = ModeName.Normal; } vimState.currentMode = ModeName.Normal; @@ -5337,10 +5338,11 @@ abstract class BaseEasyMotionCommand extends BaseCommand { return vimState; } - this.processMarkers(matches, position, vimState); - // Enter the EasyMotion mode and await further keys - vimState.easyMotion.enterMode(); + vimState.currentMode = ModeName.EasyMotionMode; + vimState.easyMotion = new EasyMotion(); + + this.processMarkers(matches, position, vimState); return vimState; } @@ -5535,12 +5537,15 @@ class MoveEasyMotion extends BaseMovement { if (markers.length === 1) { // Only one found, navigate to it var marker = markers[0]; - vimState.easyMotion.exitMode(); + vimState.easyMotion.clearDecorations(); + vimState.currentMode = ModeName.Normal; return marker.position; } else { if (markers.length === 0) { // None found, exit mode - vimState.easyMotion.exitMode(); + vimState.easyMotion.clearDecorations(); + vimState.currentMode = ModeName.Normal; + return position; } } diff --git a/src/easymotion/easymotion.ts b/src/easymotion/easymotion.ts index 5bd3c4ee948..152624b7fad 100644 --- a/src/easymotion/easymotion.ts +++ b/src/easymotion/easymotion.ts @@ -1,13 +1,9 @@ import * as vscode from "vscode"; import { Position } from './../motion/position'; -import { VimState } from './../mode/modeHandler'; -import { ModeName } from './../mode/mode'; import { TextEditor } from './../textEditor'; export class EasyMotion { - private _vimState: VimState; - /** * Refers to the accumulated keys for depth navigation */ @@ -18,7 +14,7 @@ export class EasyMotion { */ private markers: EasyMotion.Marker[]; private visibleMarkers: EasyMotion.Marker[]; // Array of currently showing markers - private decorations: any[][] = []; + private decorations: any[][]; /** * TODO: For future motions @@ -40,10 +36,10 @@ export class EasyMotion { "v", "b", "n", "m", "f", "j" ]; - constructor(vimState: VimState) { - this._vimState = vimState; - + constructor() { + this.markers = []; this.visibleMarkers = []; + this.decorations = []; } /** @@ -143,25 +139,6 @@ export class EasyMotion { return uri; } - /** - * Enter EasyMotion mode - */ - public enterMode() { - this.accumulation = ""; - this._vimState.currentMode = ModeName.EasyMotionMode; - } - - /** - * Exit EasyMotion mode and clean up - */ - public exitMode() { - this._vimState.currentMode = ModeName.Normal; - - this.accumulation = ""; - this.clearMarkers(); - this.clearDecorations(); - } - /** * Clear all decorations */ diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index 2ff4e994128..36d15b196fa 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -452,7 +452,7 @@ export class ModeHandler implements vscode.Disposable { new EasyMotionMode(), ]; this.vimState.historyTracker = new HistoryTracker(); - this.vimState.easyMotion = new EasyMotion(this.vimState); + this.vimState.easyMotion = new EasyMotion(); this._vimState.currentMode = ModeName.Normal;