diff --git a/src/vs/editor/common/controller/cursorMoveCommands.ts b/src/vs/editor/common/controller/cursorMoveCommands.ts index 97925d92dcced20b395d570f2a4216d129142961..a0f5c443432081797a81bb6d0bca50c0d4a5a40b 100644 --- a/src/vs/editor/common/controller/cursorMoveCommands.ts +++ b/src/vs/editor/common/controller/cursorMoveCommands.ts @@ -418,7 +418,19 @@ export class CursorMoveCommands { let result: CursorState[] = []; for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; - result[i] = CursorState.fromViewState(MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns)); + + let newViewState = MoveOperations.moveLeft(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); + + if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + // moved over to the previous view line + const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); + if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { + // stayed on the same model line => pass wrapping point where 2 view positions map to a single model position + newViewState = MoveOperations.moveLeft(context.config, context.viewModel, newViewState, inSelectionMode, 1); + } + } + + result[i] = CursorState.fromViewState(newViewState); } return result; } @@ -438,7 +450,18 @@ export class CursorMoveCommands { let result: CursorState[] = []; for (let i = 0, len = cursors.length; i < len; i++) { const cursor = cursors[i]; - result[i] = CursorState.fromViewState(MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns)); + let newViewState = MoveOperations.moveRight(context.config, context.viewModel, cursor.viewState, inSelectionMode, noOfColumns); + + if (noOfColumns === 1 && newViewState.position.lineNumber !== cursor.viewState.position.lineNumber) { + // moved over to the next view line + const newViewModelPosition = context.viewModel.coordinatesConverter.convertViewPositionToModelPosition(newViewState.position); + if (newViewModelPosition.lineNumber === cursor.modelState.position.lineNumber) { + // stayed on the same model line => pass wrapping point where 2 view positions map to a single model position + newViewState = MoveOperations.moveRight(context.config, context.viewModel, newViewState, inSelectionMode, 1); + } + } + + result[i] = CursorState.fromViewState(newViewState); } return result; } diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts index 98b3810dec495357b909695387a6a944cea294b1..63eae2816d334a66174a2e7f9045c1164a77dc00 100644 --- a/src/vs/editor/test/common/controller/cursor.test.ts +++ b/src/vs/editor/test/common/controller/cursor.test.ts @@ -1741,6 +1741,49 @@ suite('Editor Controller - Regression tests', () => { assertCursor(cursor, new Selection(1, 1, 1, 1)); }); }); + + test('issue #36740: wordwrap creates an extra step / character at the wrapping point', () => { + // a single model line => 4 view lines + withMockCodeEditor([ + [ + 'Lorem ipsum ', + 'dolor sit amet ', + 'consectetur ', + 'adipiscing elit', + ].join('') + ], { wordWrap: 'wordWrapColumn', wordWrapColumn: 16 }, (editor, cursor) => { + cursor.setSelections('test', [new Selection(1, 7, 1, 7)]); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 8, 1, 8)); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 9, 1, 9)); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 10, 1, 10)); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 11, 1, 11)); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 12, 1, 12)); + + moveRight(cursor); + assertCursor(cursor, new Selection(1, 13, 1, 13)); + + // moving to view line 2 + moveRight(cursor); + assertCursor(cursor, new Selection(1, 14, 1, 14)); + + moveLeft(cursor); + assertCursor(cursor, new Selection(1, 13, 1, 13)); + + // moving back to view line 1 + moveLeft(cursor); + assertCursor(cursor, new Selection(1, 12, 1, 12)); + }); + }); }); suite('Editor Controller - Cursor Configuration', () => {