diff --git a/src/Actions/MoveCursor.ts b/src/Actions/MoveCursor.ts index 16384819..0cd53feb 100644 --- a/src/Actions/MoveCursor.ts +++ b/src/Actions/MoveCursor.ts @@ -2,6 +2,7 @@ import {window, Position, Selection} from 'vscode'; import {ActionReveal} from './Reveal'; import {Motion} from '../Motions/Motion'; import {UtilPosition} from '../Utils/Position'; +import {UtilSelection} from '../Utils/Selection'; export class ActionMoveCursor { @@ -66,34 +67,34 @@ export class ActionMoveCursor { activeTextEditor.selections = activeTextEditor.selections.map((selection, i) => { let anchor: Position; - let active = args.motions.reduce((position, motion) => { - return motion.apply(position, { - isInclusive: args.isVisualMode, - preferedColumn: ActionMoveCursor.preferedColumnBySelectionIndex[i] - }); - }, selection.active); + let active = args.motions.reduce( + (position, motion) => { + return motion.apply(position, { + preferedColumn: ActionMoveCursor.preferedColumnBySelectionIndex[i] + }); + }, + args.isVisualMode + ? UtilSelection.getActiveInVisualMode(selection) + : selection.active + ); if (args.isVisualMode) { anchor = selection.anchor; - if (anchor.isEqual(active)) { - if (active.isBefore(selection.active)) { - anchor = anchor.translate(0, +1); - if (active.character > 0) { - active = active.translate(0, -1); - } - } - else { - if (anchor.character > 0) { - anchor = anchor.translate(0, -1); - } - active = active.translate(0, +1); - } + const anchorLineLength = activeTextEditor.document.lineAt(anchor.line).text.length; + const activeLineLength = activeTextEditor.document.lineAt(active.line).text.length; + + if (active.isAfterOrEqual(anchor) && active.character < activeLineLength) { + active = active.translate(0, +1); + } + + if (active.isEqual(anchor) && anchor.character > 0) { + anchor = anchor.translate(0, -1); } - else if (active.isAfter(anchor) && selection.active.isBefore(selection.anchor)) { + else if (active.isAfter(anchor) && selection.isReversed && anchor.character > 0) { anchor = anchor.translate(0, -1); } - else if (active.isBefore(anchor) && selection.active.isAfter(selection.anchor)) { + else if (active.isBefore(anchor) && !selection.isReversed && anchor.character < anchorLineLength) { anchor = anchor.translate(0, +1); } } diff --git a/src/Motions/MatchPair.ts b/src/Motions/MatchPair.ts index a48a10c4..d463dc84 100644 --- a/src/Motions/MatchPair.ts +++ b/src/Motions/MatchPair.ts @@ -35,7 +35,14 @@ export class MotionMatchPair extends Motion { return new MotionMatchPair(); } - apply(from: Position, option?: any): Position { + apply( + from: Position, + option: { + isInclusive?: boolean, + } = {} + ): Position { + option.isInclusive = option.isInclusive === undefined ? false : option.isInclusive; + from = super.apply(from); const activeTextEditor = window.activeTextEditor; @@ -65,7 +72,7 @@ export class MotionMatchPair extends Motion { else { const endRange = textObject.findEndRange(document, new Position(from.line, character)); if (endRange !== null) { - return endRange.start; + return option.isInclusive ? endRange.end : endRange.start; } } diff --git a/src/Motions/Word.ts b/src/Motions/Word.ts index d0b0c2f8..f1d2d10e 100644 --- a/src/Motions/Word.ts +++ b/src/Motions/Word.ts @@ -2,8 +2,8 @@ import {window, TextDocument, Position} from 'vscode'; import {Motion} from './Motion'; import {WordCharacterKind, UtilWord} from '../Utils/Word'; -enum MotionWordDirection {Previous, Next}; -enum MotionWordMatchKind {Start, End, Both}; +enum MotionWordDirection {Previous, Next} +enum MotionWordMatchKind {Start, End, Both} export class MotionWord extends Motion { diff --git a/src/Utils/Selection.ts b/src/Utils/Selection.ts index d9e6740a..8693777e 100644 --- a/src/Utils/Selection.ts +++ b/src/Utils/Selection.ts @@ -1,4 +1,4 @@ -import {Selection} from 'vscode'; +import {Selection, Position} from 'vscode'; export class UtilSelection { @@ -47,4 +47,24 @@ export class UtilSelection { return selections.map(selection => UtilSelection.shrinkToActive(selection)); } + static getActiveInVisualMode(anchor: Position, active: Position): Position; + static getActiveInVisualMode(selection: Selection): Position; + static getActiveInVisualMode(first: Position | Selection, second?: Position): Position { + let active: Position; + let isReversed: boolean; + + if (second) { + isReversed = (first as Position).isAfter(second); + active = second; + } + else { + isReversed = (first as Selection).isReversed; + active = (first as Selection).active; + } + + return !isReversed && active.character > 0 + ? active.translate(0, -1) + : active; + } + } diff --git a/test/ModeNormal/~.test.ts b/test/ModeNormal/~.test.ts index 048f3c77..14461380 100644 --- a/test/ModeNormal/~.test.ts +++ b/test/ModeNormal/~.test.ts @@ -1,6 +1,6 @@ import * as BlackBox from '../Framework/BlackBox'; -suite.only('Normal: ~', () => { +suite('Normal: ~', () => { const testCases: BlackBox.TestCase[] = [ { from: '[]Foo', diff --git a/test/ModeVisual/h.test.ts b/test/ModeVisual/h.test.ts new file mode 100644 index 00000000..ff8cc279 --- /dev/null +++ b/test/ModeVisual/h.test.ts @@ -0,0 +1,30 @@ +import * as BlackBox from '../Framework/BlackBox'; + +suite('Visual: h', () => { + const testCases: BlackBox.TestCase[] = [ + { + from: '[F]oo bar', + inputs: 'h', + to: '[F]oo bar', + }, + { + from: 'F[o]o bar', + inputs: 'h', + to: '~[Fo]o bar', + }, + { + from: '[Fo]o bar', + inputs: 'h', + to: '[F]oo bar', + }, + { + from: '~[F]oo bar', + inputs: 'h', + to: '~[F]oo bar', + }, + ]; + + for (let i = 0; i < testCases.length; i++) { + BlackBox.run(testCases[i]); + } +}); diff --git a/test/ModeVisual/l.test.ts b/test/ModeVisual/l.test.ts new file mode 100644 index 00000000..e0f19ca0 --- /dev/null +++ b/test/ModeVisual/l.test.ts @@ -0,0 +1,30 @@ +import * as BlackBox from '../Framework/BlackBox'; + +suite('Visual: l', () => { + const testCases: BlackBox.TestCase[] = [ + { + from: 'Foo ba~[r]', + inputs: 'l', + to: 'Foo ba[r]', + }, + { + from: 'Foo b~[a]r', + inputs: 'l', + to: 'Foo b[ar]', + }, + { + from: 'Foo b~[ar]', + inputs: 'l', + to: 'Foo ba~[r]', + }, + { + from: 'Foo ba[r]', + inputs: 'l', + to: 'Foo ba[r]', + }, + ]; + + for (let i = 0; i < testCases.length; i++) { + BlackBox.run(testCases[i]); + } +});