123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875 |
- /*---------------------------------------------------------------------------------------------
- * Copyright (c) Microsoft Corporation. All rights reserved.
- * Licensed under the MIT License. See License.txt in the project root for license information.
- *--------------------------------------------------------------------------------------------*/
- import * as strings from '../../../base/common/strings.js';
- import { createStringBuilder } from '../core/stringBuilder.js';
- import { LineDecoration, LineDecorationsNormalizer } from './lineDecorations.js';
- class LinePart {
- constructor(endIndex, type, metadata) {
- this._linePartBrand = undefined;
- this.endIndex = endIndex;
- this.type = type;
- this.metadata = metadata;
- }
- isWhitespace() {
- return (this.metadata & 1 /* IS_WHITESPACE_MASK */ ? true : false);
- }
- isPseudoAfter() {
- return (this.metadata & 4 /* PSEUDO_AFTER_MASK */ ? true : false);
- }
- }
- export class LineRange {
- constructor(startIndex, endIndex) {
- this.startOffset = startIndex;
- this.endOffset = endIndex;
- }
- equals(otherLineRange) {
- return this.startOffset === otherLineRange.startOffset
- && this.endOffset === otherLineRange.endOffset;
- }
- }
- export class RenderLineInput {
- constructor(useMonospaceOptimizations, canUseHalfwidthRightwardsArrow, lineContent, continuesWithWrappedLine, isBasicASCII, containsRTL, fauxIndentLength, lineTokens, lineDecorations, tabSize, startVisibleColumn, spaceWidth, middotWidth, wsmiddotWidth, stopRenderingLineAfter, renderWhitespace, renderControlCharacters, fontLigatures, selectionsOnLine) {
- this.useMonospaceOptimizations = useMonospaceOptimizations;
- this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
- this.lineContent = lineContent;
- this.continuesWithWrappedLine = continuesWithWrappedLine;
- this.isBasicASCII = isBasicASCII;
- this.containsRTL = containsRTL;
- this.fauxIndentLength = fauxIndentLength;
- this.lineTokens = lineTokens;
- this.lineDecorations = lineDecorations.sort(LineDecoration.compare);
- this.tabSize = tabSize;
- this.startVisibleColumn = startVisibleColumn;
- this.spaceWidth = spaceWidth;
- this.stopRenderingLineAfter = stopRenderingLineAfter;
- this.renderWhitespace = (renderWhitespace === 'all'
- ? 4 /* All */
- : renderWhitespace === 'boundary'
- ? 1 /* Boundary */
- : renderWhitespace === 'selection'
- ? 2 /* Selection */
- : renderWhitespace === 'trailing'
- ? 3 /* Trailing */
- : 0 /* None */);
- this.renderControlCharacters = renderControlCharacters;
- this.fontLigatures = fontLigatures;
- this.selectionsOnLine = selectionsOnLine && selectionsOnLine.sort((a, b) => a.startOffset < b.startOffset ? -1 : 1);
- const wsmiddotDiff = Math.abs(wsmiddotWidth - spaceWidth);
- const middotDiff = Math.abs(middotWidth - spaceWidth);
- if (wsmiddotDiff < middotDiff) {
- this.renderSpaceWidth = wsmiddotWidth;
- this.renderSpaceCharCode = 0x2E31; // U+2E31 - WORD SEPARATOR MIDDLE DOT
- }
- else {
- this.renderSpaceWidth = middotWidth;
- this.renderSpaceCharCode = 0xB7; // U+00B7 - MIDDLE DOT
- }
- }
- sameSelection(otherSelections) {
- if (this.selectionsOnLine === null) {
- return otherSelections === null;
- }
- if (otherSelections === null) {
- return false;
- }
- if (otherSelections.length !== this.selectionsOnLine.length) {
- return false;
- }
- for (let i = 0; i < this.selectionsOnLine.length; i++) {
- if (!this.selectionsOnLine[i].equals(otherSelections[i])) {
- return false;
- }
- }
- return true;
- }
- equals(other) {
- return (this.useMonospaceOptimizations === other.useMonospaceOptimizations
- && this.canUseHalfwidthRightwardsArrow === other.canUseHalfwidthRightwardsArrow
- && this.lineContent === other.lineContent
- && this.continuesWithWrappedLine === other.continuesWithWrappedLine
- && this.isBasicASCII === other.isBasicASCII
- && this.containsRTL === other.containsRTL
- && this.fauxIndentLength === other.fauxIndentLength
- && this.tabSize === other.tabSize
- && this.startVisibleColumn === other.startVisibleColumn
- && this.spaceWidth === other.spaceWidth
- && this.renderSpaceWidth === other.renderSpaceWidth
- && this.renderSpaceCharCode === other.renderSpaceCharCode
- && this.stopRenderingLineAfter === other.stopRenderingLineAfter
- && this.renderWhitespace === other.renderWhitespace
- && this.renderControlCharacters === other.renderControlCharacters
- && this.fontLigatures === other.fontLigatures
- && LineDecoration.equalsArr(this.lineDecorations, other.lineDecorations)
- && this.lineTokens.equals(other.lineTokens)
- && this.sameSelection(other.selectionsOnLine));
- }
- }
- export class DomPosition {
- constructor(partIndex, charIndex) {
- this.partIndex = partIndex;
- this.charIndex = charIndex;
- }
- }
- /**
- * Provides a both direction mapping between a line's character and its rendered position.
- */
- export class CharacterMapping {
- constructor(length, partCount) {
- this.length = length;
- this._data = new Uint32Array(this.length);
- this._absoluteOffsets = new Uint32Array(this.length);
- }
- static getPartIndex(partData) {
- return (partData & 4294901760 /* PART_INDEX_MASK */) >>> 16 /* PART_INDEX_OFFSET */;
- }
- static getCharIndex(partData) {
- return (partData & 65535 /* CHAR_INDEX_MASK */) >>> 0 /* CHAR_INDEX_OFFSET */;
- }
- setColumnInfo(column, partIndex, charIndex, partAbsoluteOffset) {
- const partData = ((partIndex << 16 /* PART_INDEX_OFFSET */)
- | (charIndex << 0 /* CHAR_INDEX_OFFSET */)) >>> 0;
- this._data[column - 1] = partData;
- this._absoluteOffsets[column - 1] = partAbsoluteOffset + charIndex;
- }
- getAbsoluteOffset(column) {
- if (this._absoluteOffsets.length === 0) {
- // No characters on this line
- return 0;
- }
- return this._absoluteOffsets[column - 1];
- }
- charOffsetToPartData(charOffset) {
- if (this.length === 0) {
- return 0;
- }
- if (charOffset < 0) {
- return this._data[0];
- }
- if (charOffset >= this.length) {
- return this._data[this.length - 1];
- }
- return this._data[charOffset];
- }
- getDomPosition(column) {
- const partData = this.charOffsetToPartData(column - 1);
- const partIndex = CharacterMapping.getPartIndex(partData);
- const charIndex = CharacterMapping.getCharIndex(partData);
- return new DomPosition(partIndex, charIndex);
- }
- getColumn(domPosition, partLength) {
- const charOffset = this.partDataToCharOffset(domPosition.partIndex, partLength, domPosition.charIndex);
- return charOffset + 1;
- }
- partDataToCharOffset(partIndex, partLength, charIndex) {
- if (this.length === 0) {
- return 0;
- }
- let searchEntry = ((partIndex << 16 /* PART_INDEX_OFFSET */)
- | (charIndex << 0 /* CHAR_INDEX_OFFSET */)) >>> 0;
- let min = 0;
- let max = this.length - 1;
- while (min + 1 < max) {
- let mid = ((min + max) >>> 1);
- let midEntry = this._data[mid];
- if (midEntry === searchEntry) {
- return mid;
- }
- else if (midEntry > searchEntry) {
- max = mid;
- }
- else {
- min = mid;
- }
- }
- if (min === max) {
- return min;
- }
- let minEntry = this._data[min];
- let maxEntry = this._data[max];
- if (minEntry === searchEntry) {
- return min;
- }
- if (maxEntry === searchEntry) {
- return max;
- }
- let minPartIndex = CharacterMapping.getPartIndex(minEntry);
- let minCharIndex = CharacterMapping.getCharIndex(minEntry);
- let maxPartIndex = CharacterMapping.getPartIndex(maxEntry);
- let maxCharIndex;
- if (minPartIndex !== maxPartIndex) {
- // sitting between parts
- maxCharIndex = partLength;
- }
- else {
- maxCharIndex = CharacterMapping.getCharIndex(maxEntry);
- }
- let minEntryDistance = charIndex - minCharIndex;
- let maxEntryDistance = maxCharIndex - charIndex;
- if (minEntryDistance <= maxEntryDistance) {
- return min;
- }
- return max;
- }
- }
- export class RenderLineOutput {
- constructor(characterMapping, containsRTL, containsForeignElements) {
- this._renderLineOutputBrand = undefined;
- this.characterMapping = characterMapping;
- this.containsRTL = containsRTL;
- this.containsForeignElements = containsForeignElements;
- }
- }
- export function renderViewLine(input, sb) {
- if (input.lineContent.length === 0) {
- if (input.lineDecorations.length > 0) {
- // This line is empty, but it contains inline decorations
- sb.appendASCIIString(`<span>`);
- let beforeCount = 0;
- let afterCount = 0;
- let containsForeignElements = 0 /* None */;
- for (const lineDecoration of input.lineDecorations) {
- if (lineDecoration.type === 1 /* Before */ || lineDecoration.type === 2 /* After */) {
- sb.appendASCIIString(`<span class="`);
- sb.appendASCIIString(lineDecoration.className);
- sb.appendASCIIString(`"></span>`);
- if (lineDecoration.type === 1 /* Before */) {
- containsForeignElements |= 1 /* Before */;
- beforeCount++;
- }
- if (lineDecoration.type === 2 /* After */) {
- containsForeignElements |= 2 /* After */;
- afterCount++;
- }
- }
- }
- sb.appendASCIIString(`</span>`);
- const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
- characterMapping.setColumnInfo(1, beforeCount, 0, 0);
- return new RenderLineOutput(characterMapping, false, containsForeignElements);
- }
- // completely empty line
- sb.appendASCIIString('<span><span></span></span>');
- return new RenderLineOutput(new CharacterMapping(0, 0), false, 0 /* None */);
- }
- return _renderLine(resolveRenderLineInput(input), sb);
- }
- export class RenderLineOutput2 {
- constructor(characterMapping, html, containsRTL, containsForeignElements) {
- this.characterMapping = characterMapping;
- this.html = html;
- this.containsRTL = containsRTL;
- this.containsForeignElements = containsForeignElements;
- }
- }
- export function renderViewLine2(input) {
- let sb = createStringBuilder(10000);
- let out = renderViewLine(input, sb);
- return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements);
- }
- class ResolvedRenderLineInput {
- constructor(fontIsMonospace, canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, parts, containsForeignElements, fauxIndentLength, tabSize, startVisibleColumn, containsRTL, spaceWidth, renderSpaceCharCode, renderWhitespace, renderControlCharacters) {
- this.fontIsMonospace = fontIsMonospace;
- this.canUseHalfwidthRightwardsArrow = canUseHalfwidthRightwardsArrow;
- this.lineContent = lineContent;
- this.len = len;
- this.isOverflowing = isOverflowing;
- this.parts = parts;
- this.containsForeignElements = containsForeignElements;
- this.fauxIndentLength = fauxIndentLength;
- this.tabSize = tabSize;
- this.startVisibleColumn = startVisibleColumn;
- this.containsRTL = containsRTL;
- this.spaceWidth = spaceWidth;
- this.renderSpaceCharCode = renderSpaceCharCode;
- this.renderWhitespace = renderWhitespace;
- this.renderControlCharacters = renderControlCharacters;
- //
- }
- }
- function resolveRenderLineInput(input) {
- const lineContent = input.lineContent;
- let isOverflowing;
- let len;
- if (input.stopRenderingLineAfter !== -1 && input.stopRenderingLineAfter < lineContent.length) {
- isOverflowing = true;
- len = input.stopRenderingLineAfter;
- }
- else {
- isOverflowing = false;
- len = lineContent.length;
- }
- let tokens = transformAndRemoveOverflowing(input.lineTokens, input.fauxIndentLength, len);
- if (input.renderControlCharacters && !input.isBasicASCII) {
- // Calling `extractControlCharacters` before adding (possibly empty) line parts
- // for inline decorations. `extractControlCharacters` removes empty line parts.
- tokens = extractControlCharacters(lineContent, tokens);
- }
- if (input.renderWhitespace === 4 /* All */ ||
- input.renderWhitespace === 1 /* Boundary */ ||
- (input.renderWhitespace === 2 /* Selection */ && !!input.selectionsOnLine) ||
- input.renderWhitespace === 3 /* Trailing */) {
- tokens = _applyRenderWhitespace(input, lineContent, len, tokens);
- }
- let containsForeignElements = 0 /* None */;
- if (input.lineDecorations.length > 0) {
- for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
- const lineDecoration = input.lineDecorations[i];
- if (lineDecoration.type === 3 /* RegularAffectingLetterSpacing */) {
- // Pretend there are foreign elements... although not 100% accurate.
- containsForeignElements |= 1 /* Before */;
- }
- else if (lineDecoration.type === 1 /* Before */) {
- containsForeignElements |= 1 /* Before */;
- }
- else if (lineDecoration.type === 2 /* After */) {
- containsForeignElements |= 2 /* After */;
- }
- }
- tokens = _applyInlineDecorations(lineContent, len, tokens, input.lineDecorations);
- }
- if (!input.containsRTL) {
- // We can never split RTL text, as it ruins the rendering
- tokens = splitLargeTokens(lineContent, tokens, !input.isBasicASCII || input.fontLigatures);
- }
- return new ResolvedRenderLineInput(input.useMonospaceOptimizations, input.canUseHalfwidthRightwardsArrow, lineContent, len, isOverflowing, tokens, containsForeignElements, input.fauxIndentLength, input.tabSize, input.startVisibleColumn, input.containsRTL, input.spaceWidth, input.renderSpaceCharCode, input.renderWhitespace, input.renderControlCharacters);
- }
- /**
- * In the rendering phase, characters are always looped until token.endIndex.
- * Ensure that all tokens end before `len` and the last one ends precisely at `len`.
- */
- function transformAndRemoveOverflowing(tokens, fauxIndentLength, len) {
- let result = [], resultLen = 0;
- // The faux indent part of the line should have no token type
- if (fauxIndentLength > 0) {
- result[resultLen++] = new LinePart(fauxIndentLength, '', 0);
- }
- for (let tokenIndex = 0, tokensLen = tokens.getCount(); tokenIndex < tokensLen; tokenIndex++) {
- const endIndex = tokens.getEndOffset(tokenIndex);
- if (endIndex <= fauxIndentLength) {
- // The faux indent part of the line should have no token type
- continue;
- }
- const type = tokens.getClassName(tokenIndex);
- if (endIndex >= len) {
- result[resultLen++] = new LinePart(len, type, 0);
- break;
- }
- result[resultLen++] = new LinePart(endIndex, type, 0);
- }
- return result;
- }
- /**
- * See https://github.com/microsoft/vscode/issues/6885.
- * It appears that having very large spans causes very slow reading of character positions.
- * So here we try to avoid that.
- */
- function splitLargeTokens(lineContent, tokens, onlyAtSpaces) {
- let lastTokenEndIndex = 0;
- let result = [], resultLen = 0;
- if (onlyAtSpaces) {
- // Split only at spaces => we need to walk each character
- for (let i = 0, len = tokens.length; i < len; i++) {
- const token = tokens[i];
- const tokenEndIndex = token.endIndex;
- if (lastTokenEndIndex + 50 /* LongToken */ < tokenEndIndex) {
- const tokenType = token.type;
- const tokenMetadata = token.metadata;
- let lastSpaceOffset = -1;
- let currTokenStart = lastTokenEndIndex;
- for (let j = lastTokenEndIndex; j < tokenEndIndex; j++) {
- if (lineContent.charCodeAt(j) === 32 /* Space */) {
- lastSpaceOffset = j;
- }
- if (lastSpaceOffset !== -1 && j - currTokenStart >= 50 /* LongToken */) {
- // Split at `lastSpaceOffset` + 1
- result[resultLen++] = new LinePart(lastSpaceOffset + 1, tokenType, tokenMetadata);
- currTokenStart = lastSpaceOffset + 1;
- lastSpaceOffset = -1;
- }
- }
- if (currTokenStart !== tokenEndIndex) {
- result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
- }
- }
- else {
- result[resultLen++] = token;
- }
- lastTokenEndIndex = tokenEndIndex;
- }
- }
- else {
- // Split anywhere => we don't need to walk each character
- for (let i = 0, len = tokens.length; i < len; i++) {
- const token = tokens[i];
- const tokenEndIndex = token.endIndex;
- let diff = (tokenEndIndex - lastTokenEndIndex);
- if (diff > 50 /* LongToken */) {
- const tokenType = token.type;
- const tokenMetadata = token.metadata;
- const piecesCount = Math.ceil(diff / 50 /* LongToken */);
- for (let j = 1; j < piecesCount; j++) {
- let pieceEndIndex = lastTokenEndIndex + (j * 50 /* LongToken */);
- result[resultLen++] = new LinePart(pieceEndIndex, tokenType, tokenMetadata);
- }
- result[resultLen++] = new LinePart(tokenEndIndex, tokenType, tokenMetadata);
- }
- else {
- result[resultLen++] = token;
- }
- lastTokenEndIndex = tokenEndIndex;
- }
- }
- return result;
- }
- function isControlCharacter(charCode) {
- if (charCode < 32) {
- return (charCode !== 9 /* Tab */);
- }
- if (charCode === 127) {
- // DEL
- return true;
- }
- if ((charCode >= 0x202A && charCode <= 0x202E)
- || (charCode >= 0x2066 && charCode <= 0x2069)
- || (charCode >= 0x200E && charCode <= 0x200F)
- || charCode === 0x061C) {
- // Unicode Directional Formatting Characters
- // LRE U+202A LEFT-TO-RIGHT EMBEDDING
- // RLE U+202B RIGHT-TO-LEFT EMBEDDING
- // PDF U+202C POP DIRECTIONAL FORMATTING
- // LRO U+202D LEFT-TO-RIGHT OVERRIDE
- // RLO U+202E RIGHT-TO-LEFT OVERRIDE
- // LRI U+2066 LEFT-TO-RIGHT ISOLATE
- // RLI U+2067 RIGHT-TO-LEFT ISOLATE
- // FSI U+2068 FIRST STRONG ISOLATE
- // PDI U+2069 POP DIRECTIONAL ISOLATE
- // LRM U+200E LEFT-TO-RIGHT MARK
- // RLM U+200F RIGHT-TO-LEFT MARK
- // ALM U+061C ARABIC LETTER MARK
- return true;
- }
- return false;
- }
- function extractControlCharacters(lineContent, tokens) {
- let result = [];
- let lastLinePart = new LinePart(0, '', 0);
- let charOffset = 0;
- for (const token of tokens) {
- const tokenEndIndex = token.endIndex;
- for (; charOffset < tokenEndIndex; charOffset++) {
- const charCode = lineContent.charCodeAt(charOffset);
- if (isControlCharacter(charCode)) {
- if (charOffset > lastLinePart.endIndex) {
- // emit previous part if it has text
- lastLinePart = new LinePart(charOffset, token.type, token.metadata);
- result.push(lastLinePart);
- }
- lastLinePart = new LinePart(charOffset + 1, 'mtkcontrol', token.metadata);
- result.push(lastLinePart);
- }
- }
- if (charOffset > lastLinePart.endIndex) {
- // emit previous part if it has text
- lastLinePart = new LinePart(tokenEndIndex, token.type, token.metadata);
- result.push(lastLinePart);
- }
- }
- return result;
- }
- /**
- * Whitespace is rendered by "replacing" tokens with a special-purpose `mtkw` type that is later recognized in the rendering phase.
- * Moreover, a token is created for every visual indent because on some fonts the glyphs used for rendering whitespace (→ or ·) do not have the same width as .
- * The rendering phase will generate `style="width:..."` for these tokens.
- */
- function _applyRenderWhitespace(input, lineContent, len, tokens) {
- const continuesWithWrappedLine = input.continuesWithWrappedLine;
- const fauxIndentLength = input.fauxIndentLength;
- const tabSize = input.tabSize;
- const startVisibleColumn = input.startVisibleColumn;
- const useMonospaceOptimizations = input.useMonospaceOptimizations;
- const selections = input.selectionsOnLine;
- const onlyBoundary = (input.renderWhitespace === 1 /* Boundary */);
- const onlyTrailing = (input.renderWhitespace === 3 /* Trailing */);
- const generateLinePartForEachWhitespace = (input.renderSpaceWidth !== input.spaceWidth);
- let result = [], resultLen = 0;
- let tokenIndex = 0;
- let tokenType = tokens[tokenIndex].type;
- let tokenEndIndex = tokens[tokenIndex].endIndex;
- const tokensLength = tokens.length;
- let lineIsEmptyOrWhitespace = false;
- let firstNonWhitespaceIndex = strings.firstNonWhitespaceIndex(lineContent);
- let lastNonWhitespaceIndex;
- if (firstNonWhitespaceIndex === -1) {
- lineIsEmptyOrWhitespace = true;
- firstNonWhitespaceIndex = len;
- lastNonWhitespaceIndex = len;
- }
- else {
- lastNonWhitespaceIndex = strings.lastNonWhitespaceIndex(lineContent);
- }
- let wasInWhitespace = false;
- let currentSelectionIndex = 0;
- let currentSelection = selections && selections[currentSelectionIndex];
- let tmpIndent = startVisibleColumn % tabSize;
- for (let charIndex = fauxIndentLength; charIndex < len; charIndex++) {
- const chCode = lineContent.charCodeAt(charIndex);
- if (currentSelection && charIndex >= currentSelection.endOffset) {
- currentSelectionIndex++;
- currentSelection = selections && selections[currentSelectionIndex];
- }
- let isInWhitespace;
- if (charIndex < firstNonWhitespaceIndex || charIndex > lastNonWhitespaceIndex) {
- // in leading or trailing whitespace
- isInWhitespace = true;
- }
- else if (chCode === 9 /* Tab */) {
- // a tab character is rendered both in all and boundary cases
- isInWhitespace = true;
- }
- else if (chCode === 32 /* Space */) {
- // hit a space character
- if (onlyBoundary) {
- // rendering only boundary whitespace
- if (wasInWhitespace) {
- isInWhitespace = true;
- }
- else {
- const nextChCode = (charIndex + 1 < len ? lineContent.charCodeAt(charIndex + 1) : 0 /* Null */);
- isInWhitespace = (nextChCode === 32 /* Space */ || nextChCode === 9 /* Tab */);
- }
- }
- else {
- isInWhitespace = true;
- }
- }
- else {
- isInWhitespace = false;
- }
- // If rendering whitespace on selection, check that the charIndex falls within a selection
- if (isInWhitespace && selections) {
- isInWhitespace = !!currentSelection && currentSelection.startOffset <= charIndex && currentSelection.endOffset > charIndex;
- }
- // If rendering only trailing whitespace, check that the charIndex points to trailing whitespace.
- if (isInWhitespace && onlyTrailing) {
- isInWhitespace = lineIsEmptyOrWhitespace || charIndex > lastNonWhitespaceIndex;
- }
- if (wasInWhitespace) {
- // was in whitespace token
- if (!isInWhitespace || (!useMonospaceOptimizations && tmpIndent >= tabSize)) {
- // leaving whitespace token or entering a new indent
- if (generateLinePartForEachWhitespace) {
- const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
- for (let i = lastEndIndex + 1; i <= charIndex; i++) {
- result[resultLen++] = new LinePart(i, 'mtkw', 1 /* IS_WHITESPACE */);
- }
- }
- else {
- result[resultLen++] = new LinePart(charIndex, 'mtkw', 1 /* IS_WHITESPACE */);
- }
- tmpIndent = tmpIndent % tabSize;
- }
- }
- else {
- // was in regular token
- if (charIndex === tokenEndIndex || (isInWhitespace && charIndex > fauxIndentLength)) {
- result[resultLen++] = new LinePart(charIndex, tokenType, 0);
- tmpIndent = tmpIndent % tabSize;
- }
- }
- if (chCode === 9 /* Tab */) {
- tmpIndent = tabSize;
- }
- else if (strings.isFullWidthCharacter(chCode)) {
- tmpIndent += 2;
- }
- else {
- tmpIndent++;
- }
- wasInWhitespace = isInWhitespace;
- while (charIndex === tokenEndIndex) {
- tokenIndex++;
- if (tokenIndex < tokensLength) {
- tokenType = tokens[tokenIndex].type;
- tokenEndIndex = tokens[tokenIndex].endIndex;
- }
- else {
- break;
- }
- }
- }
- let generateWhitespace = false;
- if (wasInWhitespace) {
- // was in whitespace token
- if (continuesWithWrappedLine && onlyBoundary) {
- let lastCharCode = (len > 0 ? lineContent.charCodeAt(len - 1) : 0 /* Null */);
- let prevCharCode = (len > 1 ? lineContent.charCodeAt(len - 2) : 0 /* Null */);
- let isSingleTrailingSpace = (lastCharCode === 32 /* Space */ && (prevCharCode !== 32 /* Space */ && prevCharCode !== 9 /* Tab */));
- if (!isSingleTrailingSpace) {
- generateWhitespace = true;
- }
- }
- else {
- generateWhitespace = true;
- }
- }
- if (generateWhitespace) {
- if (generateLinePartForEachWhitespace) {
- const lastEndIndex = (resultLen > 0 ? result[resultLen - 1].endIndex : fauxIndentLength);
- for (let i = lastEndIndex + 1; i <= len; i++) {
- result[resultLen++] = new LinePart(i, 'mtkw', 1 /* IS_WHITESPACE */);
- }
- }
- else {
- result[resultLen++] = new LinePart(len, 'mtkw', 1 /* IS_WHITESPACE */);
- }
- }
- else {
- result[resultLen++] = new LinePart(len, tokenType, 0);
- }
- return result;
- }
- /**
- * Inline decorations are "merged" on top of tokens.
- * Special care must be taken when multiple inline decorations are at play and they overlap.
- */
- function _applyInlineDecorations(lineContent, len, tokens, _lineDecorations) {
- _lineDecorations.sort(LineDecoration.compare);
- const lineDecorations = LineDecorationsNormalizer.normalize(lineContent, _lineDecorations);
- const lineDecorationsLen = lineDecorations.length;
- let lineDecorationIndex = 0;
- let result = [], resultLen = 0, lastResultEndIndex = 0;
- for (let tokenIndex = 0, len = tokens.length; tokenIndex < len; tokenIndex++) {
- const token = tokens[tokenIndex];
- const tokenEndIndex = token.endIndex;
- const tokenType = token.type;
- const tokenMetadata = token.metadata;
- while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset < tokenEndIndex) {
- const lineDecoration = lineDecorations[lineDecorationIndex];
- if (lineDecoration.startOffset > lastResultEndIndex) {
- lastResultEndIndex = lineDecoration.startOffset;
- result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
- }
- if (lineDecoration.endOffset + 1 <= tokenEndIndex) {
- // This line decoration ends before this token ends
- lastResultEndIndex = lineDecoration.endOffset + 1;
- result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
- lineDecorationIndex++;
- }
- else {
- // This line decoration continues on to the next token
- lastResultEndIndex = tokenEndIndex;
- result[resultLen++] = new LinePart(lastResultEndIndex, tokenType + ' ' + lineDecoration.className, tokenMetadata | lineDecoration.metadata);
- break;
- }
- }
- if (tokenEndIndex > lastResultEndIndex) {
- lastResultEndIndex = tokenEndIndex;
- result[resultLen++] = new LinePart(lastResultEndIndex, tokenType, tokenMetadata);
- }
- }
- const lastTokenEndIndex = tokens[tokens.length - 1].endIndex;
- if (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
- while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
- const lineDecoration = lineDecorations[lineDecorationIndex];
- result[resultLen++] = new LinePart(lastResultEndIndex, lineDecoration.className, lineDecoration.metadata);
- lineDecorationIndex++;
- }
- }
- return result;
- }
- /**
- * This function is on purpose not split up into multiple functions to allow runtime type inference (i.e. performance reasons).
- * Notice how all the needed data is fully resolved and passed in (i.e. no other calls).
- */
- function _renderLine(input, sb) {
- const fontIsMonospace = input.fontIsMonospace;
- const canUseHalfwidthRightwardsArrow = input.canUseHalfwidthRightwardsArrow;
- const containsForeignElements = input.containsForeignElements;
- const lineContent = input.lineContent;
- const len = input.len;
- const isOverflowing = input.isOverflowing;
- const parts = input.parts;
- const fauxIndentLength = input.fauxIndentLength;
- const tabSize = input.tabSize;
- const startVisibleColumn = input.startVisibleColumn;
- const containsRTL = input.containsRTL;
- const spaceWidth = input.spaceWidth;
- const renderSpaceCharCode = input.renderSpaceCharCode;
- const renderWhitespace = input.renderWhitespace;
- const renderControlCharacters = input.renderControlCharacters;
- const characterMapping = new CharacterMapping(len + 1, parts.length);
- let lastCharacterMappingDefined = false;
- let charIndex = 0;
- let visibleColumn = startVisibleColumn;
- let charOffsetInPart = 0;
- let partDisplacement = 0;
- let prevPartContentCnt = 0;
- let partAbsoluteOffset = 0;
- if (containsRTL) {
- sb.appendASCIIString('<span dir="ltr">');
- }
- else {
- sb.appendASCIIString('<span>');
- }
- for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) {
- partAbsoluteOffset += prevPartContentCnt;
- const part = parts[partIndex];
- const partEndIndex = part.endIndex;
- const partType = part.type;
- const partRendersWhitespace = (renderWhitespace !== 0 /* None */ && part.isWhitespace());
- const partRendersWhitespaceWithWidth = partRendersWhitespace && !fontIsMonospace && (partType === 'mtkw' /*only whitespace*/ || !containsForeignElements);
- const partIsEmptyAndHasPseudoAfter = (charIndex === partEndIndex && part.isPseudoAfter());
- charOffsetInPart = 0;
- sb.appendASCIIString('<span class="');
- sb.appendASCIIString(partRendersWhitespaceWithWidth ? 'mtkz' : partType);
- sb.appendASCII(34 /* DoubleQuote */);
- if (partRendersWhitespace) {
- let partContentCnt = 0;
- {
- let _charIndex = charIndex;
- let _visibleColumn = visibleColumn;
- for (; _charIndex < partEndIndex; _charIndex++) {
- const charCode = lineContent.charCodeAt(_charIndex);
- const charWidth = (charCode === 9 /* Tab */ ? (tabSize - (_visibleColumn % tabSize)) : 1) | 0;
- partContentCnt += charWidth;
- if (_charIndex >= fauxIndentLength) {
- _visibleColumn += charWidth;
- }
- }
- }
- if (partRendersWhitespaceWithWidth) {
- sb.appendASCIIString(' style="width:');
- sb.appendASCIIString(String(spaceWidth * partContentCnt));
- sb.appendASCIIString('px"');
- }
- sb.appendASCII(62 /* GreaterThan */);
- for (; charIndex < partEndIndex; charIndex++) {
- characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
- partDisplacement = 0;
- const charCode = lineContent.charCodeAt(charIndex);
- let charWidth;
- if (charCode === 9 /* Tab */) {
- charWidth = (tabSize - (visibleColumn % tabSize)) | 0;
- if (!canUseHalfwidthRightwardsArrow || charWidth > 1) {
- sb.write1(0x2192); // RIGHTWARDS ARROW
- }
- else {
- sb.write1(0xFFEB); // HALFWIDTH RIGHTWARDS ARROW
- }
- for (let space = 2; space <= charWidth; space++) {
- sb.write1(0xA0); //
- }
- }
- else { // must be CharCode.Space
- charWidth = 1;
- sb.write1(renderSpaceCharCode); // · or word separator middle dot
- }
- charOffsetInPart += charWidth;
- if (charIndex >= fauxIndentLength) {
- visibleColumn += charWidth;
- }
- }
- prevPartContentCnt = partContentCnt;
- }
- else {
- let partContentCnt = 0;
- sb.appendASCII(62 /* GreaterThan */);
- for (; charIndex < partEndIndex; charIndex++) {
- characterMapping.setColumnInfo(charIndex + 1, partIndex - partDisplacement, charOffsetInPart, partAbsoluteOffset);
- partDisplacement = 0;
- const charCode = lineContent.charCodeAt(charIndex);
- let producedCharacters = 1;
- let charWidth = 1;
- switch (charCode) {
- case 9 /* Tab */:
- producedCharacters = (tabSize - (visibleColumn % tabSize));
- charWidth = producedCharacters;
- for (let space = 1; space <= producedCharacters; space++) {
- sb.write1(0xA0); //
- }
- break;
- case 32 /* Space */:
- sb.write1(0xA0); //
- break;
- case 60 /* LessThan */:
- sb.appendASCIIString('<');
- break;
- case 62 /* GreaterThan */:
- sb.appendASCIIString('>');
- break;
- case 38 /* Ampersand */:
- sb.appendASCIIString('&');
- break;
- case 0 /* Null */:
- if (renderControlCharacters) {
- // See https://unicode-table.com/en/blocks/control-pictures/
- sb.write1(9216);
- }
- else {
- sb.appendASCIIString('�');
- }
- break;
- case 65279 /* UTF8_BOM */:
- case 8232 /* LINE_SEPARATOR */:
- case 8233 /* PARAGRAPH_SEPARATOR */:
- case 133 /* NEXT_LINE */:
- sb.write1(0xFFFD);
- break;
- default:
- if (strings.isFullWidthCharacter(charCode)) {
- charWidth++;
- }
- // See https://unicode-table.com/en/blocks/control-pictures/
- if (renderControlCharacters && charCode < 32) {
- sb.write1(9216 + charCode);
- }
- else if (renderControlCharacters && charCode === 127) {
- // DEL
- sb.write1(9249);
- }
- else if (renderControlCharacters && isControlCharacter(charCode)) {
- sb.appendASCIIString('[U+');
- sb.appendASCIIString(to4CharHex(charCode));
- sb.appendASCIIString(']');
- producedCharacters = 8;
- }
- else {
- sb.write1(charCode);
- }
- }
- charOffsetInPart += producedCharacters;
- partContentCnt += producedCharacters;
- if (charIndex >= fauxIndentLength) {
- visibleColumn += charWidth;
- }
- }
- prevPartContentCnt = partContentCnt;
- }
- if (partIsEmptyAndHasPseudoAfter) {
- partDisplacement++;
- }
- else {
- partDisplacement = 0;
- }
- if (charIndex >= len && !lastCharacterMappingDefined && part.isPseudoAfter()) {
- lastCharacterMappingDefined = true;
- characterMapping.setColumnInfo(charIndex + 1, partIndex, charOffsetInPart, partAbsoluteOffset);
- }
- sb.appendASCIIString('</span>');
- }
- if (!lastCharacterMappingDefined) {
- // When getting client rects for the last character, we will position the
- // text range at the end of the span, insteaf of at the beginning of next span
- characterMapping.setColumnInfo(len + 1, parts.length - 1, charOffsetInPart, partAbsoluteOffset);
- }
- if (isOverflowing) {
- sb.appendASCIIString('<span>…</span>');
- }
- sb.appendASCIIString('</span>');
- return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
- }
- function to4CharHex(n) {
- return n.toString(16).toUpperCase().padStart(4, '0');
- }
|