diff --git a/src/vs/editor/browser/editorBrowser.ts b/src/vs/editor/browser/editorBrowser.ts index 47455a397e9..26f39533c8b 100644 --- a/src/vs/editor/browser/editorBrowser.ts +++ b/src/vs/editor/browser/editorBrowser.ts @@ -58,6 +58,7 @@ export interface IView extends IDisposable { getCodeEditorHelper(): ICodeEditorHelper; getCenteredRangeInViewport(): Range; + getVisibleRangeInViewport(): Range; change(callback:(changeAccessor:IViewZoneChangeAccessor) => any): boolean; getWhitespaces(): editorCommon.IEditorWhitespace[]; diff --git a/src/vs/editor/browser/view/viewImpl.ts b/src/vs/editor/browser/view/viewImpl.ts index c71b44ac374..1f0d062b1f3 100644 --- a/src/vs/editor/browser/view/viewImpl.ts +++ b/src/vs/editor/browser/view/viewImpl.ts @@ -596,6 +596,14 @@ export class View extends ViewEventHandler implements editorBrowser.IView, IDisp return viewModel.convertViewRangeToModelRange(currentCenteredViewRange); } + public getVisibleRangeInViewport(): Range { + if (this._isDisposed) { + throw new Error('ViewImpl.getVisibleRangeInViewport: View is disposed'); + } + let visibleRange = this.layoutProvider.getLinesViewportData().visibleRange; + return this._context.model.convertViewRangeToModelRange(visibleRange); + } + // public getLineInfoProvider():view.ILineInfoProvider { // return this.viewLines; // } diff --git a/src/vs/editor/browser/widget/codeEditorWidget.ts b/src/vs/editor/browser/widget/codeEditorWidget.ts index 9f65caf5bb3..a3d4b205af7 100644 --- a/src/vs/editor/browser/widget/codeEditorWidget.ts +++ b/src/vs/editor/browser/widget/codeEditorWidget.ts @@ -159,6 +159,13 @@ export class CodeEditorWidget extends CommonCodeEditor implements editorBrowser. return this._view.getCenteredRangeInViewport(); } + public getVisibleRangeInViewport(): Range { + if (!this.hasView) { + return null; + } + return this._view.getVisibleRangeInViewport(); + } + public getScrollWidth(): number { if (!this.hasView) { return -1; diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 0a1001c35f2..b435da7fe80 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -287,6 +287,8 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public abstract getCenteredRangeInViewport(): Range; + public abstract getVisibleRangeInViewport(): Range; + public getVisibleColumnFromPosition(rawPosition:editorCommon.IPosition): number { if (!this.model) { return rawPosition.column; @@ -788,6 +790,12 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom var viewModelHelper:IViewModelHelper = { viewModel: this.viewModel, + getCurrentVisibleRange: () => { + return this.viewModel.convertModelRangeToViewRange(this.getVisibleRangeInViewport()); + }, + getCurrentCenteredRange: () => { + return this.viewModel.convertModelRangeToViewRange(this.getCenteredRangeInViewport()); + }, convertModelPositionToViewPosition: (lineNumber:number, column:number) => { return this.viewModel.convertModelPositionToViewPosition(lineNumber, column); }, diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index f129dd5872c..e1160e591b2 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -102,31 +102,6 @@ export class Cursor extends EventEmitter { this.model = model; this.viewModelHelper = viewModelHelper; this.enableEmptySelectionClipboard = enableEmptySelectionClipboard; - if (!this.viewModelHelper) { - this.viewModelHelper = { - viewModel: this.model, - convertModelPositionToViewPosition: (lineNumber:number, column:number) => { - return new Position(lineNumber, column); - }, - convertModelRangeToViewRange: (modelRange: Range) => { - return modelRange; - }, - convertViewToModelPosition: (lineNumber:number, column:number) => { - return new Position(lineNumber, column); - }, - convertViewSelectionToModelSelection: (viewSelection:Selection) => { - return viewSelection; - }, - validateViewPosition: (viewLineNumber:number, viewColumn:number, modelPosition:Position) => { - return modelPosition; - }, - validateViewRange: (viewStartLineNumber:number, viewStartColumn:number, viewEndLineNumber:number, viewEndColumn:number, modelRange:Range) => { - return modelRange; - } - - }; - } - this.cursors = new CursorCollection(this.editorId, this.model, this.configuration, this.viewModelHelper); this.cursorUndoStack = []; diff --git a/src/vs/editor/common/controller/oneCursor.ts b/src/vs/editor/common/controller/oneCursor.ts index 824d3aba719..0c1b117bbb1 100644 --- a/src/vs/editor/common/controller/oneCursor.ts +++ b/src/vs/editor/common/controller/oneCursor.ts @@ -57,6 +57,9 @@ export interface IViewModelHelper { viewModel:ICursorMoveHelperModel; + getCurrentVisibleRange(): Range; + getCurrentCenteredRange(): Range; + convertModelPositionToViewPosition(lineNumber:number, column:number): Position; convertModelRangeToViewRange(modelRange:Range): Range; @@ -547,6 +550,20 @@ export class OneCursor { public getViewLineCount(): number { return this.viewModelHelper.viewModel.getLineCount(); } + public getViewTopLine(lineFromTop: number): number { + let visibleRange = this.viewModelHelper.getCurrentVisibleRange(); + let lineNumber = visibleRange.startLineNumber + lineFromTop - 1; + return lineNumber > visibleRange.endLineNumber ? visibleRange.endLineNumber : lineNumber; + } + public getViewCenterLine(): number { + let centeredRange = this.viewModelHelper.getCurrentCenteredRange(); + return centeredRange.startLineNumber; + } + public getViewBottomLine(lineFromBottom: number): number { + let visibleRange = this.viewModelHelper.getCurrentVisibleRange(); + let lineNumber = visibleRange.endLineNumber - lineFromBottom + 1; + return lineNumber > visibleRange.startLineNumber ? lineNumber : visibleRange.startLineNumber; + } public getViewLineMaxColumn(lineNumber:number): number { return this.viewModelHelper.viewModel.getLineMaxColumn(lineNumber); } @@ -671,6 +688,18 @@ export class OneCursorOp { return this.moveUp(cursor, inSelectionMode, noOfLines, ctx); case editorCommon.CursorMoveViewPosition.LineDown: return this.moveDown(cursor, inSelectionMode, noOfLines, ctx); + case editorCommon.CursorMoveViewPosition.LineViewTop: + viewLineNumber= cursor.getViewTopLine(moveParams.noOfLines || 1); + viewColumn = cursor.getViewLineFirstNonWhiteSpaceColumn(viewLineNumber); + break; + case editorCommon.CursorMoveViewPosition.LineViewBottom: + viewLineNumber= cursor.getViewBottomLine(moveParams.noOfLines || 1); + viewColumn = cursor.getViewLineFirstNonWhiteSpaceColumn(viewLineNumber); + break; + case editorCommon.CursorMoveViewPosition.LineViewCenter: + viewLineNumber= cursor.getViewCenterLine(); + viewColumn = cursor.getViewLineFirstNonWhiteSpaceColumn(viewLineNumber); + break; default: return false; } diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 9495bcd1815..56a89dc8554 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -4192,7 +4192,10 @@ export const CursorMoveViewPosition = { LineEnd: 'lineEnd', LineLastNonWhitespaceCharacter: 'lineLastNonWhitespaceCharacter', LineUp: 'lineUp', - LineDown: 'lineDown' + LineDown: 'lineDown', + LineViewTop: 'lineViewTop', + LineViewCenter: 'lineViewCenter', + LineViewBottom: 'lineViewBottom' }; /** diff --git a/src/vs/editor/test/common/commands/commandTestUtils.ts b/src/vs/editor/test/common/commands/commandTestUtils.ts index dc2cda6ff0f..44a632e0f0a 100644 --- a/src/vs/editor/test/common/commands/commandTestUtils.ts +++ b/src/vs/editor/test/common/commands/commandTestUtils.ts @@ -12,6 +12,7 @@ import * as editorCommon from 'vs/editor/common/editorCommon'; import {Model} from 'vs/editor/common/model/model'; import {IMode} from 'vs/editor/common/modes'; import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; +import {viewModelHelper} from 'vs/editor/test/common/editorTestUtils'; export function testCommand( lines: string[], @@ -24,7 +25,7 @@ export function testCommand( let model = Model.createFromString(lines.join('\n'), undefined, mode); let config = new MockConfiguration(null); - let cursor = new Cursor(0, config, model, null, false); + let cursor = new Cursor(0, config, model, viewModelHelper(model), false); cursor.setSelections('tests', [selection]); diff --git a/src/vs/editor/test/common/commands/sideEditing.test.ts b/src/vs/editor/test/common/commands/sideEditing.test.ts index 35de8f3ddd3..d918da1ac52 100644 --- a/src/vs/editor/test/common/commands/sideEditing.test.ts +++ b/src/vs/editor/test/common/commands/sideEditing.test.ts @@ -14,13 +14,14 @@ import {IIdentifiedSingleEditOperation} from 'vs/editor/common/editorCommon'; import {Model} from 'vs/editor/common/model/model'; import {ILineEdit, ModelLine} from 'vs/editor/common/model/modelLine'; import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; +import {viewModelHelper} from 'vs/editor/test/common/editorTestUtils'; const NO_TAB_SIZE = 0; function testCommand(lines:string[], selection:Selection, edits:IIdentifiedSingleEditOperation[], expectedLines:string[], expectedSelection:Selection): void { let model = Model.createFromString(lines.join('\n')); let config = new MockConfiguration(null); - let cursor = new Cursor(0, config, model, null, false); + let cursor = new Cursor(0, config, model, viewModelHelper(model), false); cursor.setSelections('tests', [selection]); diff --git a/src/vs/editor/test/common/controller/cursor.test.ts b/src/vs/editor/test/common/controller/cursor.test.ts index bfcbbf055df..96c62744c36 100644 --- a/src/vs/editor/test/common/controller/cursor.test.ts +++ b/src/vs/editor/test/common/controller/cursor.test.ts @@ -13,7 +13,7 @@ import {Selection} from 'vs/editor/common/core/selection'; import { EndOfLinePreference, EventType, Handler, IPosition, ISelection, IEditorOptions, DefaultEndOfLine, ITextModelCreationOptions, ICommand, - ITokenizedModel, IEditOperationBuilder, ICursorStateComputerData, CursorMoveViewPosition + ITokenizedModel, IEditOperationBuilder, ICursorStateComputerData } from 'vs/editor/common/editorCommon'; import {Model} from 'vs/editor/common/model/model'; import {IMode, IndentAction} from 'vs/editor/common/modes'; @@ -21,6 +21,7 @@ import {LanguageConfigurationRegistry} from 'vs/editor/common/modes/languageConf import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; import {BracketMode} from 'vs/editor/test/common/testModes'; import {MockMode} from 'vs/editor/test/common/mocks/mockMode'; +import {viewModelHelper} from 'vs/editor/test/common/editorTestUtils'; let H = Handler; @@ -30,40 +31,6 @@ function cursorCommand(cursor: Cursor, command: string, extraData?: any, overwri cursor.trigger(overwriteSource || 'tests', command, extraData); } -// Move command - -function move(cursor: Cursor, args: any) { - cursorCommand(cursor, H.CursorMove, args); -} - -function moveToLineStart(cursor: Cursor) { - move(cursor, {to: CursorMoveViewPosition.LineStart}); -} - -function moveToLineFirstNonWhiteSpaceCharacter(cursor: Cursor) { - move(cursor, {to: CursorMoveViewPosition.LineFirstNonWhitespaceCharacter}); -} - -function moveToLineCenter(cursor: Cursor) { - move(cursor, {to: CursorMoveViewPosition.LineColumnCenter}); -} - -function moveToLineEnd(cursor: Cursor) { - move(cursor, {to: CursorMoveViewPosition.LineEnd}); -} - -function moveToLineLastNonWhiteSpaceCharacter(cursor: Cursor) { - move(cursor, {to: CursorMoveViewPosition.LineLastNonWhitespaceCharacter}); -} - -function moveUpByCursorMoveCommand(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { - move(cursor, {to: CursorMoveViewPosition.LineUp, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); -} - -function moveDownByCursorMoveCommand(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { - move(cursor, {to: CursorMoveViewPosition.LineDown, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); -} - function moveTo(cursor: Cursor, lineNumber: number, column: number, inSelectionMode: boolean = false) { cursorCommand(cursor, inSelectionMode ? H.MoveToSelect : H.MoveTo, { position: new Position(lineNumber, column) }); } @@ -205,7 +172,7 @@ suite('Editor Controller - Cursor', () => { thisModel = Model.createFromString(text); thisConfiguration = new MockConfiguration(null); - thisCursor = new Cursor(1, thisConfiguration, thisModel, null, false); + thisCursor = new Cursor(1, thisConfiguration, thisModel, viewModelHelper(thisModel), false); }); teardown(() => { @@ -225,168 +192,6 @@ suite('Editor Controller - Cursor', () => { cursorEqual(thisCursor, 1, 1); }); - // --------- cursor move command - - test('move to first character of line from middle', () => { - moveTo(thisCursor, 1, 8); - moveToLineStart(thisCursor); - cursorEqual(thisCursor, 1, 1); - }); - - test('move to first character of line from first non white space character', () => { - moveTo(thisCursor, 1, 6); - moveToLineStart(thisCursor); - cursorEqual(thisCursor, 1, 1); - }); - - test('move to first character of line from first character', () => { - moveTo(thisCursor, 1, 1); - moveToLineStart(thisCursor); - cursorEqual(thisCursor, 1, 1); - }); - - test('move to first non white space character of line from middle', () => { - moveTo(thisCursor, 1, 8); - moveToLineFirstNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, 6); - }); - - test('move to first non white space character of line from first non white space character', () => { - moveTo(thisCursor, 1, 6); - moveToLineFirstNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, 6); - }); - - test('move to first non white space character of line from first character', () => { - moveTo(thisCursor, 1, 1); - moveToLineFirstNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, 6); - }); - - test('move to end of line from middle', () => { - moveTo(thisCursor, 1, 8); - moveToLineEnd(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); - }); - - test('move to end of line from last non white space character', () => { - moveTo(thisCursor, 1, LINE1.length - 1); - moveToLineEnd(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); - }); - - test('move to end of line from line end', () => { - moveTo(thisCursor, 1, LINE1.length + 1); - moveToLineEnd(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); - }); - - test('move to last non white space character from middle', () => { - moveTo(thisCursor, 1, 8); - moveToLineLastNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); - }); - - test('move to last non white space character from last non white space character', () => { - moveTo(thisCursor, 1, LINE1.length - 1); - moveToLineLastNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); - }); - - test('move to last non white space character from line end', () => { - moveTo(thisCursor, 1, LINE1.length + 1); - moveToLineLastNonWhiteSpaceCharacter(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); - }); - - test('move to center of line not from center', () => { - moveTo(thisCursor, 1, 8); - moveToLineCenter(thisCursor); - cursorEqual(thisCursor, 1, 11); - }); - - test('move to center of line from center', () => { - moveTo(thisCursor, 1, 11); - moveToLineCenter(thisCursor); - cursorEqual(thisCursor, 1, 11); - }); - - test('move to center of line from start', () => { - moveToLineStart(thisCursor); - moveToLineCenter(thisCursor); - cursorEqual(thisCursor, 1, 11); - }); - - test('move to center of line from end', () => { - moveToLineEnd(thisCursor); - moveToLineCenter(thisCursor); - cursorEqual(thisCursor, 1, 11); - }); - - test('move up by cursor move command', () => { - moveTo(thisCursor, 3, 5); - cursorEqual(thisCursor, 3, 5); - - moveUpByCursorMoveCommand(thisCursor, 2); - cursorEqual(thisCursor, 1, 5); - - moveUpByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 1, 1); - }); - - test('move up with selection by cursor move command', () => { - moveTo(thisCursor, 3, 5); - cursorEqual(thisCursor, 3, 5); - - moveUpByCursorMoveCommand(thisCursor, 1, true); - cursorEqual(thisCursor, 2, 2, 3, 5); - - moveUp(thisCursor, 1, true); - cursorEqual(thisCursor, 1, 5, 3, 5); - }); - - test('move up and down with tabs by cursor move command', () => { - moveTo(thisCursor, 1, 5); - cursorEqual(thisCursor, 1, 5); - - moveDownByCursorMoveCommand(thisCursor, 4); - cursorEqual(thisCursor, 5, 2); - - moveUpByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 4, 1); - - moveUpByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 3, 5); - - moveUpByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 2, 2); - - moveUpByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 1, 5); - }); - - test('move up and down with end of lines starting from a long one by cursor move command', () => { - moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length - 1); - - moveToEndOfLine(thisCursor); - cursorEqual(thisCursor, 1, LINE1.length + 1); - - moveDownByCursorMoveCommand(thisCursor, 2); - cursorEqual(thisCursor, 3, LINE3.length + 1); - - moveDownByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 4, LINE4.length + 1); - - moveDownByCursorMoveCommand(thisCursor, 1); - cursorEqual(thisCursor, 5, LINE5.length + 1); - - moveUpByCursorMoveCommand(thisCursor, 4); - cursorEqual(thisCursor, 1, LINE1.length + 1); - }); - - // --- end of cursor move command tests - test('move', () => { moveTo(thisCursor, 1, 2); cursorEqual(thisCursor, 1, 2); @@ -992,7 +797,7 @@ suite('Editor Controller - Cursor', () => { '\t\t}', '\t}' ].join('\n')); - let cursor = new Cursor(1, new MockConfiguration(null), model, null, true); + let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 7, false); cursorEqual(cursor, 1, 7); @@ -1026,7 +831,7 @@ suite('Editor Controller - Cursor', () => { 'var concat = require("gulp-concat");', 'var newer = require("gulp-newer");', ].join('\n')); - let cursor = new Cursor(1, new MockConfiguration(null), model, null, true); + let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 4, false); cursorEqual(cursor, 1, 4); @@ -1058,7 +863,7 @@ suite('Editor Controller - Cursor', () => { 'var concat = require("gulp-concat");', 'var newer = require("gulp-newer");', ].join('\n')); - let cursor = new Cursor(1, new MockConfiguration(null), model, null, true); + let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 1, 4, false); cursorEqual(cursor, 1, 4); @@ -1595,7 +1400,7 @@ suite('Editor Controller - Regression tests', () => { 'qwerty' ]; let model = Model.createFromString(text.join('\n')); - let cursor = new Cursor(1, new MockConfiguration(null), model, null, true); + let cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 2, 1, false); cursorEqual(cursor, 2, 1, 2, 1); @@ -1613,7 +1418,7 @@ suite('Editor Controller - Regression tests', () => { '' ]; model = Model.createFromString(text.join('\n')); - cursor = new Cursor(1, new MockConfiguration(null), model, null, true); + cursor = new Cursor(1, new MockConfiguration(null), model, viewModelHelper(model), true); moveTo(cursor, 2, 1, false); cursorEqual(cursor, 2, 1, 2, 1); @@ -2798,7 +2603,7 @@ interface ICursorOpts { function usingCursor(opts:ICursorOpts, callback:(model:Model, cursor:Cursor)=>void): void { let model = Model.createFromString(opts.text.join('\n'), opts.modelOpts, opts.mode); let config = new MockConfiguration(opts.editorOpts); - let cursor = new Cursor(1, config, model, null, false); + let cursor = new Cursor(1, config, model, viewModelHelper(model), false); callback(model, cursor); diff --git a/src/vs/editor/test/common/controller/cursorMoveCommand.test.ts b/src/vs/editor/test/common/controller/cursorMoveCommand.test.ts new file mode 100644 index 00000000000..b10aa16b17d --- /dev/null +++ b/src/vs/editor/test/common/controller/cursorMoveCommand.test.ts @@ -0,0 +1,462 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +'use strict'; + +import * as assert from 'assert'; +import {Cursor} from 'vs/editor/common/controller/cursor'; +import {Position} from 'vs/editor/common/core/position'; +import {Handler, IEditorOptions, ITextModelCreationOptions, CursorMoveViewPosition, ISelection, IPosition} from 'vs/editor/common/editorCommon'; +import {Model} from 'vs/editor/common/model/model'; +import {IMode} from 'vs/editor/common/modes'; +import {MockConfiguration} from 'vs/editor/test/common/mocks/mockConfiguration'; +import {viewModelHelper as aViewModelHelper} from 'vs/editor/test/common/editorTestUtils'; +import {IViewModelHelper} from 'vs/editor/common/controller/oneCursor'; +import {Range} from 'vs/editor/common/core/range'; + +let H = Handler; + +suite('Cursor move command test', () => { + const LINE1 = ' \tMy First Line\t '; + const LINE2 = '\tMy Second Line'; + const LINE3 = ' Third Line💩'; + const LINE4 = ''; + const LINE5 = '1'; + + let thisModel: Model; + let thisConfiguration: MockConfiguration; + let thisCursor: Cursor; + + setup(() => { + let text = + LINE1 + '\r\n' + + LINE2 + '\n' + + LINE3 + '\n' + + LINE4 + '\r\n' + + LINE5; + + thisModel = Model.createFromString(text); + thisConfiguration = new MockConfiguration(null); + }); + + teardown(() => { + thisCursor.dispose(); + thisModel.dispose(); + thisConfiguration.dispose(); + }); + + test('move to first character of line from middle', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 8); + moveToLineStart(thisCursor); + cursorEqual(thisCursor, 1, 1); + }); + + test('move to first character of line from first non white space character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 6); + + moveToLineStart(thisCursor); + + cursorEqual(thisCursor, 1, 1); + }); + + test('move to first character of line from first character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 1); + + moveToLineStart(thisCursor); + + cursorEqual(thisCursor, 1, 1); + }); + + test('move to first non white space character of line from middle', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 8); + + moveToLineFirstNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, 6); + }); + + test('move to first non white space character of line from first non white space character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 6); + + moveToLineFirstNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, 6); + }); + + test('move to first non white space character of line from first character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 1); + + moveToLineFirstNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, 6); + }); + + test('move to end of line from middle', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 8); + + moveToLineEnd(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length + 1); + }); + + test('move to end of line from last non white space character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, LINE1.length - 1); + + moveToLineEnd(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length + 1); + }); + + test('move to end of line from line end', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, LINE1.length + 1); + + moveToLineEnd(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length + 1); + }); + + test('move to last non white space character from middle', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 8); + + moveToLineLastNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length - 1); + }); + + test('move to last non white space character from last non white space character', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, LINE1.length - 1); + + moveToLineLastNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length - 1); + }); + + test('move to last non white space character from line end', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, LINE1.length + 1); + + moveToLineLastNonWhiteSpaceCharacter(thisCursor); + + cursorEqual(thisCursor, 1, LINE1.length - 1); + }); + + test('move to center of line not from center', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 8); + + moveToLineCenter(thisCursor); + + cursorEqual(thisCursor, 1, 11); + }); + + test('move to center of line from center', () => { + thisCursor= aCursor(); + moveTo(thisCursor, 1, 11); + + moveToLineCenter(thisCursor); + + cursorEqual(thisCursor, 1, 11); + }); + + test('move to center of line from start', () => { + thisCursor= aCursor(); + moveToLineStart(thisCursor); + + moveToLineCenter(thisCursor); + + cursorEqual(thisCursor, 1, 11); + }); + + test('move to center of line from end', () => { + thisCursor= aCursor(); + moveToLineEnd(thisCursor); + + moveToLineCenter(thisCursor); + + cursorEqual(thisCursor, 1, 11); + }); + + test('move up by cursor move command', () => { + thisCursor= aCursor(); + + moveTo(thisCursor, 3, 5); + cursorEqual(thisCursor, 3, 5); + + moveUpByCursorMoveCommand(thisCursor, 2); + cursorEqual(thisCursor, 1, 5); + + moveUpByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 1, 1); + }); + + test('move up with selection by cursor move command', () => { + thisCursor= aCursor(); + + moveTo(thisCursor, 3, 5); + cursorEqual(thisCursor, 3, 5); + + moveUpByCursorMoveCommand(thisCursor, 1, true); + cursorEqual(thisCursor, 2, 2, 3, 5); + + moveUpByCursorMoveCommand(thisCursor, 1, true); + cursorEqual(thisCursor, 1, 5, 3, 5); + }); + + test('move up and down with tabs by cursor move command', () => { + thisCursor= aCursor(); + + moveTo(thisCursor, 1, 5); + cursorEqual(thisCursor, 1, 5); + + moveDownByCursorMoveCommand(thisCursor, 4); + cursorEqual(thisCursor, 5, 2); + + moveUpByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 4, 1); + + moveUpByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 3, 5); + + moveUpByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 2, 2); + + moveUpByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 1, 5); + }); + + test('move up and down with end of lines starting from a long one by cursor move command', () => { + thisCursor= aCursor(); + + moveToEndOfLine(thisCursor); + cursorEqual(thisCursor, 1, LINE1.length - 1); + + moveToEndOfLine(thisCursor); + cursorEqual(thisCursor, 1, LINE1.length + 1); + + moveDownByCursorMoveCommand(thisCursor, 2); + cursorEqual(thisCursor, 3, LINE3.length + 1); + + moveDownByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 4, LINE4.length + 1); + + moveDownByCursorMoveCommand(thisCursor, 1); + cursorEqual(thisCursor, 5, LINE5.length + 1); + + moveUpByCursorMoveCommand(thisCursor, 4); + cursorEqual(thisCursor, 1, LINE1.length + 1); + }); + + test('move to view top line moves to first visible line if it is first line', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(1, 1, 10, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 2, 2); + moveToTop(thisCursor); + + cursorEqual(thisCursor, 1, 6); + }); + + test('move to view top line moves to top visible line when first line is not visible', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(2, 1, 10, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 4, 1); + moveToTop(thisCursor); + + cursorEqual(thisCursor, 2, 2); + }); + + test('move to view top line moves to nth line from top', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(1, 1, 10, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 4, 1); + moveToTop(thisCursor, 3); + + cursorEqual(thisCursor, 3, 5); + }); + + test('move to view top line moves to last line if n is greater than last visible line number', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(1, 1, 3, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 2, 2); + moveToTop(thisCursor, 4); + + cursorEqual(thisCursor, 3, 5); + }); + + test('move to view center line moves to the center line', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentCenteredRange= () => new Range(3, 1, 3, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 2, 2); + moveToCenter(thisCursor); + + cursorEqual(thisCursor, 3, 5); + }); + + test('move to view bottom line moves to last visible line if it is last line', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(1, 1, 5, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 2, 2); + moveToBottom(thisCursor); + + cursorEqual(thisCursor, 5, 1); + }); + + test('move to view bottom line moves to last visible line when last line is not visible', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(2, 1, 3, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 2, 2); + moveToBottom(thisCursor); + + cursorEqual(thisCursor, 3, 5); + }); + + test('move to view bottom line moves to nth line from bottom', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(1, 1, 5, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 4, 1); + moveToBottom(thisCursor, 3); + + cursorEqual(thisCursor, 3, 5); + }); + + test('move to view bottom line moves to first line if n is lesser than first visible line number', () => { + let viewModelHelper= aViewModelHelper(thisModel); + viewModelHelper.getCurrentVisibleRange= () => new Range(2, 1, 5, 1); + thisCursor= aCursor(viewModelHelper); + + moveTo(thisCursor, 4, 1); + moveToBottom(thisCursor, 5); + + cursorEqual(thisCursor, 2, 2); + }); + + function aCursor(viewModelHelper?: IViewModelHelper): Cursor { + return new Cursor(1, thisConfiguration, thisModel, viewModelHelper || aViewModelHelper(thisModel), false); + } + +}); + +interface ICursorOpts { + text: string[]; + mode?: IMode; + modelOpts?: ITextModelCreationOptions; + editorOpts?: IEditorOptions; +} + +// --------- utils + +function cursorCommand(cursor: Cursor, command: string, extraData?: any, overwriteSource?: string) { + cursor.trigger(overwriteSource || 'tests', command, extraData); +} + + + +// Move command + +function move(cursor: Cursor, args: any) { + cursorCommand(cursor, H.CursorMove, args); +} + +function moveToLineStart(cursor: Cursor) { + move(cursor, {to: CursorMoveViewPosition.LineStart}); +} + +function moveToLineFirstNonWhiteSpaceCharacter(cursor: Cursor) { + move(cursor, {to: CursorMoveViewPosition.LineFirstNonWhitespaceCharacter}); +} + +function moveToLineCenter(cursor: Cursor) { + move(cursor, {to: CursorMoveViewPosition.LineColumnCenter}); +} + +function moveToLineEnd(cursor: Cursor) { + move(cursor, {to: CursorMoveViewPosition.LineEnd}); +} + +function moveToLineLastNonWhiteSpaceCharacter(cursor: Cursor) { + move(cursor, {to: CursorMoveViewPosition.LineLastNonWhitespaceCharacter}); +} + +function moveUpByCursorMoveCommand(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { + move(cursor, {to: CursorMoveViewPosition.LineUp, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); +} + +function moveDownByCursorMoveCommand(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { + move(cursor, {to: CursorMoveViewPosition.LineDown, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); +} + +function moveToTop(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { + move(cursor, {to: CursorMoveViewPosition.LineViewTop, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); +} + +function moveToCenter(cursor: Cursor, inSelectionMode?: boolean) { + move(cursor, {to: CursorMoveViewPosition.LineViewCenter, inSelectionMode: inSelectionMode}); +} + +function moveToBottom(cursor: Cursor, noOfLines: number= 1, inSelectionMode?: boolean) { + move(cursor, {to: CursorMoveViewPosition.LineViewBottom, noOfLines: noOfLines, inSelectionMode: inSelectionMode}); +} + +function cursorEqual(cursor: Cursor, posLineNumber: number, posColumn: number, selLineNumber: number = posLineNumber, selColumn: number = posColumn) { + positionEqual(cursor.getPosition(), posLineNumber, posColumn); + selectionEqual(cursor.getSelection(), posLineNumber, posColumn, selLineNumber, selColumn); +} + +function positionEqual(position:IPosition, lineNumber: number, column: number) { + assert.deepEqual({ + lineNumber: position.lineNumber, + column: position.column + }, { + lineNumber: lineNumber, + column: column + }, 'position equal'); +} + +function selectionEqual(selection:ISelection, posLineNumber: number, posColumn: number, selLineNumber: number, selColumn: number) { + assert.deepEqual({ + selectionStartLineNumber: selection.selectionStartLineNumber, + selectionStartColumn: selection.selectionStartColumn, + positionLineNumber: selection.positionLineNumber, + positionColumn: selection.positionColumn + }, { + selectionStartLineNumber: selLineNumber, + selectionStartColumn: selColumn, + positionLineNumber: posLineNumber, + positionColumn: posColumn + }, 'selection equal'); +} + +function moveTo(cursor: Cursor, lineNumber: number, column: number, inSelectionMode: boolean = false) { + cursorCommand(cursor, inSelectionMode ? H.MoveToSelect : H.MoveTo, { position: new Position(lineNumber, column) }); +} + +function moveToEndOfLine(cursor: Cursor, inSelectionMode: boolean = false) { + cursorCommand(cursor, inSelectionMode ? H.CursorEndSelect : H.CursorEnd); +} \ No newline at end of file diff --git a/src/vs/editor/test/common/editorTestUtils.ts b/src/vs/editor/test/common/editorTestUtils.ts index 0bc6d8cbc06..27f8a9fed06 100644 --- a/src/vs/editor/test/common/editorTestUtils.ts +++ b/src/vs/editor/test/common/editorTestUtils.ts @@ -5,9 +5,39 @@ 'use strict'; import {Model} from 'vs/editor/common/model/model'; +import {IViewModelHelper} from 'vs/editor/common/controller/oneCursor'; +import {Position} from 'vs/editor/common/core/position'; +import {Range} from 'vs/editor/common/core/range'; +import {Selection} from 'vs/editor/common/core/selection'; export function withEditorModel(text:string[], callback:(model:Model) => void): void { var model = Model.createFromString(text.join('\n')); callback(model); model.dispose(); } + +export function viewModelHelper(model): IViewModelHelper{ + return { + viewModel: model, + getCurrentVisibleRange: () => { return null; }, + getCurrentCenteredRange: () => { return null; }, + convertModelPositionToViewPosition: (lineNumber: number, column: number) => { + return new Position(lineNumber, column); + }, + convertModelRangeToViewRange: (modelRange: Range) => { + return modelRange; + }, + convertViewToModelPosition: (lineNumber: number, column: number) => { + return new Position(lineNumber, column); + }, + convertViewSelectionToModelSelection: (viewSelection: Selection) => { + return viewSelection; + }, + validateViewPosition: (viewLineNumber: number, viewColumn: number, modelPosition: Position) => { + return modelPosition; + }, + validateViewRange: (viewStartLineNumber: number, viewStartColumn: number, viewEndLineNumber: number, viewEndColumn: number, modelRange: Range) => { + return modelRange; + } + }; +} diff --git a/src/vs/editor/test/common/mocks/mockCodeEditor.ts b/src/vs/editor/test/common/mocks/mockCodeEditor.ts index 919f47f7972..5eb3647e6d5 100644 --- a/src/vs/editor/test/common/mocks/mockCodeEditor.ts +++ b/src/vs/editor/test/common/mocks/mockCodeEditor.ts @@ -26,6 +26,7 @@ export class MockCodeEditor extends CommonCodeEditor { return new MockConfiguration(options); } public getCenteredRangeInViewport(): Range { return null; } + public getVisibleRangeInViewport(): Range { return null; } public getScrollWidth(): number { return 0; } public getScrollLeft(): number { return 0; } diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts index a36292b35ae..dfec7132b6c 100644 --- a/src/vs/monaco.d.ts +++ b/src/vs/monaco.d.ts @@ -3205,6 +3205,9 @@ declare module monaco.editor { LineLastNonWhitespaceCharacter: string; LineUp: string; LineDown: string; + LineViewTop: string; + LineViewCenter: string; + LineViewBottom: string; }; /**