monaco-editor-core
Version:
A browser based code editor
166 lines (165 loc) • 9.18 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Position } from '../core/position.js';
import { Range } from '../core/range.js';
import { InlineDecoration, ViewModelDecoration } from '../viewModel.js';
import { filterValidationDecorations } from '../config/editorOptions.js';
export class ViewModelDecorations {
constructor(editorId, model, configuration, linesCollection, coordinatesConverter) {
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;
}
_clearCachedModelDecorationsResolver() {
this._cachedModelDecorationsResolver = null;
this._cachedModelDecorationsResolverViewRange = null;
}
dispose() {
this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
}
reset() {
this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
}
onModelDecorationsChanged() {
this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
}
onLineMappingChanged() {
this._decorationsCache = Object.create(null);
this._clearCachedModelDecorationsResolver();
}
_getOrCreateViewModelDecoration(modelDecoration) {
const id = modelDecoration.id;
let r = this._decorationsCache[id];
if (!r) {
const modelRange = modelDecoration.range;
const options = modelDecoration.options;
let viewRange;
if (options.isWholeLine) {
const start = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.startLineNumber, 1), 0 /* PositionAffinity.Left */, false, true);
const end = this._coordinatesConverter.convertModelPositionToViewPosition(new Position(modelRange.endLineNumber, this.model.getLineMaxColumn(modelRange.endLineNumber)), 1 /* PositionAffinity.Right */);
viewRange = new Range(start.lineNumber, start.column, end.lineNumber, end.column);
}
else {
// For backwards compatibility reasons, we want injected text before any decoration.
// Thus, move decorations to the right.
viewRange = this._coordinatesConverter.convertModelRangeToViewRange(modelRange, 1 /* PositionAffinity.Right */);
}
r = new ViewModelDecoration(viewRange, options);
this._decorationsCache[id] = r;
}
return r;
}
getMinimapDecorationsInRange(range) {
return this._getDecorationsInRange(range, true, false).decorations;
}
getDecorationsViewportData(viewRange) {
let cacheIsValid = (this._cachedModelDecorationsResolver !== null);
cacheIsValid = cacheIsValid && (viewRange.equalsRange(this._cachedModelDecorationsResolverViewRange));
if (!cacheIsValid) {
this._cachedModelDecorationsResolver = this._getDecorationsInRange(viewRange, false, false);
this._cachedModelDecorationsResolverViewRange = viewRange;
}
return this._cachedModelDecorationsResolver;
}
getInlineDecorationsOnLine(lineNumber, onlyMinimapDecorations = false, onlyMarginDecorations = false) {
const range = new Range(lineNumber, this._linesCollection.getViewLineMinColumn(lineNumber), lineNumber, this._linesCollection.getViewLineMaxColumn(lineNumber));
return this._getDecorationsInRange(range, onlyMinimapDecorations, onlyMarginDecorations).inlineDecorations[0];
}
_getDecorationsInRange(viewRange, onlyMinimapDecorations, onlyMarginDecorations) {
const modelDecorations = this._linesCollection.getDecorationsInRange(viewRange, this.editorId, filterValidationDecorations(this.configuration.options), onlyMinimapDecorations, onlyMarginDecorations);
const startLineNumber = viewRange.startLineNumber;
const endLineNumber = viewRange.endLineNumber;
const decorationsInViewport = [];
let decorationsInViewportLen = 0;
const inlineDecorations = [];
for (let j = startLineNumber; j <= endLineNumber; j++) {
inlineDecorations[j - startLineNumber] = [];
}
for (let i = 0, len = modelDecorations.length; i < len; i++) {
const modelDecoration = modelDecorations[i];
const decorationOptions = modelDecoration.options;
if (!isModelDecorationVisible(this.model, modelDecoration)) {
continue;
}
const viewModelDecoration = this._getOrCreateViewModelDecoration(modelDecoration);
const viewRange = viewModelDecoration.range;
decorationsInViewport[decorationsInViewportLen++] = viewModelDecoration;
if (decorationOptions.inlineClassName) {
const inlineDecoration = new InlineDecoration(viewRange, decorationOptions.inlineClassName, decorationOptions.inlineClassNameAffectsLetterSpacing ? 3 /* InlineDecorationType.RegularAffectingLetterSpacing */ : 0 /* InlineDecorationType.Regular */);
const intersectedStartLineNumber = Math.max(startLineNumber, viewRange.startLineNumber);
const 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) {
const inlineDecoration = new InlineDecoration(new Range(viewRange.startLineNumber, viewRange.startColumn, viewRange.startLineNumber, viewRange.startColumn), decorationOptions.beforeContentClassName, 1 /* InlineDecorationType.Before */);
inlineDecorations[viewRange.startLineNumber - startLineNumber].push(inlineDecoration);
}
}
if (decorationOptions.afterContentClassName) {
if (startLineNumber <= viewRange.endLineNumber && viewRange.endLineNumber <= endLineNumber) {
const inlineDecoration = new InlineDecoration(new Range(viewRange.endLineNumber, viewRange.endColumn, viewRange.endLineNumber, viewRange.endColumn), decorationOptions.afterContentClassName, 2 /* InlineDecorationType.After */);
inlineDecorations[viewRange.endLineNumber - startLineNumber].push(inlineDecoration);
}
}
}
return {
decorations: decorationsInViewport,
inlineDecorations: inlineDecorations
};
}
}
export function isModelDecorationVisible(model, decoration) {
if (decoration.options.hideInCommentTokens && isModelDecorationInComment(model, decoration)) {
return false;
}
if (decoration.options.hideInStringTokens && isModelDecorationInString(model, decoration)) {
return false;
}
return true;
}
export function isModelDecorationInComment(model, decoration) {
return testTokensInRange(model, decoration.range, (tokenType) => tokenType === 1 /* StandardTokenType.Comment */);
}
export function isModelDecorationInString(model, decoration) {
return testTokensInRange(model, decoration.range, (tokenType) => tokenType === 2 /* StandardTokenType.String */);
}
/**
* Calls the callback for every token that intersects the range.
* If the callback returns `false`, iteration stops and `false` is returned.
* Otherwise, `true` is returned.
*/
function testTokensInRange(model, range, callback) {
for (let lineNumber = range.startLineNumber; lineNumber <= range.endLineNumber; lineNumber++) {
const lineTokens = model.tokenization.getLineTokens(lineNumber);
const isFirstLine = lineNumber === range.startLineNumber;
const isEndLine = lineNumber === range.endLineNumber;
let tokenIdx = isFirstLine ? lineTokens.findTokenIndexAtOffset(range.startColumn - 1) : 0;
while (tokenIdx < lineTokens.getCount()) {
if (isEndLine) {
const startOffset = lineTokens.getStartOffset(tokenIdx);
if (startOffset > range.endColumn - 1) {
break;
}
}
const callbackResult = callback(lineTokens.getStandardTokenType(tokenIdx));
if (!callbackResult) {
return false;
}
tokenIdx++;
}
}
return true;
}