/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ 'use strict'; import {ViewLineToken} from 'vs/editor/common/core/viewLineToken'; export class RenderLineInput { _renderLineInputBrand: void; lineContent: string; tabSize: number; spaceWidth: number; stopRenderingLineAfter: number; renderWhitespace: boolean; parts: ViewLineToken[]; constructor( lineContent: string, tabSize: number, spaceWidth: number, stopRenderingLineAfter: number, renderWhitespace: boolean, parts: ViewLineToken[] ) { this.lineContent = lineContent; this.tabSize = tabSize; this.spaceWidth = spaceWidth; this.stopRenderingLineAfter = stopRenderingLineAfter; this.renderWhitespace = renderWhitespace; this.parts = parts; } } export class RenderLineOutput { _renderLineOutputBrand: void; charOffsetInPart: number[]; lastRenderedPartIndex: number; output: string; constructor(charOffsetInPart: number[], lastRenderedPartIndex: number, output: string) { this.charOffsetInPart = charOffsetInPart; this.lastRenderedPartIndex = lastRenderedPartIndex; this.output = output; } } const _space = ' '.charCodeAt(0); const _tab = '\t'.charCodeAt(0); const _lowerThan = '<'.charCodeAt(0); const _greaterThan = '>'.charCodeAt(0); const _ampersand = '&'.charCodeAt(0); const _carriageReturn = '\r'.charCodeAt(0); const _lineSeparator = '\u2028'.charCodeAt(0); //http://www.fileformat.info/info/unicode/char/2028/index.htm const _bom = 65279; export function renderLine(input:RenderLineInput): RenderLineOutput { const lineText = input.lineContent; const lineTextLength = lineText.length; const tabSize = input.tabSize; const spaceWidth = input.spaceWidth; const actualLineParts = input.parts; const renderWhitespace = input.renderWhitespace; const charBreakIndex = (input.stopRenderingLineAfter === -1 ? lineTextLength : input.stopRenderingLineAfter - 1); if (lineTextLength === 0) { return new RenderLineOutput( [], 0, // This is basically for IE's hit test to work ' ' ); } if (actualLineParts.length === 0) { throw new Error('Cannot render non empty line without line parts!'); } return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts.slice(0), renderWhitespace, charBreakIndex); } function isWhitespace(type:string): boolean { return (type.indexOf('whitespace') >= 0); } function isIndentGuide(type:string): boolean { return (type.indexOf('indent-guide') >= 0); } function renderLineActual(lineText:string, lineTextLength:number, tabSize:number, spaceWidth:number, actualLineParts:ViewLineToken[], renderWhitespace:boolean, charBreakIndex:number): RenderLineOutput { lineTextLength = +lineTextLength; tabSize = +tabSize; charBreakIndex = +charBreakIndex; let charIndex = 0; let out = ''; let charOffsetInPartArr: number[] = []; let charOffsetInPart = 0; let tabsCharDelta = 0; out += ''; for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) { let part = actualLineParts[partIndex]; let parsRendersWhitespace = (renderWhitespace && isWhitespace(part.type)); let partIsFixedWidth = parsRendersWhitespace || isIndentGuide(part.type); let toCharIndex = lineTextLength; if (partIndex + 1 < partIndexLen) { let nextPart = actualLineParts[partIndex + 1]; toCharIndex = Math.min(lineTextLength, nextPart.startIndex); } charOffsetInPart = 0; if (partIsFixedWidth) { let partContentCnt = 0; let partContent = ''; for (; charIndex < toCharIndex; charIndex++) { charOffsetInPartArr[charIndex] = charOffsetInPart; let charCode = lineText.charCodeAt(charIndex); if (charCode === _tab) { let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; charOffsetInPart += insertSpacesCount - 1; if (insertSpacesCount > 0) { partContent += parsRendersWhitespace ? '→' : ' '; partContentCnt++; insertSpacesCount--; } while (insertSpacesCount > 0) { partContent += ' '; partContentCnt++; insertSpacesCount--; } } else { // must be _space partContent += parsRendersWhitespace ? '·' : ' '; partContentCnt++; } charOffsetInPart ++; if (charIndex >= charBreakIndex) { out += ''; out += partContent; out += '…'; charOffsetInPartArr[charIndex] = charOffsetInPart; return new RenderLineOutput( charOffsetInPartArr, partIndex, out ); } } out += ''; out += partContent; out += ''; } else { out += ''; for (; charIndex < toCharIndex; charIndex++) { charOffsetInPartArr[charIndex] = charOffsetInPart; let charCode = lineText.charCodeAt(charIndex); switch (charCode) { case _tab: let insertSpacesCount = tabSize - (charIndex + tabsCharDelta) % tabSize; tabsCharDelta += insertSpacesCount - 1; charOffsetInPart += insertSpacesCount - 1; while (insertSpacesCount > 0) { out += ' '; insertSpacesCount--; } break; case _space: out += ' '; break; case _lowerThan: out += '<'; break; case _greaterThan: out += '>'; break; case _ampersand: out += '&'; break; case 0: out += '�'; break; case _bom: case _lineSeparator: out += '\ufffd'; break; case _carriageReturn: // zero width space, because carriage return would introduce a line break out += '​'; break; default: out += lineText.charAt(charIndex); } charOffsetInPart ++; if (charIndex >= charBreakIndex) { out += '…'; charOffsetInPartArr[charIndex] = charOffsetInPart; return new RenderLineOutput( charOffsetInPartArr, partIndex, out ); } } out += ''; } } out += ''; // 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 charOffsetInPartArr.push(charOffsetInPart); return new RenderLineOutput( charOffsetInPartArr, actualLineParts.length - 1, out ); }