UNPKG

monaco-editor

Version:
474 lines (473 loc) • 23.6 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); } return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); import * as browser from '../../../../base/browser/browser.js'; import { createFastDomNode } from '../../../../base/browser/fastDomNode.js'; import * as platform from '../../../../base/common/platform.js'; import { RangeUtil } from './rangeUtil.js'; import { HorizontalRange } from '../../../common/view/renderingContext.js'; import { LineDecoration } from '../../../common/viewLayout/lineDecorations.js'; import { CharacterMapping, RenderLineInput, renderViewLine } from '../../../common/viewLayout/viewLineRenderer.js'; import { HIGH_CONTRAST } from '../../../../platform/theme/common/themeService.js'; var canUseFastRenderedViewLine = (function () { if (platform.isNative) { // In VSCode we know very well when the zoom level changes return true; } if (platform.isLinux || browser.isFirefox || browser.isSafari) { // On Linux, it appears that zooming affects char widths (in pixels), which is unexpected. // -- // Even though we read character widths correctly, having read them at a specific zoom level // does not mean they are the same at the current zoom level. // -- // This could be improved if we ever figure out how to get an event when browsers zoom, // but until then we have to stick with reading client rects. // -- // The same has been observed with Firefox on Windows7 // -- // The same has been oversved with Safari return false; } return true; })(); var alwaysRenderInlineSelection = (browser.isEdgeOrIE); var DomReadingContext = /** @class */ (function () { function DomReadingContext(domNode, endNode) { this._domNode = domNode; this._clientRectDeltaLeft = 0; this._clientRectDeltaLeftRead = false; this.endNode = endNode; } Object.defineProperty(DomReadingContext.prototype, "clientRectDeltaLeft", { get: function () { if (!this._clientRectDeltaLeftRead) { this._clientRectDeltaLeftRead = true; this._clientRectDeltaLeft = this._domNode.getBoundingClientRect().left; } return this._clientRectDeltaLeft; }, enumerable: true, configurable: true }); return DomReadingContext; }()); export { DomReadingContext }; var ViewLineOptions = /** @class */ (function () { function ViewLineOptions(config, themeType) { this.themeType = themeType; this.renderWhitespace = config.editor.viewInfo.renderWhitespace; this.renderControlCharacters = config.editor.viewInfo.renderControlCharacters; this.spaceWidth = config.editor.fontInfo.spaceWidth; this.useMonospaceOptimizations = (config.editor.fontInfo.isMonospace && !config.editor.viewInfo.disableMonospaceOptimizations); this.canUseHalfwidthRightwardsArrow = config.editor.fontInfo.canUseHalfwidthRightwardsArrow; this.lineHeight = config.editor.lineHeight; this.stopRenderingLineAfter = config.editor.viewInfo.stopRenderingLineAfter; this.fontLigatures = config.editor.viewInfo.fontLigatures; } ViewLineOptions.prototype.equals = function (other) { return (this.themeType === other.themeType && this.renderWhitespace === other.renderWhitespace && this.renderControlCharacters === other.renderControlCharacters && this.spaceWidth === other.spaceWidth && this.useMonospaceOptimizations === other.useMonospaceOptimizations && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow && this.lineHeight === other.lineHeight && this.stopRenderingLineAfter === other.stopRenderingLineAfter && this.fontLigatures === other.fontLigatures); }; return ViewLineOptions; }()); export { ViewLineOptions }; var ViewLine = /** @class */ (function () { function ViewLine(options) { this._options = options; this._isMaybeInvalid = true; this._renderedViewLine = null; } // --- begin IVisibleLineData ViewLine.prototype.getDomNode = function () { if (this._renderedViewLine && this._renderedViewLine.domNode) { return this._renderedViewLine.domNode.domNode; } return null; }; ViewLine.prototype.setDomNode = function (domNode) { if (this._renderedViewLine) { this._renderedViewLine.domNode = createFastDomNode(domNode); } else { throw new Error('I have no rendered view line to set the dom node to...'); } }; ViewLine.prototype.onContentChanged = function () { this._isMaybeInvalid = true; }; ViewLine.prototype.onTokensChanged = function () { this._isMaybeInvalid = true; }; ViewLine.prototype.onDecorationsChanged = function () { this._isMaybeInvalid = true; }; ViewLine.prototype.onOptionsChanged = function (newOptions) { this._isMaybeInvalid = true; this._options = newOptions; }; ViewLine.prototype.onSelectionChanged = function () { if (alwaysRenderInlineSelection || this._options.themeType === HIGH_CONTRAST) { this._isMaybeInvalid = true; return true; } return false; }; ViewLine.prototype.renderLine = function (lineNumber, deltaTop, viewportData, sb) { if (this._isMaybeInvalid === false) { // it appears that nothing relevant has changed return false; } this._isMaybeInvalid = false; var lineData = viewportData.getViewLineRenderingData(lineNumber); var options = this._options; var actualInlineDecorations = LineDecoration.filter(lineData.inlineDecorations, lineNumber, lineData.minColumn, lineData.maxColumn); if (alwaysRenderInlineSelection || options.themeType === HIGH_CONTRAST) { var selections = viewportData.selections; for (var i = 0, len = selections.length; i < len; i++) { var selection = selections[i]; if (selection.endLineNumber < lineNumber || selection.startLineNumber > lineNumber) { // Selection does not intersect line continue; } var startColumn = (selection.startLineNumber === lineNumber ? selection.startColumn : lineData.minColumn); var endColumn = (selection.endLineNumber === lineNumber ? selection.endColumn : lineData.maxColumn); if (startColumn < endColumn) { actualInlineDecorations.push(new LineDecoration(startColumn, endColumn, 'inline-selected-text', 0 /* Regular */)); } } } var renderLineInput = new RenderLineInput(options.useMonospaceOptimizations, options.canUseHalfwidthRightwardsArrow, lineData.content, lineData.continuesWithWrappedLine, lineData.isBasicASCII, lineData.containsRTL, lineData.minColumn - 1, lineData.tokens, actualInlineDecorations, lineData.tabSize, options.spaceWidth, options.stopRenderingLineAfter, options.renderWhitespace, options.renderControlCharacters, options.fontLigatures); if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) { // no need to do anything, we have the same render input return false; } sb.appendASCIIString('<div style="top:'); sb.appendASCIIString(String(deltaTop)); sb.appendASCIIString('px;height:'); sb.appendASCIIString(String(this._options.lineHeight)); sb.appendASCIIString('px;" class="'); sb.appendASCIIString(ViewLine.CLASS_NAME); sb.appendASCIIString('">'); var output = renderViewLine(renderLineInput, sb); sb.appendASCIIString('</div>'); var renderedViewLine = null; if (canUseFastRenderedViewLine && lineData.isBasicASCII && options.useMonospaceOptimizations && output.containsForeignElements === 0 /* None */) { if (lineData.content.length < 300 && renderLineInput.lineTokens.getCount() < 100) { // Browser rounding errors have been observed in Chrome and IE, so using the fast // view line only for short lines. Please test before removing the length check... // --- // Another rounding error has been observed on Linux in VSCode, where <span> width // rounding errors add up to an observable large number... // --- // Also see another example of rounding errors on Windows in // https://github.com/Microsoft/vscode/issues/33178 renderedViewLine = new FastRenderedViewLine(this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping); } } if (!renderedViewLine) { renderedViewLine = createRenderedLine(this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, output.characterMapping, output.containsRTL, output.containsForeignElements); } this._renderedViewLine = renderedViewLine; return true; }; ViewLine.prototype.layoutLine = function (lineNumber, deltaTop) { if (this._renderedViewLine && this._renderedViewLine.domNode) { this._renderedViewLine.domNode.setTop(deltaTop); this._renderedViewLine.domNode.setHeight(this._options.lineHeight); } }; // --- end IVisibleLineData ViewLine.prototype.getWidth = function () { if (!this._renderedViewLine) { return 0; } return this._renderedViewLine.getWidth(); }; ViewLine.prototype.getWidthIsFast = function () { if (!this._renderedViewLine) { return true; } return this._renderedViewLine.getWidthIsFast(); }; ViewLine.prototype.getVisibleRangesForRange = function (startColumn, endColumn, context) { if (!this._renderedViewLine) { return null; } startColumn = startColumn | 0; // @perf endColumn = endColumn | 0; // @perf startColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, startColumn)); endColumn = Math.min(this._renderedViewLine.input.lineContent.length + 1, Math.max(1, endColumn)); var stopRenderingLineAfter = this._renderedViewLine.input.stopRenderingLineAfter | 0; // @perf if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter && endColumn > stopRenderingLineAfter) { // This range is obviously not visible return null; } if (stopRenderingLineAfter !== -1 && startColumn > stopRenderingLineAfter) { startColumn = stopRenderingLineAfter; } if (stopRenderingLineAfter !== -1 && endColumn > stopRenderingLineAfter) { endColumn = stopRenderingLineAfter; } return this._renderedViewLine.getVisibleRangesForRange(startColumn, endColumn, context); }; ViewLine.prototype.getColumnOfNodeOffset = function (lineNumber, spanNode, offset) { if (!this._renderedViewLine) { return 1; } return this._renderedViewLine.getColumnOfNodeOffset(lineNumber, spanNode, offset); }; ViewLine.CLASS_NAME = 'view-line'; return ViewLine; }()); export { ViewLine }; /** * A rendered line which is guaranteed to contain only regular ASCII and is rendered with a monospace font. */ var FastRenderedViewLine = /** @class */ (function () { function FastRenderedViewLine(domNode, renderLineInput, characterMapping) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; this._charWidth = renderLineInput.spaceWidth; } FastRenderedViewLine.prototype.getWidth = function () { return this._getCharPosition(this._characterMapping.length); }; FastRenderedViewLine.prototype.getWidthIsFast = function () { return true; }; FastRenderedViewLine.prototype.getVisibleRangesForRange = function (startColumn, endColumn, context) { var startPosition = this._getCharPosition(startColumn); var endPosition = this._getCharPosition(endColumn); return [new HorizontalRange(startPosition, endPosition - startPosition)]; }; FastRenderedViewLine.prototype._getCharPosition = function (column) { var charOffset = this._characterMapping.getAbsoluteOffsets(); if (charOffset.length === 0) { // No characters on this line return 0; } return Math.round(this._charWidth * charOffset[column - 1]); }; FastRenderedViewLine.prototype.getColumnOfNodeOffset = function (lineNumber, spanNode, offset) { var spanNodeTextContentLength = spanNode.textContent.length; var spanIndex = -1; while (spanNode) { spanNode = spanNode.previousSibling; spanIndex++; } var charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); return charOffset + 1; }; return FastRenderedViewLine; }()); /** * Every time we render a line, we save what we have rendered in an instance of this class. */ var RenderedViewLine = /** @class */ (function () { function RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) { this.domNode = domNode; this.input = renderLineInput; this._characterMapping = characterMapping; this._isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent); this._containsForeignElements = containsForeignElements; this._cachedWidth = -1; this._pixelOffsetCache = null; if (!containsRTL || this._characterMapping.length === 0 /* the line is empty */) { this._pixelOffsetCache = new Int32Array(Math.max(2, this._characterMapping.length + 1)); for (var column = 0, len = this._characterMapping.length; column <= len; column++) { this._pixelOffsetCache[column] = -1; } } } // --- Reading from the DOM methods RenderedViewLine.prototype._getReadingTarget = function () { return this.domNode.domNode.firstChild; }; /** * Width of the line in pixels */ RenderedViewLine.prototype.getWidth = function () { if (this._cachedWidth === -1) { this._cachedWidth = this._getReadingTarget().offsetWidth; } return this._cachedWidth; }; RenderedViewLine.prototype.getWidthIsFast = function () { if (this._cachedWidth === -1) { return false; } return true; }; /** * Visible ranges for a model range */ RenderedViewLine.prototype.getVisibleRangesForRange = function (startColumn, endColumn, context) { if (this._pixelOffsetCache !== null) { // the text is LTR var startOffset = this._readPixelOffset(startColumn, context); if (startOffset === -1) { return null; } var endOffset = this._readPixelOffset(endColumn, context); if (endOffset === -1) { return null; } return [new HorizontalRange(startOffset, endOffset - startOffset)]; } return this._readVisibleRangesForRange(startColumn, endColumn, context); }; RenderedViewLine.prototype._readVisibleRangesForRange = function (startColumn, endColumn, context) { if (startColumn === endColumn) { var pixelOffset = this._readPixelOffset(startColumn, context); if (pixelOffset === -1) { return null; } else { return [new HorizontalRange(pixelOffset, 0)]; } } else { return this._readRawVisibleRangesForRange(startColumn, endColumn, context); } }; RenderedViewLine.prototype._readPixelOffset = function (column, context) { if (this._characterMapping.length === 0) { // This line has no content if (this._containsForeignElements === 0 /* None */) { // We can assume the line is really empty return 0; } if (this._containsForeignElements === 2 /* After */) { // We have foreign elements after the (empty) line return 0; } if (this._containsForeignElements === 1 /* Before */) { // We have foreign element before the (empty) line return this.getWidth(); } } if (this._pixelOffsetCache !== null) { // the text is LTR var cachedPixelOffset = this._pixelOffsetCache[column]; if (cachedPixelOffset !== -1) { return cachedPixelOffset; } var result = this._actualReadPixelOffset(column, context); this._pixelOffsetCache[column] = result; return result; } return this._actualReadPixelOffset(column, context); }; RenderedViewLine.prototype._actualReadPixelOffset = function (column, context) { if (this._characterMapping.length === 0) { // This line has no content var r_1 = RangeUtil.readHorizontalRanges(this._getReadingTarget(), 0, 0, 0, 0, context.clientRectDeltaLeft, context.endNode); if (!r_1 || r_1.length === 0) { return -1; } return r_1[0].left; } if (column === this._characterMapping.length && this._isWhitespaceOnly && this._containsForeignElements === 0 /* None */) { // This branch helps in the case of whitespace only lines which have a width set return this.getWidth(); } var partData = this._characterMapping.charOffsetToPartData(column - 1); var partIndex = CharacterMapping.getPartIndex(partData); var charOffsetInPart = CharacterMapping.getCharIndex(partData); var r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, context.clientRectDeltaLeft, context.endNode); if (!r || r.length === 0) { return -1; } return r[0].left; }; RenderedViewLine.prototype._readRawVisibleRangesForRange = function (startColumn, endColumn, context) { if (startColumn === 1 && endColumn === this._characterMapping.length) { // This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line return [new HorizontalRange(0, this.getWidth())]; } var startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1); var startPartIndex = CharacterMapping.getPartIndex(startPartData); var startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData); var endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1); var endPartIndex = CharacterMapping.getPartIndex(endPartData); var endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData); return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, context.clientRectDeltaLeft, context.endNode); }; /** * Returns the column for the text found at a specific offset inside a rendered dom node */ RenderedViewLine.prototype.getColumnOfNodeOffset = function (lineNumber, spanNode, offset) { var spanNodeTextContentLength = spanNode.textContent.length; var spanIndex = -1; while (spanNode) { spanNode = spanNode.previousSibling; spanIndex++; } var charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset); return charOffset + 1; }; return RenderedViewLine; }()); var WebKitRenderedViewLine = /** @class */ (function (_super) { __extends(WebKitRenderedViewLine, _super); function WebKitRenderedViewLine() { return _super !== null && _super.apply(this, arguments) || this; } WebKitRenderedViewLine.prototype._readVisibleRangesForRange = function (startColumn, endColumn, context) { var output = _super.prototype._readVisibleRangesForRange.call(this, startColumn, endColumn, context); if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) { return output; } // WebKit is buggy and returns an expanded range (to contain words in some cases) // The last client rect is enlarged (I think) if (!this.input.containsRTL) { // This is an attempt to patch things up // Find position of last column var endPixelOffset = this._readPixelOffset(endColumn, context); if (endPixelOffset !== -1) { var lastRange = output[output.length - 1]; if (lastRange.left < endPixelOffset) { // Trim down the width of the last visible range to not go after the last column's position lastRange.width = endPixelOffset - lastRange.left; } } } return output; }; return WebKitRenderedViewLine; }(RenderedViewLine)); var createRenderedLine = (function () { if (browser.isWebKit) { return createWebKitRenderedLine; } return createNormalRenderedLine; })(); function createWebKitRenderedLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) { return new WebKitRenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); } function createNormalRenderedLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements) { return new RenderedViewLine(domNode, renderLineInput, characterMapping, containsRTL, containsForeignElements); }