diff --git a/src/vs/editor/test/common/mocks/mockCodeEditor.ts b/src/vs/editor/test/common/mocks/mockCodeEditor.ts index 6ce79012013..403a12f01a5 100644 --- a/src/vs/editor/test/common/mocks/mockCodeEditor.ts +++ b/src/vs/editor/test/common/mocks/mockCodeEditor.ts @@ -61,6 +61,14 @@ export class MockCodeEditor extends CommonCodeEditor { this._contributions[r.getId()] = r; return r; } + + public dispose() { + super.dispose(); + if (this.model) { + this.model.dispose(); + } + this._contextKeyService.dispose(); + } } export class MockScopeLocation implements IContextKeyServiceTarget { @@ -72,6 +80,12 @@ export class MockScopeLocation implements IContextKeyServiceTarget { } export function withMockCodeEditor(text:string[], options:editorCommon.ICodeEditorWidgetCreationOptions, callback:(editor:MockCodeEditor, cursor:Cursor)=>void): void { + let editor = mockCodeEditor(text, options); + callback(editor, editor.getCursor()); + editor.dispose(); +} + +export function mockCodeEditor(text:string[], options:editorCommon.ICodeEditorWidgetCreationOptions): CommonCodeEditor { let contextKeyService = new MockKeybindingService(); @@ -80,17 +94,10 @@ export function withMockCodeEditor(text:string[], options:editorCommon.ICodeEdit let instantiationService = new InstantiationService(services); let editor = new MockCodeEditor(new MockScopeLocation(), options, instantiationService, contextKeyService); - let model: Model; - if (!options.model) { - model = Model.createFromString(text.join('\n')); + let model = options.model || Model.createFromString(text.join('\n')); + if (model) { editor.setModel(model); } - callback(editor, editor.getCursor()); - - editor.dispose(); - if (model) { - model.dispose(); - } - contextKeyService.dispose(); + return editor; } diff --git a/src/vs/workbench/common/editor/rangeDecorations.ts b/src/vs/workbench/common/editor/rangeDecorations.ts index e0c82ca6476..2a3b71dd323 100644 --- a/src/vs/workbench/common/editor/rangeDecorations.ts +++ b/src/vs/workbench/common/editor/rangeDecorations.ts @@ -5,7 +5,6 @@ import {IDisposable} from 'vs/base/common/lifecycle'; import URI from 'vs/base/common/uri'; -import {Range} from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService'; import {IEditor} from 'vs/platform/editor/common/editor'; @@ -13,7 +12,7 @@ import {asFileEditorInput} from 'vs/workbench/common/editor'; export interface IRangeHighlightDecoration { resource: URI; - range: Range; + range: editorCommon.IRange; } export class RangeHighlightDecorations implements IDisposable { diff --git a/src/vs/workbench/test/common/editor/rangeDecorations.test.ts b/src/vs/workbench/test/common/editor/rangeDecorations.test.ts new file mode 100644 index 00000000000..ae2c0c1dbef --- /dev/null +++ b/src/vs/workbench/test/common/editor/rangeDecorations.test.ts @@ -0,0 +1,168 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import * as assert from 'assert'; +import { TestInstantiationService } from 'vs/test/utils/instantiationTestUtils'; +import URI from 'vs/base/common/uri'; +import {createMockModelService, TestEditorService, workbenchInstantiationService} from 'vs/test/utils/servicesTestUtils'; +import {IModelService} from 'vs/editor/common/services/modelService'; +import {IModeService} from 'vs/editor/common/services/modeService'; +import WorkbenchEditorService = require('vs/workbench/services/editor/common/editorService'); +import { RangeHighlightDecorations } from 'vs/workbench/common/editor/rangeDecorations'; +import {Model} from 'vs/editor/common/model/model'; +import {mockCodeEditor, MockCodeEditor} from 'vs/editor/test/common/mocks/mockCodeEditor'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import {IEditorInput} from 'vs/platform/editor/common/editor'; +import {FileEditorInput} from 'vs/workbench/parts/files/common/editors/fileEditorInput'; +import {TextModel} from 'vs/editor/common/model/textModel'; +import {Range} from 'vs/editor/common/core/range'; +import {Position} from 'vs/editor/common/core/position'; +import {Cursor} from 'vs/editor/common/controller/cursor'; + +suite('Editor - Range decorations', () => { + + let instantiationService: TestInstantiationService; + let editorService: WorkbenchEditorService.IWorkbenchEditorService; + let modelService: IModelService; + let modeService: IModeService; + let codeEditor: editorCommon.ICommonCodeEditor; + let cursor: Cursor; + let model: Model; + let text: string; + let testObject: RangeHighlightDecorations; + let modelsToDispose: Model[] = []; + + setup(() => { + instantiationService = workbenchInstantiationService(); + editorService= instantiationService.stub(WorkbenchEditorService.IWorkbenchEditorService, new TestEditorService(function () { })); + modeService= instantiationService.stub(IModeService); + modelService = instantiationService.stub(IModelService, createMockModelService(instantiationService)); + text = 'LINE1' + '\n' + 'LINE2' + '\n' + 'LINE3' + '\n' + 'LINE4' + '\r\n' + 'LINE5'; + model = aModel(URI.file('some_file')); + codeEditor = mockCodeEditor([], { model }); + cursor = (codeEditor).getCursor(); + mockEditorService(codeEditor.getModel().uri); + + instantiationService.stub(WorkbenchEditorService.IWorkbenchEditorService, 'getActiveEditor', { getControl: () => { return codeEditor; } }); + + testObject = instantiationService.createInstance(RangeHighlightDecorations); + }); + + teardown(() => { + codeEditor.dispose(); + modelsToDispose.forEach(model => model.dispose()); + }); + + test('highlight range for the resource if it is an active editor', function () { + let range: editorCommon.IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; + testObject.highlightRange({ resource: model.uri, range }); + + let actuals = rangeHighlightDecorations(model); + + assert.deepEqual([range], actuals); + }); + + test('remove highlight range', function () { + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }}); + testObject.removeHighlightRange(); + + let actuals = rangeHighlightDecorations(model); + + assert.deepEqual([], actuals); + }); + + test('highlight range for the resource removes previous highlight', function () { + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }}); + let range: editorCommon.IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 }; + testObject.highlightRange({ resource: model.uri, range }); + + let actuals = rangeHighlightDecorations(model); + + assert.deepEqual([range], actuals); + }); + + test('highlight range for a new resource removes highlight of previous resource', function () { + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); + + let anotherModel = prepareActiveEditor('anotherModel'); + let range: editorCommon.IRange = { startLineNumber: 2, startColumn: 2, endLineNumber: 4, endColumn: 3 }; + testObject.highlightRange({ resource: anotherModel.uri, range }); + + let actuals = rangeHighlightDecorations(model); + assert.deepEqual([], actuals); + actuals = rangeHighlightDecorations(anotherModel); + assert.deepEqual([range], actuals); + }); + + test('highlight is removed on model change', function () { + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); + prepareActiveEditor('anotherModel'); + + let actuals = rangeHighlightDecorations(model); + assert.deepEqual([], actuals); + }); + + test('highlight is removed on cursor position change', function () { + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); + cursor.trigger('mouse', editorCommon.Handler.MoveTo, { + position: new Position(2, 1) + }); + + let actuals = rangeHighlightDecorations(model); + assert.deepEqual([], actuals); + }); + + test('range is not highlight if not active editor', function () { + let model = aModel(URI.file('some model')); + testObject.highlightRange({ resource: model.uri, range: { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 } }); + + let actuals = rangeHighlightDecorations(model); + assert.deepEqual([], actuals); + }); + + test('previous highlight is not removed if not active editor', function () { + let range: editorCommon.IRange = { startLineNumber: 1, startColumn: 1, endLineNumber: 1, endColumn: 1 }; + testObject.highlightRange({ resource: model.uri, range}); + + let model1 = aModel(URI.file('some model')); + testObject.highlightRange({ resource: model1.uri, range: { startLineNumber: 2, startColumn: 1, endLineNumber: 2, endColumn: 1 } }); + + let actuals = rangeHighlightDecorations(model); + assert.deepEqual([range], actuals); + }); + + function prepareActiveEditor(resource: string): Model { + let model = aModel(URI.file(resource)); + codeEditor.setModel(model); + mockEditorService(model.uri); + return model; + } + + function aModel(resource: URI, content: string = text): Model { + let model = Model.createFromString(content, TextModel.DEFAULT_CREATION_OPTIONS, null, resource); + modelsToDispose.push(model); + return model; + } + + function mockEditorService(editorInput: IEditorInput) + function mockEditorService(resource: URI) + function mockEditorService(arg: any) { + let editorInput: IEditorInput = arg instanceof URI ? instantiationService.createInstance(FileEditorInput, arg, '', '') : arg; + instantiationService.stub(WorkbenchEditorService.IWorkbenchEditorService, 'getActiveEditorInput', editorInput); + } + + function rangeHighlightDecorations(m: Model): editorCommon.IRange[] { + let rangeHighlights: editorCommon.IRange[] = []; + + for (let dec of m.getAllDecorations()) { + if (dec.options.className === 'rangeHighlight') { + rangeHighlights.push(dec.range); + } + } + + rangeHighlights.sort(Range.compareRangesUsingStarts); + return rangeHighlights; + } +}); \ No newline at end of file