From 34501026ed4ea2edb023fa672734ddc19aac8b4f Mon Sep 17 00:00:00 2001 From: berknam Date: Tue, 30 Jun 2020 02:19:38 +0100 Subject: [PATCH] Implement RegisterPluginAction to surround plugin - Create 'StartSurroundMode' function similar to the previous 'CommandSurroundModeStart' action exec - Create 'BaseSurroundCommand' used by 'cs', 'ds' and 'S' in visual mode - Implements 'ys' has an operator - Make all surround actions only apply if surround is set in configuration - Add 'surround' plugin name to the 'isPluginActive'' from remappingValidator --- src/actions/operator.ts | 11 - src/actions/plugins/surround.ts | 216 ++++++++++-------- .../validators/remappingValidator.ts | 2 + 3 files changed, 118 insertions(+), 111 deletions(-) diff --git a/src/actions/operator.ts b/src/actions/operator.ts index de2feeff5b79..ef77c444f51b 100644 --- a/src/actions/operator.ts +++ b/src/actions/operator.ts @@ -267,17 +267,6 @@ export class YankOperator extends BaseOperator { canBeRepeatedWithDot = false; public async run(vimState: VimState, start: Position, end: Position): Promise { - // Hack to make Surround with y (which takes a motion) work. - - if (vimState.surround) { - vimState.surround.range = new Range(start, end); - await vimState.setCurrentMode(Mode.SurroundInputMode); - vimState.cursorStopPosition = start; - vimState.cursorStartPosition = start; - - return vimState; - } - const originalMode = vimState.currentMode; [start, end] = sorted(start, end); diff --git a/src/actions/plugins/surround.ts b/src/actions/plugins/surround.ts index 390b0d572930..7caa5e846112 100644 --- a/src/actions/plugins/surround.ts +++ b/src/actions/plugins/surround.ts @@ -3,9 +3,9 @@ import { PairMatcher } from './../../common/matching/matcher'; import { Position, PositionDiff, sorted } from './../../common/motion/position'; import { Range } from './../../common/motion/range'; import { configuration } from './../../configuration/configuration'; -import { Mode } from './../../mode/mode'; +import { Mode, isVisualMode } from './../../mode/mode'; import { TextEditor } from './../../textEditor'; -import { RegisterAction } from './../base'; +import { RegisterAction, RegisterPluginAction } from './../base'; import { BaseCommand } from './../commands/actions'; import { BaseMovement, IMovement } from '../baseMotion'; import { @@ -21,7 +21,7 @@ import { MoveInsideTag, MoveQuoteMatch, } from '../motion'; -import { ChangeOperator, DeleteOperator, YankOperator } from './../operator'; +import { ChangeOperator, DeleteOperator, YankOperator, BaseOperator } from './../operator'; import { SelectInnerBigWord, SelectInnerParagraph, @@ -29,6 +29,7 @@ import { SelectInnerWord, TextObjectMovement, } from './../textobject'; +import { RegisterMode } from '../../register/register'; @RegisterAction class CommandSurroundAddTarget extends BaseCommand { @@ -116,139 +117,146 @@ class CommandSurroundAddTarget extends BaseCommand { } } -// Aaaaagghhhh. I tried so hard to make surround an operator to make use of our -// sick new operator repeat structure, but there's just no clean way to do it. -// In the future, if somebody wants to refactor Surround, the big problem for -// why it's so weird is that typing `ys` loads up the Yank operator first, -// which prevents us from making a surround operator that's `ys` or something. -// You'd need to refactor our keybinding handling to "give up" keystrokes if it -// can't find a match. +async function StartSurroundMode( + vimState: VimState, + operatorString: 'change' | 'delete' | 'yank' | undefined = undefined, + keys: string[], + start: Position | undefined, + end: Position | undefined +): Promise { + // Only execute the action if the configuration is set + if (!configuration.surround || !operatorString || keys.length === 0) { + return vimState; + } -@RegisterAction -class CommandSurroundModeRepeat extends BaseMovement { - modes = [Mode.OperatorPendingMode]; - keys = ['s']; - isCompleteAction = false; - runsOnceForEveryCursor() { - return false; + // Start to record the keys to store for playback of surround using dot + vimState.recordedState.surroundKeys.push(keys[0]); + if (operatorString === 'yank' && vimState.recordedState.operator) { + // include the motion keys after the operator + vimState.recordedState.surroundKeyIndexStart = + vimState.keyHistory.length - vimState.recordedState.actionKeys.length; + } else { + vimState.recordedState.surroundKeyIndexStart = vimState.keyHistory.length; } - public async execAction(position: Position, vimState: VimState): Promise { - return { - start: position.getLineBeginRespectingIndent(), - stop: position.getLineEnd().getLastWordEnd().getRight(), - }; + let range: Range | undefined = undefined; + if (start && end) { + range = new Range(start, end); } - public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { - return super.doesActionApply(vimState, keysPressed) && vimState.surround !== undefined; + vimState.surround = { + active: true, + target: undefined, + operator: operatorString, + replacement: undefined, + range: range, + previousMode: vimState.currentMode, + }; + + if (operatorString === 'yank' && start) { + if (isVisualMode(vimState.surround.previousMode) && end) { + // Put the cursor at the beginning of the visual selection + vimState.cursorStopPosition = start; + vimState.cursorStartPosition = end; + } else { + vimState.cursorStopPosition = start; + vimState.cursorStartPosition = start; + } } + await vimState.setCurrentMode(Mode.SurroundInputMode); + + return vimState; } -@RegisterAction -class CommandSurroundModeStart extends BaseCommand { - modes = [Mode.OperatorPendingMode]; - keys = ['s']; +class BaseSurroundCommand extends BaseCommand { + keys: string[] = []; + operatorString: 'change' | 'delete' | 'yank' | undefined = undefined; isCompleteAction = false; runsOnceForEveryCursor() { return false; } public async exec(position: Position, vimState: VimState): Promise { - // Only execute the action if the configuration is set - if (!configuration.surround) { + if (!this.operatorString || this.keys.length === 0) { return vimState; } - - const operator = vimState.recordedState.operator; - let operatorString: 'change' | 'delete' | 'yank' | undefined; - - if (operator instanceof ChangeOperator) { - operatorString = 'change'; - } else if (operator instanceof DeleteOperator) { - operatorString = 'delete'; - } else if (operator instanceof YankOperator) { - operatorString = 'yank'; - } - - if (!operatorString) { - return vimState; - } - - // Start to record the keys to store for playback of surround using dot - vimState.recordedState.surroundKeys.push(vimState.keyHistory[vimState.keyHistory.length - 2]); - vimState.recordedState.surroundKeys.push('s'); - vimState.recordedState.surroundKeyIndexStart = vimState.keyHistory.length; - - vimState.surround = { - active: true, - target: undefined, - operator: operatorString, - replacement: undefined, - range: undefined, - previousMode: vimState.currentMode, - }; - - if (operatorString !== 'yank') { - await vimState.setCurrentMode(Mode.SurroundInputMode); - } - - return vimState; + return StartSurroundMode(vimState, this.operatorString, this.keys, undefined, undefined); } public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { - const hasSomeOperator = !!vimState.recordedState.operator; - - return super.doesActionApply(vimState, keysPressed) && hasSomeOperator; + return configuration.surround && super.doesActionApply(vimState, keysPressed); } public couldActionApply(vimState: VimState, keysPressed: string[]): boolean { - const hasSomeOperator = !!vimState.recordedState.operator; - - return super.doesActionApply(vimState, keysPressed) && hasSomeOperator; + return configuration.surround && super.couldActionApply(vimState, keysPressed); } } -@RegisterAction -class CommandSurroundModeStartVisual extends BaseCommand { - modes = [Mode.Visual, Mode.VisualLine]; - keys = ['S']; - isCompleteAction = false; - runsOnceForEveryCursor() { - return false; - } +@RegisterPluginAction('surround') +class CommandChangeSurround extends BaseSurroundCommand { + modes = [Mode.Normal]; + pluginActionDefaultKeys = ['c', 's']; + keys = ['Csurround']; + operatorString: 'change' | 'delete' | 'yank' | undefined = 'change'; +} - public async exec(position: Position, vimState: VimState): Promise { - // Only execute the action if the configuration is set - if (!configuration.surround) { - return vimState; - } +@RegisterPluginAction('surround') +class CommandDeleteSurround extends BaseSurroundCommand { + modes = [Mode.Normal]; + pluginActionDefaultKeys = ['d', 's']; + keys = ['Dsurround']; + operatorString: 'change' | 'delete' | 'yank' | undefined = 'delete'; +} - // Start to record the keys to store for playback of surround using dot - vimState.recordedState.surroundKeys.push('S'); - vimState.recordedState.surroundKeyIndexStart = vimState.keyHistory.length; +@RegisterPluginAction('surround') +class CommandSurroundModeStartVisual extends BaseSurroundCommand { + modes = [Mode.Visual, Mode.VisualLine]; + pluginActionDefaultKeys = ['S']; + keys = ['VSurround']; + operatorString: 'change' | 'delete' | 'yank' | undefined = 'yank'; + public async exec(position: Position, vimState: VimState): Promise { let [start, end] = sorted(vimState.cursorStartPosition, vimState.cursorStopPosition); if (vimState.currentMode === Mode.VisualLine) { [start, end] = [start.getLineBegin(), end.getLineEnd()]; } + return StartSurroundMode(vimState, 'yank', this.keys, start, end); + } +} - vimState.surround = { - active: true, - target: undefined, - operator: 'yank', - replacement: undefined, - range: new Range(start, end), - previousMode: vimState.currentMode, - }; +@RegisterPluginAction('surround') +class CommandSurroundModeStartLine extends BaseSurroundCommand { + modes = [Mode.Normal]; + pluginActionDefaultKeys = ['y', 's', 's']; + keys = ['Yssurround']; - await vimState.setCurrentMode(Mode.SurroundInputMode); + public async exec(position: Position, vimState: VimState): Promise { + const start: Position = position.getLineBeginRespectingIndent(); + const end: Position = position.getLineEnd().getLastWordEnd().getRight(); + return StartSurroundMode(vimState, 'yank', this.keys, start, end); + } +} - // Put the cursor at the beginning of the visual selection - vimState.cursorStopPosition = start; - vimState.cursorStartPosition = end; +@RegisterPluginAction('surround') +class SurroundModeStartOperator extends BaseOperator { + modes = [Mode.Normal]; + pluginActionDefaultKeys = ['y', 's']; + keys = ['Ysurround']; + isCompleteAction = false; + runsOnceForEveryCursor() { + return false; + } - return vimState; + public async run(vimState: VimState, start: Position, end: Position): Promise { + return StartSurroundMode(vimState, 'yank', this.keys, start, end); + } + + public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { + return configuration.surround && super.doesActionApply(vimState, keysPressed); + } + + public couldActionApply(vimState: VimState, keysPressed: string[]): boolean { + return configuration.surround && super.couldActionApply(vimState, keysPressed); } } @@ -257,6 +265,14 @@ export class CommandSurroundAddToReplacement extends BaseCommand { modes = [Mode.SurroundInputMode]; keys = ['']; + public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { + return configuration.surround && super.doesActionApply(vimState, keysPressed); + } + + public couldActionApply(vimState: VimState, keysPressed: string[]): boolean { + return configuration.surround && super.couldActionApply(vimState, keysPressed); + } + public async exec(position: Position, vimState: VimState): Promise { if (!vimState.surround) { return vimState; diff --git a/src/configuration/validators/remappingValidator.ts b/src/configuration/validators/remappingValidator.ts index f85ebc92e650..d7d28aff953c 100644 --- a/src/configuration/validators/remappingValidator.ts +++ b/src/configuration/validators/remappingValidator.ts @@ -140,6 +140,8 @@ export class RemappingValidator implements IConfigurationValidator { return config.replaceWithRegister; case 'sneak': return config.sneak; + case 'surround': + return config.surround; default: return false; }