diff --git a/package.json b/package.json index bcc0e20e25d..af9bb98e637 100644 --- a/package.json +++ b/package.json @@ -475,6 +475,11 @@ "type": "boolean", "description": "In visual mode, start a search with * or # using the current selection", "default": false + }, + "vim.foldfix": { + "type": "boolean", + "description": "Uses a hack to move around folds properly", + "default": false } } } diff --git a/src/actions/base.ts b/src/actions/base.ts index ef91f1f5ae8..786c3847691 100644 --- a/src/actions/base.ts +++ b/src/actions/base.ts @@ -139,12 +139,16 @@ export class Actions { * * If no action could ever match, returns false. */ - public static getRelevantAction(keysPressed: string[], vimState: VimState): BaseAction | KeypressState { + public static getRelevantAction(keysPressed: string[], vimState: VimState): BaseAction |KeypressState { let couldPotentiallyHaveMatch = false; for (const thing of Actions.allActions) { const { type, action } = thing!; + // It's an action that can't be called directly. + if (action.keys === undefined) { + continue; + } if (action.doesActionApply(vimState, keysPressed)) { const result = new type(); diff --git a/src/actions/motion.ts b/src/actions/motion.ts index b8af42ba6fb..61bea693151 100644 --- a/src/actions/motion.ts +++ b/src/actions/motion.ts @@ -1,6 +1,7 @@ import * as vscode from 'vscode'; import { ModeName } from './../mode/mode'; import { Position, PositionDiff } from './../common/motion/position'; +import { Configuration } from './../configuration/configuration'; import { TextEditor, CursorMovePosition, CursorMoveByUnit } from './../textEditor'; import { VimState } from './../mode/modeHandler'; import { RegisterMode } from './../register/register'; @@ -14,7 +15,7 @@ import { BaseAction } from './base'; export function isIMovement(o: IMovement | Position): o is IMovement { return (o as IMovement).start !== undefined && - (o as IMovement).stop !== undefined; + (o as IMovement).stop !== undefined; } /** @@ -42,10 +43,10 @@ export interface IMovement { */ export abstract class BaseMovement extends BaseAction { modes = [ - ModeName.Normal, - ModeName.Visual, - ModeName.VisualLine, - ModeName.VisualBlock, + ModeName.Normal, + ModeName.Visual, + ModeName.VisualLine, + ModeName.VisualBlock, ]; isMotion = true; @@ -107,8 +108,8 @@ export abstract class BaseMovement extends BaseAction { const firstIteration = (i === 0); const lastIteration = (i === count - 1); const temporaryResult = (recordedState.operator && lastIteration) ? - await this.execActionForOperator(position, vimState) : - await this.execAction(position, vimState); + await this.execActionForOperator(position, vimState) : + await this.execAction(position, vimState); if (temporaryResult instanceof Position) { result = temporaryResult; @@ -142,15 +143,225 @@ export abstract class BaseMovement extends BaseAction { } } +abstract class MoveByScreenLine extends BaseMovement { + modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; + movementType: CursorMovePosition; + by: CursorMoveByUnit; + value: number = 1; + + public async execAction(position: Position, vimState: VimState): Promise { + await vscode.commands.executeCommand("cursorMove", { + to: this.movementType, + select: vimState.currentMode !== ModeName.Normal, + by: this.by, + value: this.value + }); + + if (vimState.currentMode === ModeName.Normal) { + return Position.FromVSCodePosition(vimState.editor.selection.active); + } else { + /** + * cursorMove command is handling the selection for us. + * So we are not following our design principal (do no real movement inside an action) here. + */ + let start = Position.FromVSCodePosition(vimState.editor.selection.start); + let stop = Position.FromVSCodePosition(vimState.editor.selection.end); + let curPos = Position.FromVSCodePosition(vimState.editor.selection.active); + + // We want to swap the cursor start stop positions based on which direction we are moving, up or down + if (start.isEqual(curPos)) { + position = start; + [start, stop] = [stop, start]; + start = start.getLeft(); + } + + return { start, stop }; + } + } + + public async execActionForOperator(position: Position, vimState: VimState): Promise { + await vscode.commands.executeCommand("cursorMove", { + to: this.movementType, + select: true, + by: this.by, + value: this.value + }); + + return { + start: Position.FromVSCodePosition(vimState.editor.selection.start), + stop: Position.FromVSCodePosition(vimState.editor.selection.end) + }; + } +} + +abstract class MoveByScreenLineMaintainDesiredColumn extends MoveByScreenLine { + doesntChangeDesiredColumn = true; + public async execAction(position: Position, vimState: VimState): Promise < Position | IMovement > { + let prevDesiredColumn = vimState.desiredColumn; + let prevLine = vimState.editor.selection.active.line; + + await vscode.commands.executeCommand("cursorMove", { + to: this.movementType, + select: vimState.currentMode !== ModeName.Normal, + by: this.by, + value: this.value + }); + + if (vimState.currentMode === ModeName.Normal) { + let returnedPos = Position.FromVSCodePosition(vimState.editor.selection.active); + if (prevLine !== returnedPos.line) { + returnedPos = returnedPos.setLocation(returnedPos.line, prevDesiredColumn); + } + return returnedPos; + } else { + /** + * cursorMove command is handling the selection for us. + * So we are not following our design principal (do no real movement inside an action) here. + */ + let start = Position.FromVSCodePosition(vimState.editor.selection.start); + let stop = Position.FromVSCodePosition(vimState.editor.selection.end); + let curPos = Position.FromVSCodePosition(vimState.editor.selection.active); + + // We want to swap the cursor start stop positions based on which direction we are moving, up or down + if (start.isEqual(curPos)) { + position = start; + [start, stop] = [stop, start]; + start = start.getLeft(); + } + + return { start, stop }; + + } + } +} + +class MoveDownByScreenLineMaintainDesiredColumn extends MoveByScreenLineMaintainDesiredColumn { + movementType: CursorMovePosition = "down"; + by: CursorMoveByUnit = "wrappedLine"; + value = 1; +} + +class MoveDownFoldFix extends MoveByScreenLineMaintainDesiredColumn { + movementType: CursorMovePosition = "down"; + by: CursorMoveByUnit = "line"; + value = 1; + + public async execAction(position: Position, vimState: VimState): Promise < Position | IMovement > { + if (position.line === TextEditor.getLineCount() - 1) { + return position; + } + let t: Position; + let count = 0; + const prevDesiredColumn = vimState.desiredColumn; + do { + t = (await new MoveDownByScreenLine().execAction(position, vimState)); + count += 1; + } while (t.line === position.line); + if (t.line > position.line + 1) { + return t; + } + while (count > 0) { + t = await new MoveUpByScreenLine().execAction(position, vimState); + count--; + } + vimState.desiredColumn = prevDesiredColumn; + return await position.getDown(vimState.desiredColumn); + } +} + +@RegisterAction +class MoveDown extends BaseMovement { + keys = ["j"]; + doesntChangeDesiredColumn = true; + + + public async execAction(position: Position, vimState: VimState): Promise { + if (Configuration.foldfix && vimState.currentMode !== ModeName.VisualBlock) { + return new MoveDownFoldFix().execAction(position, vimState); + } + return position.getDown(vimState.desiredColumn); + } + + public async execActionForOperator(position: Position, vimState: VimState): Promise { + vimState.currentRegisterMode = RegisterMode.LineWise; + return position.getDown(position.getLineEnd().character); + } + +} + +@RegisterAction +class MoveDownArrow extends MoveDown { + keys = [""]; +} + +class MoveUpByScreenLineMaintainDesiredColumn extends MoveByScreenLineMaintainDesiredColumn { + movementType: CursorMovePosition = "up"; + by: CursorMoveByUnit = "wrappedLine"; + value = 1; +} + +@RegisterAction +class MoveUp extends BaseMovement { + keys = ["k"]; + doesntChangeDesiredColumn = true; + + public async execAction(position: Position, vimState: VimState): Promise { + if (Configuration.foldfix && vimState.currentMode !== ModeName.VisualBlock) { + return new MoveUpFoldFix().execAction(position, vimState); + } + return position.getUp(vimState.desiredColumn); + } + + public async execActionForOperator(position: Position, vimState: VimState): Promise { + vimState.currentRegisterMode = RegisterMode.LineWise; + return position.getUp(position.getLineEnd().character); + } +} + +@RegisterAction +class MoveUpFoldFix extends MoveByScreenLineMaintainDesiredColumn { + movementType: CursorMovePosition = "up"; + by: CursorMoveByUnit = "line"; + value = 1; + + public async execAction(position: Position, vimState: VimState): Promise < Position | IMovement > { + if (position.line === 0) { + return position; + } + let t: Position; + const prevDesiredColumn = vimState.desiredColumn; + let count = 0; + + do { + t = (await new MoveUpByScreenLineMaintainDesiredColumn().execAction(position, vimState)); + count += 1; + } while (t.line === position.line); + vimState.desiredColumn = prevDesiredColumn; + if (t.line < position.line - 1) { + return t; + } + while (count > 0) { + t = await new MoveDownByScreenLine().execAction(position, vimState); + count--; + } + vimState.desiredColumn = prevDesiredColumn; + return await position.getUp(vimState.desiredColumn); + } +} + +@RegisterAction +class MoveUpArrow extends MoveUp { + keys = [""]; +} @RegisterAction class ArrowsInReplaceMode extends BaseMovement { modes = [ModeName.Replace]; keys = [ - [""], - [""], - [""], - [""], + [""], + [""], + [""], + [""], ]; public async execAction(position: Position, vimState: VimState): Promise { @@ -158,19 +369,19 @@ class ArrowsInReplaceMode extends BaseMovement { switch (this.keysPressed[0]) { case "": - newPosition = await new MoveUpArrow().execAction(position, vimState); - break; + newPosition = (await new MoveUpArrow().execAction(position, vimState)); + break; case "": - newPosition = await new MoveDownArrow().execAction(position, vimState); - break; + newPosition = (await new MoveDownArrow().execAction(position, vimState)); + break; case "": - newPosition = await new MoveLeftArrow().execAction(position, vimState); - break; + newPosition = await new MoveLeftArrow().execAction(position, vimState); + break; case "": - newPosition = await new MoveRightArrow().execAction(position, vimState); - break; + newPosition = await new MoveRightArrow().execAction(position, vimState); + break; default: - break; + break; } vimState.replaceState = new ReplaceState(newPosition); return newPosition; @@ -290,48 +501,6 @@ class BackSpaceInNormalMode extends BaseMovement { } } -@RegisterAction -class MoveUp extends BaseMovement { - keys = ["k"]; - doesntChangeDesiredColumn = true; - - public async execAction(position: Position, vimState: VimState): Promise { - return position.getUp(vimState.desiredColumn); - } - - public async execActionForOperator(position: Position, vimState: VimState): Promise { - vimState.currentRegisterMode = RegisterMode.LineWise; - return position.getUp(position.getLineEnd().character); - } -} - -@RegisterAction -class MoveUpArrow extends MoveUp { - modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; - keys = [""]; -} - -@RegisterAction -class MoveDown extends BaseMovement { - keys = ["j"]; - doesntChangeDesiredColumn = true; - - public async execAction(position: Position, vimState: VimState): Promise { - return position.getDown(vimState.desiredColumn); - } - - public async execActionForOperator(position: Position, vimState: VimState): Promise { - vimState.currentRegisterMode = RegisterMode.LineWise; - return position.getDown(position.getLineEnd().character); - } -} - -@RegisterAction -class MoveDownArrow extends MoveDown { - modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine, ModeName.VisualBlock]; - keys = [""]; -} - @RegisterAction class MoveRight extends BaseMovement { keys = ["l"]; @@ -363,7 +532,7 @@ class MoveDownNonBlank extends BaseMovement { public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise { return position.getDownByCount(Math.max(count, 1)) - .getFirstLineNonBlankChar(); + .getFirstLineNonBlankChar(); } } @@ -373,7 +542,7 @@ class MoveUpNonBlank extends BaseMovement { public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise { return position.getUpByCount(Math.max(count, 1)) - .getFirstLineNonBlankChar(); + .getFirstLineNonBlankChar(); } } @@ -383,7 +552,7 @@ class MoveDownUnderscore extends BaseMovement { public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise { return position.getDownByCount(Math.max(count - 1, 0)) - .getFirstLineNonBlankChar(); + .getFirstLineNonBlankChar(); } } @@ -515,10 +684,10 @@ class MoveRepeat extends BaseMovement { class MoveRepeatReversed extends BaseMovement { keys = [","]; static reverseMotionMapping: Map BaseMovement> = new Map([ - [MoveFindForward, () => new MoveFindBackward()], - [MoveFindBackward, () => new MoveFindForward()], - [MoveTilForward, () => new MoveTilBackward()], - [MoveTilBackward, () => new MoveTilForward()] + [MoveFindForward, () => new MoveFindBackward()], + [MoveFindBackward, () => new MoveFindForward()], + [MoveTilForward, () => new MoveTilBackward()], + [MoveTilBackward, () => new MoveTilForward()] ]); public async execActionWithCount(position: Position, vimState: VimState, count: number): Promise { @@ -541,9 +710,9 @@ class MoveRepeatReversed extends BaseMovement { @RegisterAction class MoveLineEnd extends BaseMovement { keys = [ - ["$"], - [""], - [""]]; + ["$"], + [""], + [""]]; setsDesiredColumnToEOL = true; public async execAction(position: Position, vimState: VimState): Promise { @@ -563,64 +732,12 @@ class MoveLineBegin extends BaseMovement { public doesActionApply(vimState: VimState, keysPressed: string[]): boolean { return super.doesActionApply(vimState, keysPressed) && - vimState.recordedState.count === 0; + vimState.recordedState.count === 0; } public couldActionApply(vimState: VimState, keysPressed: string[]): boolean { return super.couldActionApply(vimState, keysPressed) && - vimState.recordedState.count === 0; - } -} - -abstract class MoveByScreenLine extends BaseMovement { - modes = [ModeName.Normal, ModeName.Visual, ModeName.VisualLine]; - movementType: CursorMovePosition; - by: CursorMoveByUnit; - value: number = 1; - - public async execAction(position: Position, vimState: VimState): Promise { - await vscode.commands.executeCommand("cursorMove", { - to: this.movementType, - select: vimState.currentMode !== ModeName.Normal, - by: this.by, - value: this.value - }); - - - if (vimState.currentMode === ModeName.Normal) { - return Position.FromVSCodePosition(vimState.editor.selection.active); - } else { - /** - * cursorMove command is handling the selection for us. - * So we are not following our design principal (do no real movement inside an action) here. - */ - let start = Position.FromVSCodePosition(vimState.editor.selection.start); - let stop = Position.FromVSCodePosition(vimState.editor.selection.end); - let curPos = Position.FromVSCodePosition(vimState.editor.selection.active); - - // We want to swap the cursor start stop positions based on which direction we are moving, up or down - if (start.isEqual(curPos)) { - position = start; - [start, stop] = [stop, start]; - start = start.getLeft(); - } - - return { start, stop }; - } - } - - public async execActionForOperator(position: Position, vimState: VimState): Promise { - await vscode.commands.executeCommand("cursorMove", { - to: this.movementType, - select: true, - by: this.by, - value: this.value - }); - - return { - start: Position.FromVSCodePosition(vimState.editor.selection.start), - stop: Position.FromVSCodePosition(vimState.editor.selection.end) - }; + vimState.recordedState.count === 0; } } @@ -894,7 +1011,7 @@ export class MoveWordBegin extends BaseMovement { } if (result.isLineEnd()) { - return new Position(result.line, result.character + 1); + return new Position(result.line, result.character + 1); } return result; @@ -1710,19 +1827,19 @@ export class ArrowsInInsertMode extends BaseMovement { switch (this.keys[0]) { case "": - newPosition = await new MoveUpArrow().execAction(position, vimState); - break; + newPosition = (await new MoveUpArrow().execAction(position, vimState)); + break; case "": - newPosition = await new MoveDownArrow().execAction(position, vimState); - break; + newPosition = (await new MoveDownArrow().execAction(position, vimState)); + break; case "": - newPosition = await new MoveLeftArrow().execAction(position, vimState); - break; + newPosition = await new MoveLeftArrow().execAction(position, vimState); + break; case "": - newPosition = await new MoveRightArrow().execAction(position, vimState); - break; + newPosition = await new MoveRightArrow().execAction(position, vimState); + break; default: - break; + break; } vimState.replaceState = new ReplaceState(newPosition); return newPosition; diff --git a/src/configuration/configuration.ts b/src/configuration/configuration.ts index 2d9b7803548..5662e862ea3 100644 --- a/src/configuration/configuration.ts +++ b/src/configuration/configuration.ts @@ -288,6 +288,11 @@ class ConfigurationClass { * In visual mode, start a search with * or # using the current selection */ visualstar = false; + + /** + * Uses a hack to fix moving around folds. + */ + foldfix = false; } function overlapSetting(args: { codeName: string, default: OptionValue, codeValueMapping?: ValueMapping }) {