/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { IDisposable } from '../../../../vs/base/common/lifecycle'; import { Position } from '../../../../vs/editor/common/core/position'; import { Range } from '../../../../vs/editor/common/core/range'; import * as editorCommon from '../../../../vs/editor/common/editorCommon'; import { IModelDecoration, ITextModel, } from '../../../../vs/editor/common/model'; import { IViewModelLinesCollection } from '../../../../vs/editor/common/viewModel/splitLinesCollection'; import { ICoordinatesConverter, InlineDecoration, InlineDecorationType, ViewModelDecoration, } from '../../../../vs/editor/common/viewModel/viewModel'; import { filterValidationDecorations } from '../../../../vs/editor/common/config/editorOptions'; export interface IDecorationsViewportData { /** * decorations in the viewport. */ readonly decorations: ViewModelDecoration[]; /** * inline decorations grouped by each line in the viewport. */ readonly inlineDecorations: InlineDecoration[][]; } export class ViewModelDecorations implements IDisposable { private readonly editorId: number; private readonly model: ITextModel; private readonly configuration: editorCommon.IConfiguration; private readonly _linesCollection: IViewModelLinesCollection; private readonly _coordinatesConverter: ICoordinatesConverter; private _decorationsCache: { [decorationId: string]: ViewModelDecoration }; private _cachedModelDecorationsResolver: IDecorationsViewportData | null; private _cachedModelDecorationsResolverViewRange: Range | null; constructor( editorId: number, model: ITextModel, configuration: editorCommon.IConfiguration, linesCollection: IViewModelLinesCollection, coordinatesConverter: ICoordinatesConverter ) { this.editorId = editorId; this.model = model; this.configuration = configuration; this._linesCollection = linesCollection; this._coordinatesConverter = coordinatesConverter; this._decorationsCache = Object.create(null); this._cachedModelDecorationsResolver = null; this._cachedModelDecorationsResolverViewRange = null; } private _clearCachedModelDecorationsResolver(): void { this._cachedModelDecorationsResolver = null; this._cachedModelDecorationsResolverViewRange = null; } public dispose(): void { this._decorationsCache = Object.create(null); this._clearCachedModelDecorationsResolver(); } public reset(): void { this._decorationsCache = Object.create(null); this._clearCachedModelDecorationsResolver(); } public onModelDecorationsChanged(): void { this._decorationsCache = Object.create(null); this._clearCachedModelDecorationsResolver(); } public onLineMappingChanged(): void { this._decorationsCache = Object.create(null); this._clearCachedModelDecorationsResolver(); } private _getOrCreateViewModelDecoration( modelDecoration: IModelDecoration ): ViewModelDecoration { const id = modelDecoration.id; let r = this._decorationsCache[id]; if (!r) { const modelRange = modelDecoration.range; const options = modelDecoration.options; let viewRange: Range; if (options.isWholeLine) { const start = this._coordinatesConverter.convertModelPositionToViewPosition( new Position(modelRange.startLineNumber, 1) ); const end = this._coordinatesConverter.convertModelPositionToViewPosition( new Position( modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber) ) ); viewRange = new Range( start.lineNumber, start.column, end.lineNumber, end.column ); } else { viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange); } r = new ViewModelDecoration(viewRange, options); this._decorationsCache[id] = r; } return r; } public getDecorationsViewportData( viewRange: Range ): IDecorationsViewportData { let cacheIsValid = this._cachedModelDecorationsResolver !== null; cacheIsValid = cacheIsValid && viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange); if (!cacheIsValid) { this._cachedModelDecorationsResolver = this._getDecorationsViewportData(viewRange); this._cachedModelDecorationsResolverViewRange = viewRange; } return this._cachedModelDecorationsResolver!; } private _getDecorationsViewportData( viewportRange: Range ): IDecorationsViewportData { const modelDecorations = this._linesCollection.getDecorationsInRange( viewportRange, this.editorId, filterValidationDecorations(this.configuration.options) ); const startLineNumber = viewportRange.startLineNumber; const endLineNumber = viewportRange.endLineNumber; let decorationsInViewport: ViewModelDecoration[] = [], decorationsInViewportLen = 0; let inlineDecorations: InlineDecoration[][] = []; for (let j = startLineNumber; j <= endLineNumber; j++) { inlineDecorations[j - startLineNumber] = []; } for (let i = 0, len = modelDecorations.length; i < len; i++) { let modelDecoration = modelDecorations[i]; let decorationOptions = modelDecoration.options; let viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration); let viewRange = viewModelDecoration.range; decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration; if (decorationOptions.inlineClassName) { let inlineDecoration = new InlineDecoration( viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? InlineDecorationType.RegularAffectingLetterSpacing : InlineDecorationType.Regular ); let intersectedStartLineNumber = Math.max( startLineNumber, viewRange.startLineNumber ); let intersectedEndLineNumber = Math.min( endLineNumber, viewRange.endLineNumber ); for ( let j = intersectedStartLineNumber; j <= intersectedEndLineNumber; j++ ) { inlineDecorations[j - startLineNumber].push(inlineDecoration); } } if (decorationOptions.beforeContentClassName) { if ( startLineNumber <= viewRange.startLineNumber && viewRange.startLineNumber <= endLineNumber ) { let inlineDecoration = new InlineDecoration( new Range( viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn ), decorationOptions.beforeContentClassName, InlineDecorationType.Before ); inlineDecorations[viewRange.startLineNumber - startLineNumber].push( inlineDecoration ); } } if (decorationOptions.afterContentClassName) { if ( startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber ) { let inlineDecoration = new InlineDecoration( new Range( viewRange.endLineNumber, viewRange.endColumn, viewRange.endLineNumber, viewRange.endColumn ), decorationOptions.afterContentClassName, InlineDecorationType.After ); inlineDecorations[viewRange.endLineNumber - startLineNumber].push( inlineDecoration ); } } } return { decorations: decorationsInViewport, inlineDecorations: inlineDecorations, }; } }