text: /*---------------------------------------------------------------------------------------------
   *  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';
  import { createStringBuilder } from '../core/stringBuilder';
  import { LineDecoration, LineDecorationsNormalizer } from './lineDecorations';
  class LinePart {
      constructor(endIndex, type, metadata) {
          this.endIndex = endIndex;
          this.type = type;
          this.metadata = metadata;
      }
      isWhitespace() {
          return (this.metadata & 1 /* IS_WHITESPACE_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));
      }
  }
  /**
   * 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 */;
      }
      setPartData(charOffset, partIndex, charIndex, partAbsoluteOffset) {
          let partData = ((partIndex << 16 /* PART_INDEX_OFFSET */)
              | (charIndex << 0 /* CHAR_INDEX_OFFSET */)) >>> 0;
          this._data[charOffset] = partData;
          this._absoluteOffsets[charOffset] = partAbsoluteOffset + charIndex;
      }
      getAbsoluteOffsets() {
          return this._absoluteOffsets;
      }
      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];
      }
      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.characterMapping = characterMapping;
          this.containsRTL = containsRTL;
          this.containsForeignElements = containsForeignElements;
      }
  }
  export function renderViewLine(input, sb) {
      if (input.lineContent.length === 0) {
          let containsForeignElements = 0 /* None */;
          let content = '<span><span></span></span>';
          if (input.lineDecorations.length > 0) {
              // This line is empty, but it contains inline decorations
              const beforeClassNames = [];
              const afterClassNames = [];
              for (let i = 0, len = input.lineDecorations.length; i < len; i++) {
                  const lineDecoration = input.lineDecorations[i];
                  if (lineDecoration.type === 1 /* Before */) {
                      beforeClassNames.push(input.lineDecorations[i].className);
                      containsForeignElements |= 1 /* Before */;
                  }
                  if (lineDecoration.type === 2 /* After */) {
                      afterClassNames.push(input.lineDecorations[i].className);
                      containsForeignElements |= 2 /* After */;
                  }
              }
              if (containsForeignElements !== 0 /* None */) {
                  const beforeSpan = (beforeClassNames.length > 0 ? `<span class="${beforeClassNames.join(' ')}"></span>` : ``);
                  const afterSpan = (afterClassNames.length > 0 ? `<span class="${afterClassNames.join(' ')}"></span>` : ``);
                  content = `<span>${beforeSpan}${afterSpan}</span>`;
              }
          }
          sb.appendASCIIString(content);
          return new RenderLineOutput(new CharacterMapping(0, 0), false, containsForeignElements);
      }
      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.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;
  }
  /**
   * 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 (&rarr; or &middot;) do not have the same width as &nbsp;.
   * 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;
              }
          }
      }
      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) {
          let classNames = [];
          let metadata = 0;
          while (lineDecorationIndex < lineDecorationsLen && lineDecorations[lineDecorationIndex].startOffset === lastTokenEndIndex) {
              classNames.push(lineDecorations[lineDecorationIndex].className);
              metadata |= lineDecorations[lineDecorationIndex].metadata;
              lineDecorationIndex++;
          }
          result[resultLen++] = new LinePart(lastResultEndIndex, classNames.join(' '), metadata);
      }
      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 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.metadata === 4 /* PSEUDO_AFTER */);
          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.setPartData(charIndex, 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); // &nbsp;
                      }
                  }
                  else { // must be CharCode.Space
                      charWidth = 1;
                      sb.write1(renderSpaceCharCode); // &middot; 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.setPartData(charIndex, 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); // &nbsp;
                          }
                          break;
                      case 32 /* Space */:
                          sb.write1(0xA0); // &nbsp;
                          break;
                      case 60 /* LessThan */:
                          sb.appendASCIIString('&lt;');
                          break;
                      case 62 /* GreaterThan */:
                          sb.appendASCIIString('&gt;');
                          break;
                      case 38 /* Ampersand */:
                          sb.appendASCIIString('&amp;');
                          break;
                      case 0 /* Null */:
                          sb.appendASCIIString('&#00;');
                          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++;
                          }
                          if (renderControlCharacters && charCode < 32) {
                              sb.write1(9216 + charCode);
                          }
                          else {
                              sb.write1(charCode);
                          }
                  }
                  charOffsetInPart += producedCharacters;
                  partContentCnt += producedCharacters;
                  if (charIndex >= fauxIndentLength) {
                      visibleColumn += charWidth;
                  }
              }
              prevPartContentCnt = partContentCnt;
          }
          if (partIsEmptyAndHasPseudoAfter) {
              partDisplacement++;
          }
          else {
              partDisplacement = 0;
          }
          sb.appendASCIIString('</span>');
      }
      // 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.setPartData(len, parts.length - 1, charOffsetInPart, partAbsoluteOffset);
      if (isOverflowing) {
          sb.appendASCIIString('<span>&hellip;</span>');
      }
      sb.appendASCIIString('</span>');
      return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements);
  }
  