From 64c822ece2b6b365ba1faf23e4648595b2453749 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Sat, 9 Sep 2017 12:45:23 +0900 Subject: [PATCH 01/11] fix #2009 --- src/actions/plugins/easymotion/easymotion.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/actions/plugins/easymotion/easymotion.ts b/src/actions/plugins/easymotion/easymotion.ts index e4d3303a5e3..d15076a6fbe 100644 --- a/src/actions/plugins/easymotion/easymotion.ts +++ b/src/actions/plugins/easymotion/easymotion.ts @@ -68,7 +68,9 @@ export class EasyMotion { after: { margin: `0 0 0 -${width}px`, height: `14px`, - width: `${width}px`, + // This is a tricky part. Set position and z-index property along with width + // to bring markers to front + width: `${width}px; position:absoulute; z-index:99;`, }, }); From ddd8df2dd1604803bfe31008f94b57ae5a1b81d8 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Wed, 13 Sep 2017 00:38:54 +0900 Subject: [PATCH 02/11] [WIP] Getting color for marker from vscode theme tweaks Also get foreground color for markers from vscode themecolor --- package.json | 15 +-- src/actions/plugins/easymotion/easymotion.ts | 129 +++++++++---------- src/configuration/configuration.ts | 10 +- 3 files changed, 70 insertions(+), 84 deletions(-) diff --git a/package.json b/package.json index e7e76ca3006..0bf98e3b3bb 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "url": "https://github.com/VSCodeVim/Vim/issues" }, "engines": { - "vscode": "^1.12.0" + "vscode": "^1.13.0" }, "categories": [ "Other", @@ -422,18 +422,15 @@ }, "vim.easymotionMarkerBackgroundColor": { "type": "string", - "description": "Set a custom background color for EasyMotion markers.", - "default": "#000000" + "description": "Set a custom background color for EasyMotion markers." }, "vim.easymotionMarkerForegroundColorOneChar": { "type": "string", - "description": "Set a custom color for the text on one character long markers.", - "default": "#ff0000" + "description": "Set a custom color for the text on one character long markers." }, "vim.easymotionMarkerForegroundColorTwoChar": { "type": "string", - "description": "Set a custom color for the text on two character long markers.", - "default": "#ffa500" + "description": "Set a custom color for the text on two character long markers." }, "vim.easymotionMarkerWidthPerChar": { "type": "number", @@ -463,7 +460,7 @@ "vim.easymotionMarkerYOffset": { "type": "number", "description": "Set the Y offset of the marker text (the distance from the top).", - "default": 11 + "default": 0 }, "vim.easymotionKeys": { "type": "string", @@ -579,6 +576,6 @@ "mocha": "^3.2.0", "tslint": "^4.3.1", "typescript": "^2.3.2", - "vscode": "^1.0.5" + "vscode": "^1.1.5" } } \ No newline at end of file diff --git a/src/actions/plugins/easymotion/easymotion.ts b/src/actions/plugins/easymotion/easymotion.ts index d15076a6fbe..176ac3d5be9 100644 --- a/src/actions/plugins/easymotion/easymotion.ts +++ b/src/actions/plugins/easymotion/easymotion.ts @@ -29,12 +29,6 @@ export class EasyMotion { * Caches for decorations */ private static decorationTypeCache: vscode.TextEditorDecorationType[] = []; - private static svgCache: { [code: string]: vscode.Uri } = {}; - private static cachedBackgroundColor: string = ''; - private static cachedOneFontColor: string = ''; - private static cachedTwoFontColor: string = ''; - private static cachedWidthPerChar: number = -1; - private static cachedHeight: number = -1; public get markers() { return this._markers; @@ -80,60 +74,6 @@ export class EasyMotion { } } - /** - * Create and cache the SVG data URI for different marker codes and colors - */ - private static getSvgDataUri( - code: string, - backgroundColor: string | undefined = 'black', - fontFamily: string | undefined = 'Consolas', - fontColor: string | undefined = 'white', - fontSize: string | undefined = '14', - fontWeight: string | undefined = 'normal' - ): vscode.Uri { - // Clear cache if the backgroundColor or fontColor has changed - if (this.cachedBackgroundColor !== backgroundColor) { - this.svgCache = {}; - this.cachedBackgroundColor = backgroundColor; - } - - if (this.cachedOneFontColor !== Configuration.easymotionMarkerForegroundColorOneChar) { - this.svgCache = {}; - this.cachedOneFontColor = Configuration.easymotionMarkerForegroundColorOneChar; - } - - if (this.cachedTwoFontColor !== Configuration.easymotionMarkerForegroundColorTwoChar) { - this.svgCache = {}; - this.cachedTwoFontColor = Configuration.easymotionMarkerForegroundColorTwoChar; - } - - const widthPerChar = Configuration.easymotionMarkerWidthPerChar; - const width = code.length * widthPerChar + 1; - const height = Configuration.easymotionMarkerHeight; - - if (this.cachedWidthPerChar !== widthPerChar || this.cachedHeight !== height) { - this.svgCache = {}; - this.cachedWidthPerChar = width; - this.cachedHeight = height; - } - - const cache = this.svgCache[code]; - if (cache) { - return cache; - } else { - const uri = vscode.Uri.parse( - `data:image/svg+xml;utf8,${code}` - ); - - this.svgCache[code] = uri; - - return uri; - } - } - /** * Clear all decorations */ @@ -244,6 +184,52 @@ export class EasyMotion { return matches; } + private themeColorApiSupported(): boolean { + // Theme color is available from version 1.12. + const vscodeVersionAsNumber = parseInt(vscode.version.replace(/\./g, ''), 10); + return vscodeVersionAsNumber >= 1120; + } + + private getMarkerColor( + customizedValue: string, + defaultValue: string | vscode.ThemeColor, + themeColorId: string + ): string | vscode.ThemeColor { + if (!this.themeColorApiSupported()) { + return customizedValue || defaultValue; + } else { + if (customizedValue) { + return customizedValue; + } else { + return new vscode.ThemeColor(themeColorId); + } + } + } + + private getEasymotionMarkerBackgroundColor() { + return this.getMarkerColor( + Configuration.easymotionMarkerBackgroundColor, + '#000', + 'activityBarBadge.background' + ); + } + + private getEasymotionMarkerForegroundColorOneChar() { + return this.getMarkerColor( + Configuration.easymotionMarkerForegroundColorOneChar, + '#f00', + 'activityBarBadge.foreground' + ); + } + + private getEasymotionMarkerForegroundColorTwoChar() { + return this.getMarkerColor( + Configuration.easymotionMarkerForegroundColorTwoChar, + '#f00', + 'activityBarBadge.foreground' + ); + } + public updateDecorations() { this.clearDecorations(); @@ -262,19 +248,22 @@ export class EasyMotion { const fontColor = keystroke.length > 1 - ? Configuration.easymotionMarkerForegroundColorTwoChar - : Configuration.easymotionMarkerForegroundColorOneChar; + ? this.getEasymotionMarkerForegroundColorTwoChar() + : this.getEasymotionMarkerForegroundColorOneChar(); const renderOptions: vscode.ThemableDecorationInstanceRenderOptions = { after: { - contentIconPath: EasyMotion.getSvgDataUri( - keystroke, - Configuration.easymotionMarkerBackgroundColor, - Configuration.easymotionMarkerFontFamily, - fontColor, - Configuration.easymotionMarkerFontSize, - Configuration.easymotionMarkerFontWeight - ), + contentText: keystroke, + backgroundColor: this.getEasymotionMarkerBackgroundColor(), + height: `${Configuration.easymotionMarkerHeight}px`, + width: `${keystroke.length * Configuration.easymotionMarkerWidthPerChar}px`, + color: `${fontColor}; + font-family: ${Configuration.easymotionMarkerFontFamily}; + font-size: ${Configuration.easymotionMarkerFontSize}px; + font-weight: ${Configuration.easymotionMarkerFontWeight}; + position: absolute; + z-index: 99; + bottom: ${Configuration.easymotionMarkerYOffset}px`, }, }; // Position should be offsetted by the length of the keystroke to prevent hiding behind the gutter diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 7d14f35e1b5..a663bf94ee7 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -184,7 +184,7 @@ class ConfigurationClass { /** * Easymotion marker appearance settings */ - easymotionMarkerBackgroundColor = '#000000'; + easymotionMarkerBackgroundColor = ''; easymotionMarkerForegroundColorOneChar = '#ff0000'; easymotionMarkerForegroundColorTwoChar = '#ffa500'; easymotionMarkerWidthPerChar = 8; @@ -192,7 +192,7 @@ class ConfigurationClass { easymotionMarkerFontFamily = 'Consolas'; easymotionMarkerFontSize = '14'; easymotionMarkerFontWeight = 'normal'; - easymotionMarkerYOffset = 11; + easymotionMarkerYOffset = 0; easymotionKeys = 'hklyuiopnm,qwertzxcvbasdgjf;'; /** @@ -316,9 +316,9 @@ function overlapSetting(args: { default: OptionValue; codeValueMapping?: ValueMapping; }) { - return function(target: any, propertyKey: string) { + return function (target: any, propertyKey: string) { Object.defineProperty(target, propertyKey, { - get: function() { + get: function () { if (this['_' + propertyKey] !== undefined) { return this['_' + propertyKey]; } @@ -333,7 +333,7 @@ function overlapSetting(args: { return vscode.workspace.getConfiguration('editor').get(args.codeName, args.default); } }, - set: function(value) { + set: function (value) { this['_' + propertyKey] = value; taskQueue.enqueueTask({ From 63d2efb0e45e8cfde36513250b2685fbf26110f9 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Wed, 13 Sep 2017 23:28:27 +0900 Subject: [PATCH 03/11] Support bi-directional motions --- .../plugins/easymotion/easymotion.cmd.ts | 40 ++++- .../plugins/easymotion/registerMoveActions.ts | 159 ++++++++---------- 2 files changed, 103 insertions(+), 96 deletions(-) diff --git a/src/actions/plugins/easymotion/easymotion.cmd.ts b/src/actions/plugins/easymotion/easymotion.cmd.ts index 5ff86595238..d186b5e9406 100644 --- a/src/actions/plugins/easymotion/easymotion.cmd.ts +++ b/src/actions/plugins/easymotion/easymotion.cmd.ts @@ -11,6 +11,18 @@ import { EasyMotionCharMoveOpions, } from './types'; +interface EasymotionTrigger { + key: string; + leaderCount?: number; +} + +function buildTriggerKeys(trigger: EasymotionTrigger) { + return [ + ...Array.from({ length: trigger.leaderCount || 2 }, () => ''), + ...trigger.key.split(''), + ]; +} + abstract class BaseEasyMotionCommand extends BaseCommand { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; @@ -18,9 +30,15 @@ abstract class BaseEasyMotionCommand extends BaseCommand { public abstract getMatches(position: Position, vimState: VimState): EasyMotion.Match[]; - constructor(baseOptions: EasyMotionMoveOptionsBase) { + constructor( + baseOptions: EasyMotionMoveOptionsBase, + trigger?: EasymotionTrigger + ) { super(); this._baseOptions = baseOptions; + if (trigger) { + this.keys = buildTriggerKeys(trigger); + } } public abstract resolveMatchPosition(match: EasyMotion.Match): Position; @@ -226,10 +244,10 @@ export class EasyMotionCharMoveCommandBase extends BaseCommand { modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; private _action: EasyMotionSearchAction; - constructor(trigger: string, action: EasyMotionSearchAction) { + constructor(trigger: EasymotionTrigger, action: EasyMotionSearchAction) { super(); this._action = action; - this.keys = ['', '', ...trigger.split('')]; + this.keys = buildTriggerKeys(trigger); } public async exec(position: Position, vimState: VimState): Promise { @@ -246,10 +264,12 @@ export class EasyMotionCharMoveCommandBase extends BaseCommand { export class EasyMotionWordMoveCommandBase extends BaseEasyMotionCommand { private _options: EasyMotionWordMoveOpions; - constructor(trigger: string, options: EasyMotionWordMoveOpions = {}) { - super(options); + constructor( + trigger: EasymotionTrigger, + options: EasyMotionWordMoveOpions = {} + ) { + super(options, trigger); this._options = options; - this.keys = ['', '', ...trigger.split('')]; } public getMatches(position: Position, vimState: VimState): EasyMotion.Match[] { @@ -279,10 +299,12 @@ export class EasyMotionWordMoveCommandBase extends BaseEasyMotionCommand { export class EasyMotionLineMoveCommandBase extends BaseEasyMotionCommand { private _options: EasyMotionMoveOptionsBase; - constructor(trigger: string, options: EasyMotionMoveOptionsBase) { - super(options); + constructor( + trigger: EasymotionTrigger, + options: EasyMotionMoveOptionsBase = {} + ) { + super(options, trigger); this._options = options; - this.keys = ['', '', ...trigger.split('')]; } public resolveMatchPosition(match: EasyMotion.Match): Position { diff --git a/src/actions/plugins/easymotion/registerMoveActions.ts b/src/actions/plugins/easymotion/registerMoveActions.ts index ab1b0f65310..4fcaa6aaf87 100644 --- a/src/actions/plugins/easymotion/registerMoveActions.ts +++ b/src/actions/plugins/easymotion/registerMoveActions.ts @@ -12,7 +12,7 @@ import { @RegisterAction class EasyMotionNCharSearchCommand extends EasyMotionCharMoveCommandBase { constructor() { - super('/', new SearchByNCharCommand()); + super({ key: '/' }, new SearchByNCharCommand()); } } @@ -21,51 +21,40 @@ class EasyMotionNCharSearchCommand extends EasyMotionCharMoveCommandBase { @RegisterAction class EasyMotionTwoCharSearchCommand extends EasyMotionCharMoveCommandBase { constructor() { - super( - '2s', - new SearchByCharCommand({ - charCount: 2, - }) - ); + super({ key: '2s' }, new SearchByCharCommand({ charCount: 2 })); } } @RegisterAction class EasyMotionTwoCharFindForwardCommand extends EasyMotionCharMoveCommandBase { constructor() { - super( - '2f', - new SearchByCharCommand({ - charCount: 2, - searchOptions: 'min', - }) - ); + super({ key: '2f' }, new SearchByCharCommand({ charCount: 2, searchOptions: 'min' })); } } @RegisterAction class EasyMotionTwoCharFindBackwardCommand extends EasyMotionCharMoveCommandBase { + constructor() { + super({ key: '2F' }, new SearchByCharCommand({ charCount: 2, searchOptions: 'max' })); + } +} + +@RegisterAction +class EasyMotionTwoCharTilCharacterForwardCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - '2F', - new SearchByCharCommand({ - charCount: 2, - searchOptions: 'max', - }) + { key: '2t' }, + new SearchByCharCommand({ charCount: 2, searchOptions: 'min', labelPosition: 'before' }) ); } } @RegisterAction -class EasyMotionTwoCharTilForwardCommand extends EasyMotionCharMoveCommandBase { +class EasyMotionTwoCharTilCharacterBidirectionalCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - '2t', - new SearchByCharCommand({ - charCount: 2, - searchOptions: 'min', - labelPosition: 'before', - }) + { key: 'bd2t', leaderCount: 3 }, + new SearchByCharCommand({ charCount: 2, labelPosition: 'before' }) ); } } @@ -74,12 +63,8 @@ class EasyMotionTwoCharTilForwardCommand extends EasyMotionCharMoveCommandBase { class EasyMotionTwoCharTilBackwardCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - '2T', - new SearchByCharCommand({ - charCount: 2, - searchOptions: 'max', - labelPosition: 'after', - }) + { key: '2T' }, + new SearchByCharCommand({ charCount: 2, searchOptions: 'max', labelPosition: 'after' }) ); } } @@ -87,51 +72,42 @@ class EasyMotionTwoCharTilBackwardCommand extends EasyMotionCharMoveCommandBase @RegisterAction class EasyMotionSearchCommand extends EasyMotionCharMoveCommandBase { constructor() { - super( - 's', - new SearchByCharCommand({ - charCount: 1, - }) - ); + super({ key: 's' }, new SearchByCharCommand({ charCount: 1 })); } } @RegisterAction class EasyMotionFindForwardCommand extends EasyMotionCharMoveCommandBase { constructor() { - super( - 'f', - new SearchByCharCommand({ - charCount: 1, - searchOptions: 'min', - }) - ); + super({ key: 'f' }, new SearchByCharCommand({ charCount: 1, searchOptions: 'min' })); } } @RegisterAction class EasyMotionFindBackwardCommand extends EasyMotionCharMoveCommandBase { + constructor() { + super({ key: 'F' }, new SearchByCharCommand({ charCount: 1, searchOptions: 'max' })); + } +} + +@RegisterAction +class EasyMotionTilCharacterForwardCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - 'F', - new SearchByCharCommand({ - charCount: 1, - searchOptions: 'max', - }) + { key: 't' }, + new SearchByCharCommand({ charCount: 1, searchOptions: 'min', labelPosition: 'before' }) ); } } +// easymotion-bd-t + @RegisterAction -class EasyMotionTilForwardCommand extends EasyMotionCharMoveCommandBase { +class EasyMotionTilCharacterBidirectionalCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - 't', - new SearchByCharCommand({ - charCount: 1, - searchOptions: 'min', - labelPosition: 'before', - }) + { key: 'bdt', leaderCount: 3 }, + new SearchByCharCommand({ charCount: 1, labelPosition: 'before' }) ); } } @@ -140,12 +116,8 @@ class EasyMotionTilForwardCommand extends EasyMotionCharMoveCommandBase { class EasyMotionTilBackwardCommand extends EasyMotionCharMoveCommandBase { constructor() { super( - 'T', - new SearchByCharCommand({ - charCount: 1, - searchOptions: 'max', - labelPosition: 'after', - }) + { key: 'T' }, + new SearchByCharCommand({ charCount: 1, searchOptions: 'max', labelPosition: 'after' }) ); } } @@ -153,59 +125,72 @@ class EasyMotionTilBackwardCommand extends EasyMotionCharMoveCommandBase { // EasyMotion word-move commands @RegisterAction -class EasyMotionWordCommand extends EasyMotionWordMoveCommandBase { +class EasyMotionStartOfWordForwardsCommand extends EasyMotionWordMoveCommandBase { constructor() { - super('w', { - searchOptions: 'min', - }); + super({ key: 'w' }, { searchOptions: 'min' }); } } +// easymotion-bd-w + +@RegisterAction +class EasyMotionStartOfWordBidirectionalCommand extends EasyMotionWordMoveCommandBase { + constructor() { + super({ key: 'bdw', leaderCount: 3 }); + } +} + +@RegisterAction +class EasyMotionEndOfWordForwardsCommand extends EasyMotionWordMoveCommandBase { + constructor() { + super({ key: 'e' }, { searchOptions: 'min', labelPosition: 'after' }); + } +} + +// easymotion-bd-e + @RegisterAction -class EasyMotionEndForwardCommand extends EasyMotionWordMoveCommandBase { +class EasyMotionEndOfWordBidirectionalCommand extends EasyMotionWordMoveCommandBase { constructor() { - super('e', { - searchOptions: 'min', - labelPosition: 'after', - }); + super({ key: 'bde', leaderCount: 3 }, { labelPosition: 'after' }); } } @RegisterAction class EasyMotionBeginningWordCommand extends EasyMotionWordMoveCommandBase { constructor() { - super('b', { - searchOptions: 'max', - }); + super({ key: 'b' }, { searchOptions: 'max' }); } } @RegisterAction class EasyMotionEndBackwardCommand extends EasyMotionWordMoveCommandBase { constructor() { - super('ge', { - searchOptions: 'max', - labelPosition: 'after', - }); + super({ key: 'ge' }, { searchOptions: 'max', labelPosition: 'after' }); } } // EasyMotion line-move commands @RegisterAction -class EasyMotionDownLinesCommand extends EasyMotionLineMoveCommandBase { +class EasyMotionStartOfLineForwardsCommand extends EasyMotionLineMoveCommandBase { constructor() { - super('j', { - searchOptions: 'min', - }); + super({ key: 'j' }, { searchOptions: 'min' }); } } @RegisterAction -class EasyMotionUpLinesCommand extends EasyMotionLineMoveCommandBase { +class EasyMotionStartOfLineBackwordsCommand extends EasyMotionLineMoveCommandBase { + constructor() { + super({ key: 'k' }, { searchOptions: 'max' }); + } +} + +// easymotion-bd-jk + +@RegisterAction +class EasyMotionStartOfLineBidirectionalCommand extends EasyMotionLineMoveCommandBase { constructor() { - super('k', { - searchOptions: 'max', - }); + super({ key: 'bdjk', leaderCount: 3 }); } } From 953095980b0732d9f1af6c2d81c6be392d107677 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Wed, 13 Sep 2017 23:58:05 +0900 Subject: [PATCH 04/11] Add tests for new easymotion motions --- .../plugins/easymotion/easymotion.cmd.ts | 19 ++--- .../plugins/easymotion/registerMoveActions.ts | 2 + test/plugins/easymotion.test.ts | 84 ++++++++++++++----- 3 files changed, 72 insertions(+), 33 deletions(-) diff --git a/src/actions/plugins/easymotion/easymotion.cmd.ts b/src/actions/plugins/easymotion/easymotion.cmd.ts index d186b5e9406..14d6278bcb6 100644 --- a/src/actions/plugins/easymotion/easymotion.cmd.ts +++ b/src/actions/plugins/easymotion/easymotion.cmd.ts @@ -11,12 +11,12 @@ import { EasyMotionCharMoveOpions, } from './types'; -interface EasymotionTrigger { +export interface EasymotionTrigger { key: string; leaderCount?: number; } -function buildTriggerKeys(trigger: EasymotionTrigger) { +export function buildTriggerKeys(trigger: EasymotionTrigger) { return [ ...Array.from({ length: trigger.leaderCount || 2 }, () => ''), ...trigger.key.split(''), @@ -30,10 +30,7 @@ abstract class BaseEasyMotionCommand extends BaseCommand { public abstract getMatches(position: Position, vimState: VimState): EasyMotion.Match[]; - constructor( - baseOptions: EasyMotionMoveOptionsBase, - trigger?: EasymotionTrigger - ) { + constructor(baseOptions: EasyMotionMoveOptionsBase, trigger?: EasymotionTrigger) { super(); this._baseOptions = baseOptions; if (trigger) { @@ -264,10 +261,7 @@ export class EasyMotionCharMoveCommandBase extends BaseCommand { export class EasyMotionWordMoveCommandBase extends BaseEasyMotionCommand { private _options: EasyMotionWordMoveOpions; - constructor( - trigger: EasymotionTrigger, - options: EasyMotionWordMoveOpions = {} - ) { + constructor(trigger: EasymotionTrigger, options: EasyMotionWordMoveOpions = {}) { super(options, trigger); this._options = options; } @@ -299,10 +293,7 @@ export class EasyMotionWordMoveCommandBase extends BaseEasyMotionCommand { export class EasyMotionLineMoveCommandBase extends BaseEasyMotionCommand { private _options: EasyMotionMoveOptionsBase; - constructor( - trigger: EasymotionTrigger, - options: EasyMotionMoveOptionsBase = {} - ) { + constructor(trigger: EasymotionTrigger, options: EasyMotionMoveOptionsBase = {}) { super(options, trigger); this._options = options; } diff --git a/src/actions/plugins/easymotion/registerMoveActions.ts b/src/actions/plugins/easymotion/registerMoveActions.ts index 4fcaa6aaf87..11cb5a906df 100644 --- a/src/actions/plugins/easymotion/registerMoveActions.ts +++ b/src/actions/plugins/easymotion/registerMoveActions.ts @@ -49,6 +49,8 @@ class EasyMotionTwoCharTilCharacterForwardCommand extends EasyMotionCharMoveComm } } +// easymotion-bd-t2 + @RegisterAction class EasyMotionTwoCharTilCharacterBidirectionalCommand extends EasyMotionCharMoveCommandBase { constructor() { diff --git a/test/plugins/easymotion.test.ts b/test/plugins/easymotion.test.ts index 51e38680024..7a644809a67 100644 --- a/test/plugins/easymotion.test.ts +++ b/test/plugins/easymotion.test.ts @@ -4,9 +4,13 @@ import { ModeHandler } from '../../src/mode/modeHandler'; import { getTestingFunctions } from '../testSimplifier'; import { getAndUpdateModeHandler } from '../../extension'; import { Configuration } from '../../src/configuration/configuration'; +import { + EasymotionTrigger, + buildTriggerKeys, +} from '../../src/actions/plugins/easymotion/easymotion.cmd'; -function easymotionCommand(trigger: string, searchWord: string, jumpKey: string) { - return ['', trigger, searchWord, jumpKey].join(''); +function easymotionCommand(trigger: EasymotionTrigger, searchWord: string, jumpKey: string) { + return [...buildTriggerKeys(trigger), searchWord, jumpKey].join(''); } suite('easymotion plugin', () => { @@ -27,119 +31,161 @@ suite('easymotion plugin', () => { newTest({ title: 'Can handle s move', start: ['a|bcdabcd'], - keysPressed: easymotionCommand('s', 'a', 'k'), + keysPressed: easymotionCommand({ key: 's' }, 'a', 'k'), end: ['|abcdabcd'], }); newTest({ title: 'Can handle 2s move', start: ['ab|cdabcd'], - keysPressed: easymotionCommand('2s', 'ab', 'k'), + keysPressed: easymotionCommand({ key: '2s' }, 'ab', 'k'), end: ['|abcdabcd'], }); newTest({ title: 'Can handle f move', start: ['a|bcdabcdabcd'], - keysPressed: easymotionCommand('f', 'a', 'k'), + keysPressed: easymotionCommand({ key: 'f' }, 'a', 'k'), end: ['abcdabcd|abcd'], }); newTest({ title: 'Can handle 2f move', start: ['a|bcdabcdabcd'], - keysPressed: easymotionCommand('2f', 'ab', 'k'), + keysPressed: easymotionCommand({ key: '2f' }, 'ab', 'k'), end: ['abcdabcd|abcd'], }); newTest({ title: 'Can handle F move', start: ['abcdabc|dabcd'], - keysPressed: easymotionCommand('F', 'a', 'k'), + keysPressed: easymotionCommand({ key: 'F' }, 'a', 'k'), end: ['|abcdabcdabcd'], }); newTest({ title: 'Can handle 2F move', start: ['abcdabc|dabcd'], - keysPressed: easymotionCommand('2F', 'ab', 'k'), + keysPressed: easymotionCommand({ key: '2F' }, 'ab', 'k'), end: ['|abcdabcdabcd'], }); newTest({ title: 'Can handle t move', start: ['abcd|abcdabcd'], - keysPressed: easymotionCommand('t', 'c', 'k'), + keysPressed: easymotionCommand({ key: 't' }, 'c', 'k'), end: ['abcdabcda|bcd'], }); + newTest({ + title: 'Can handle bd-t move', + start: ['abcd|abcdabcd'], + keysPressed: easymotionCommand({ key: 'bdt', leaderCount: 3 }, 'c', 'k'), + end: ['a|bcdabcdabcd'], + }); + newTest({ title: 'Can handle 2t move', start: ['abcd|abcdabcd'], - keysPressed: easymotionCommand('2t', 'cd', 'k'), + keysPressed: easymotionCommand({ key: '2t' }, 'cd', 'k'), end: ['abcdabcda|bcd'], }); + newTest({ + title: 'Can handle bd-t2 move', + start: ['abcd|abcdabcd'], + keysPressed: easymotionCommand({ key: 'bd2t', leaderCount: 3 }, 'cd', 'k'), + end: ['a|bcdabcdabcd'], + }); + newTest({ title: 'Can handle T move', start: ['abcdab|cdabcd'], - keysPressed: easymotionCommand('T', 'a', 'k'), + keysPressed: easymotionCommand({ key: 'T' }, 'a', 'k'), end: ['a|bcdabcdabcd'], }); newTest({ title: 'Can handle 2T move', start: ['abcdabc|dabcd'], - keysPressed: easymotionCommand('2T', 'ab', 'k'), + keysPressed: easymotionCommand({ key: '2T' }, 'ab', 'k'), end: ['ab|cdabcdabcd'], }); newTest({ title: 'Can handle w move', start: ['abc |def ghi jkl'], - keysPressed: easymotionCommand('w', '', 'k'), + keysPressed: easymotionCommand({ key: 'w' }, '', 'k'), end: ['abc def ghi |jkl'], }); + newTest({ + title: 'Can handle bd-w move', + start: ['abc |def ghi jkl'], + keysPressed: easymotionCommand({ key: 'bdw', leaderCount: 3 }, '', 'k'), + end: ['|abc def ghi jkl'], + }); + newTest({ title: 'Can handle b move', start: ['abc def |ghi jkl'], - keysPressed: easymotionCommand('b', '', 'k'), + keysPressed: easymotionCommand({ key: 'b' }, '', 'k'), end: ['|abc def ghi jkl'], }); newTest({ title: 'Can handle e move', start: ['abc |def ghi jkl'], - keysPressed: easymotionCommand('e', '', 'k'), + keysPressed: easymotionCommand({ key: 'e' }, '', 'k'), end: ['abc def ghi jk|l'], }); + newTest({ + title: 'Can handle bd-e move', + start: ['abc |def ghi jkl'], + keysPressed: easymotionCommand({ key: 'bde', leaderCount: 3 }, '', 'k'), + end: ['abc| def ghi jkl'], + }); + newTest({ title: 'Can handle ge move', start: ['abc def |ghi jkl'], - keysPressed: easymotionCommand('ge', '', 'k'), + keysPressed: easymotionCommand({ key: 'ge' }, '', 'k'), end: ['ab|c def ghi jkl'], }); newTest({ title: 'Can handle n-char move', start: ['abc |def ghi jkl', 'abc def ghi jkl'], - keysPressed: easymotionCommand('/', 'ghi\n', 'k'), + keysPressed: easymotionCommand({ key: '/' }, 'ghi\n', 'k'), end: ['abc def ghi jkl', 'abc def |ghi jkl'], }); newTest({ title: 'Can handle j move', start: ['abc', 'd|ef', 'ghi', 'jkl'], - keysPressed: easymotionCommand('j', '', 'k'), + keysPressed: easymotionCommand({ key: 'j' }, '', 'k'), end: ['abc', 'def', 'ghi', '|jkl'], }); newTest({ title: 'Can handle k move', start: ['abc', 'def', 'g|hi', 'jkl'], - keysPressed: easymotionCommand('k', '', 'k'), + keysPressed: easymotionCommand({ key: 'k' }, '', 'k'), end: ['abc', '|def', 'ghi', 'jkl'], }); + + newTest({ + title: 'Can handle bd-jk move (1)', + start: ['abc', 'def', '|ghi', 'jkl'], + keysPressed: easymotionCommand({ key: 'bdjk', leaderCount: 3 }, '', 'k'), + end: ['abc', '|def', 'ghi', 'jkl'], + }); + + newTest({ + title: 'Can handle bd-jk move (2)', + start: ['abc', 'def', '|ghi', 'jkl'], + keysPressed: easymotionCommand({ key: 'bdjk', leaderCount: 3 }, '', 'h'), + end: ['abc', 'def', 'ghi', '|jkl'], + }); }); From 58cb458d73233016450a80166ff5bcb483c03365 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Thu, 14 Sep 2017 22:01:40 +0900 Subject: [PATCH 05/11] Add description about new easymoiton motions --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e5b41aaea7e..17349bed493 100644 --- a/README.md +++ b/README.md @@ -402,8 +402,12 @@ Motion Command | Description ` j`|Start of line forwards ` k`|Start of line backwards ` / ... `|Search n-character +` bdt`|Til character +` bdw`|Start of word +` bde`|End of word +` bdjk`|Start of line -` (2s|2f|2F|2t|2T) ` are also available. +` (2s|2f|2F|2t|2T) ` and ` bd2t char>` are also available. The difference is character count required for search. For example, ` 2s ` requires two characters, and search by two characters. This mapping is not a standard mapping, so it is recommended to use your custom mapping. From 0a534288704ec4b65ea695295f04f6effae90c49 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Thu, 14 Sep 2017 23:09:19 +0900 Subject: [PATCH 06/11] Improve easymotion messages on the status bar update 1 Extract to createCurrentCommandText method update 2 update 3 --- .../plugins/easymotion/easymotion.cmd.ts | 9 +++ src/mode/modeHandler.ts | 71 +++++++++++-------- 2 files changed, 50 insertions(+), 30 deletions(-) diff --git a/src/actions/plugins/easymotion/easymotion.cmd.ts b/src/actions/plugins/easymotion/easymotion.cmd.ts index 14d6278bcb6..6bb8be0e996 100644 --- a/src/actions/plugins/easymotion/easymotion.cmd.ts +++ b/src/actions/plugins/easymotion/easymotion.cmd.ts @@ -141,12 +141,17 @@ export interface EasyMotionSearchAction { updateSearchString(s: string): void; getSearchString(): string; getMatches(position: Position, vimState: VimState): EasyMotion.Match[]; + readonly searchCharCount: number; } export class SearchByCharCommand extends BaseEasyMotionCommand implements EasyMotionSearchAction { private _searchString: string = ''; private _options: EasyMotionCharMoveOpions; + get searchCharCount() { + return this._options.charCount; + } + constructor(options: EasyMotionCharMoveOpions) { super(options); this._options = options; @@ -194,6 +199,10 @@ export class SearchByCharCommand extends BaseEasyMotionCommand implements EasyMo export class SearchByNCharCommand extends BaseEasyMotionCommand implements EasyMotionSearchAction { private _searchString: string = ''; + get searchCharCount() { + return -1; + } + constructor() { super({}); } diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index fba04a6b463..ad5aff0ded3 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -115,15 +115,15 @@ export class VimState { public focusChanged = false; public surround: - | undefined - | { - active: boolean; - operator: 'change' | 'delete' | 'yank'; - target: string | undefined; - replacement: string | undefined; - range: Range | undefined; - isVisualLine: boolean; - } = undefined; + | undefined + | { + active: boolean; + operator: 'change' | 'delete' | 'yank'; + target: string | undefined; + replacement: string | undefined; + range: Range | undefined; + isVisualLine: boolean; + } = undefined; /** * Used for command like which allows you to return to insert after a command @@ -1049,8 +1049,8 @@ export class ModeHandler implements vscode.Disposable { x => x.start.isEarlierThan(x.stop) ? x.withNewStop( - x.stop.isLineEnd() ? x.stop.getRightThroughLineBreaks() : x.stop.getRight() - ) + x.stop.isLineEnd() ? x.stop.getRightThroughLineBreaks() : x.stop.getRight() + ) : x ); } @@ -1317,7 +1317,7 @@ export class ModeHandler implements vscode.Disposable { if ( recordedState.operators.length > 1 && recordedState.operators.reverse()[0].constructor === - recordedState.operators.reverse()[1].constructor + recordedState.operators.reverse()[1].constructor ) { resultVimState = await recordedState.operator.runRepeat( resultVimState, @@ -1905,8 +1905,8 @@ export class ModeHandler implements vscode.Disposable { const easyMotionHighlightRanges = this.currentMode.name === ModeName.EasyMotionInputMode ? vimState.easyMotion.searchAction - .getMatches(vimState.cursorPosition, vimState) - .map(x => x.toRange()) + .getMatches(vimState.cursorPosition, vimState) + .map(x => x.toRange()) : []; this.vimState.editor.setDecorations( this._easymotionHighlightDecoration, @@ -1937,8 +1937,6 @@ export class ModeHandler implements vscode.Disposable { } else if (this.currentMode.name === ModeName.EasyMotionMode) { // Update all EasyMotion decorations this._vimState.easyMotion.updateDecorations(); - - this.setStatusBarText(`Current depth: ${this.vimState.easyMotion.accumulation}`); } else { this._renderStatusBar(); } @@ -1953,40 +1951,53 @@ export class ModeHandler implements vscode.Disposable { vscode.commands.executeCommand('setContext', 'vim.platform', process.platform); } - private _renderStatusBar(): void { - const modeText = `-- ${this.currentMode.text.toUpperCase()} ${this._vimState.isMultiCursor - ? 'MULTI CURSOR' - : ''} --`; - const macroText = ` ${this._vimState.isRecordingMacro - ? 'Recording @' + this._vimState.recordedMacro.registerName - : ''}`; - let currentCommandText = ` ${this._vimState.recordedState.commandString}`; - + private _createCurrentCommandText(): string { if (this._vimState.currentMode === ModeName.Insert) { - currentCommandText = ''; + return ''; } if (this._vimState.currentMode === ModeName.SearchInProgressMode) { - currentCommandText = ` ${this._vimState.globalState.searchState!.searchString}`; + return `${this._vimState.globalState.searchState!.searchString}`; } if (this.vimState.currentMode === ModeName.EasyMotionInputMode) { const state = this.vimState.easyMotion; if (state) { - currentCommandText = state.searchAction.getSearchString(); + const searchCharCount = state.searchAction.searchCharCount; + const message = + searchCharCount > 0 + ? `Search for ${searchCharCount} character(s): ` + : 'Search for characters: '; + return message + state.searchAction.getSearchString(); } } + if (this._vimState.currentMode === ModeName.EasyMotionMode) { + return `Target key: ${this.vimState.easyMotion.accumulation}`; + } + if (this._vimState.currentMode === ModeName.SurroundInputMode) { if (this._vimState.surround !== undefined) { const surroundText = this._vimState.surround.replacement; if (surroundText !== undefined) { - currentCommandText = surroundText; + return surroundText; } } } - this.setStatusBarText(`${modeText}${currentCommandText}${macroText}`); + return `${this._vimState.recordedState.commandString}`; + } + + private _renderStatusBar(): void { + const modeText = `-- ${this.currentMode.text.toUpperCase()} ${this._vimState.isMultiCursor + ? 'MULTI CURSOR' + : ''} --`; + const macroText = this._vimState.isRecordingMacro + ? 'Recording @' + this._vimState.recordedMacro.registerName + : ''; + + const statusBarText = [modeText, this._createCurrentCommandText(), macroText].join(' '); + this.setStatusBarText(statusBarText); } async handleMultipleKeyEvents(keys: string[]): Promise { From 7d62dfed056e1d3653b13bf6ee5745e777ba9b0b Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Thu, 14 Sep 2017 23:42:53 +0900 Subject: [PATCH 07/11] Make sure not to go to easymotion input state when easymoiton is disabled --- .../plugins/easymotion/easymotion.cmd.ts | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/src/actions/plugins/easymotion/easymotion.cmd.ts b/src/actions/plugins/easymotion/easymotion.cmd.ts index 6bb8be0e996..24f859a01c5 100644 --- a/src/actions/plugins/easymotion/easymotion.cmd.ts +++ b/src/actions/plugins/easymotion/easymotion.cmd.ts @@ -257,13 +257,18 @@ export class EasyMotionCharMoveCommandBase extends BaseCommand { } public async exec(position: Position, vimState: VimState): Promise { - vimState.easyMotion = new EasyMotion(); - vimState.easyMotion.previousMode = vimState.currentMode; - vimState.easyMotion.searchAction = this._action; - vimState.globalState.hl = true; + // Only execute the action if easymotion is enabled + if (!Configuration.easymotion) { + return vimState; + } else { + vimState.easyMotion = new EasyMotion(); + vimState.easyMotion.previousMode = vimState.currentMode; + vimState.easyMotion.searchAction = this._action; + vimState.globalState.hl = true; - vimState.currentMode = ModeName.EasyMotionInputMode; - return vimState; + vimState.currentMode = ModeName.EasyMotionInputMode; + return vimState; + } } } From abffbce06dc2f2a16cbfdcaccf1f63f3ff612145 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Fri, 15 Sep 2017 23:27:02 +0900 Subject: [PATCH 08/11] Fix EasyMotion Mode is not rendered on the status bar after switching texteditors --- src/mode/modeHandler.ts | 39 +++++++++++++++++---------------------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/src/mode/modeHandler.ts b/src/mode/modeHandler.ts index ad5aff0ded3..f2057ec8ddd 100644 --- a/src/mode/modeHandler.ts +++ b/src/mode/modeHandler.ts @@ -115,15 +115,15 @@ export class VimState { public focusChanged = false; public surround: - | undefined - | { - active: boolean; - operator: 'change' | 'delete' | 'yank'; - target: string | undefined; - replacement: string | undefined; - range: Range | undefined; - isVisualLine: boolean; - } = undefined; + | undefined + | { + active: boolean; + operator: 'change' | 'delete' | 'yank'; + target: string | undefined; + replacement: string | undefined; + range: Range | undefined; + isVisualLine: boolean; + } = undefined; /** * Used for command like which allows you to return to insert after a command @@ -1049,8 +1049,8 @@ export class ModeHandler implements vscode.Disposable { x => x.start.isEarlierThan(x.stop) ? x.withNewStop( - x.stop.isLineEnd() ? x.stop.getRightThroughLineBreaks() : x.stop.getRight() - ) + x.stop.isLineEnd() ? x.stop.getRightThroughLineBreaks() : x.stop.getRight() + ) : x ); } @@ -1317,7 +1317,7 @@ export class ModeHandler implements vscode.Disposable { if ( recordedState.operators.length > 1 && recordedState.operators.reverse()[0].constructor === - recordedState.operators.reverse()[1].constructor + recordedState.operators.reverse()[1].constructor ) { resultVimState = await recordedState.operator.runRepeat( resultVimState, @@ -1905,8 +1905,8 @@ export class ModeHandler implements vscode.Disposable { const easyMotionHighlightRanges = this.currentMode.name === ModeName.EasyMotionInputMode ? vimState.easyMotion.searchAction - .getMatches(vimState.cursorPosition, vimState) - .map(x => x.toRange()) + .getMatches(vimState.cursorPosition, vimState) + .map(x => x.toRange()) : []; this.vimState.editor.setDecorations( this._easymotionHighlightDecoration, @@ -1930,16 +1930,11 @@ export class ModeHandler implements vscode.Disposable { this.vimState.postponedCodeViewChanges = []; - if (this.currentMode.name === ModeName.SearchInProgressMode) { - this.setStatusBarText( - `Searching for: ${this.vimState.globalState.searchState!.searchString}` - ); - } else if (this.currentMode.name === ModeName.EasyMotionMode) { + if (this.currentMode.name === ModeName.EasyMotionMode) { // Update all EasyMotion decorations this._vimState.easyMotion.updateDecorations(); - } else { - this._renderStatusBar(); } + this._renderStatusBar(); vscode.commands.executeCommand('setContext', 'vim.useCtrlKeys', Configuration.useCtrlKeys); vscode.commands.executeCommand('setContext', 'vim.overrideCopy', Configuration.overrideCopy); @@ -1957,7 +1952,7 @@ export class ModeHandler implements vscode.Disposable { } if (this._vimState.currentMode === ModeName.SearchInProgressMode) { - return `${this._vimState.globalState.searchState!.searchString}`; + return `Searching for: ${this._vimState.globalState.searchState!.searchString}`; } if (this.vimState.currentMode === ModeName.EasyMotionInputMode) { From 51733e21311a076f551ae023df61be82bad7cec2 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Sat, 16 Sep 2017 00:14:14 +0900 Subject: [PATCH 09/11] Run prettier --- src/configuration/configuration.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index a663bf94ee7..ac4731abe9b 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -316,9 +316,9 @@ function overlapSetting(args: { default: OptionValue; codeValueMapping?: ValueMapping; }) { - return function (target: any, propertyKey: string) { + return function(target: any, propertyKey: string) { Object.defineProperty(target, propertyKey, { - get: function () { + get: function() { if (this['_' + propertyKey] !== undefined) { return this['_' + propertyKey]; } @@ -333,7 +333,7 @@ function overlapSetting(args: { return vscode.workspace.getConfiguration('editor').get(args.codeName, args.default); } }, - set: function (value) { + set: function(value) { this['_' + propertyKey] = value; taskQueue.enqueueTask({ From 05bfeade8fa0b81394bcae6ade8ee3e81ddc7a02 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Sat, 16 Sep 2017 09:10:48 +0900 Subject: [PATCH 10/11] Tweaks --- src/actions/plugins/easymotion/easymotion.ts | 5 +++-- src/actions/plugins/easymotion/markerGenerator.ts | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/actions/plugins/easymotion/easymotion.ts b/src/actions/plugins/easymotion/easymotion.ts index 176ac3d5be9..e4a1cace9eb 100644 --- a/src/actions/plugins/easymotion/easymotion.ts +++ b/src/actions/plugins/easymotion/easymotion.ts @@ -225,7 +225,7 @@ export class EasyMotion { private getEasymotionMarkerForegroundColorTwoChar() { return this.getMarkerColor( Configuration.easymotionMarkerForegroundColorTwoChar, - '#f00', + '#ffa500', 'activityBarBadge.foreground' ); } @@ -257,7 +257,8 @@ export class EasyMotion { backgroundColor: this.getEasymotionMarkerBackgroundColor(), height: `${Configuration.easymotionMarkerHeight}px`, width: `${keystroke.length * Configuration.easymotionMarkerWidthPerChar}px`, - color: `${fontColor}; + color: fontColor, + textDecoration: `none; font-family: ${Configuration.easymotionMarkerFontFamily}; font-size: ${Configuration.easymotionMarkerFontSize}px; font-weight: ${Configuration.easymotionMarkerFontWeight}; diff --git a/src/actions/plugins/easymotion/markerGenerator.ts b/src/actions/plugins/easymotion/markerGenerator.ts index 30dd3133c2d..a6bde09455f 100644 --- a/src/actions/plugins/easymotion/markerGenerator.ts +++ b/src/actions/plugins/easymotion/markerGenerator.ts @@ -36,7 +36,7 @@ export class MarkerGenerator { private createPrefixKeyTable(): string[] { const keyTable = this.keyTable; const totalRemainder = Math.max(this.matchesCount - keyTable.length, 0); - const totalSteps = Math.floor(totalRemainder / keyTable.length) + 1; + const totalSteps = Math.ceil(totalRemainder / keyTable.length); const reversed = this.keyTable.slice().reverse(); const count = Math.min(totalSteps, reversed.length); return reversed.slice(0, count); From 8c9a05df500bb6985cec20a1d6e244fda7f637d3 Mon Sep 17 00:00:00 2001 From: Maxfield Walker Date: Sat, 16 Sep 2017 11:20:16 +0900 Subject: [PATCH 11/11] passing test --- test/plugins/easymotion.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/easymotion.test.ts b/test/plugins/easymotion.test.ts index 7a644809a67..ae2d0fc21f1 100644 --- a/test/plugins/easymotion.test.ts +++ b/test/plugins/easymotion.test.ts @@ -144,7 +144,7 @@ suite('easymotion plugin', () => { title: 'Can handle bd-e move', start: ['abc |def ghi jkl'], keysPressed: easymotionCommand({ key: 'bde', leaderCount: 3 }, '', 'k'), - end: ['abc| def ghi jkl'], + end: ['ab|c def ghi jkl'], }); newTest({