From 121f96c2ac7727f6ff5a27dd7642c9d2ef472603 Mon Sep 17 00:00:00 2001 From: Alex Dima Date: Sat, 26 Aug 2017 14:50:44 +0200 Subject: [PATCH] Use a StringBuilder for rendering view lines --- src/vs/editor/browser/view/viewLayer.ts | 44 +++--- src/vs/editor/browser/view/viewOverlays.ts | 15 +- .../viewParts/glyphMargin/glyphMargin.ts | 4 +- .../browser/viewParts/lines/viewLine.ts | 21 ++- .../editor/browser/widget/diffEditorWidget.ts | 36 ++--- src/vs/editor/browser/widget/diffReview.ts | 2 +- src/vs/editor/common/core/stringBuilder.ts | 148 ++++++++++++++++++ .../common/viewLayout/viewLineRenderer.ts | 121 +++++++++----- src/vs/editor/standalone/browser/colorizer.ts | 2 +- .../viewLayout/viewLineRenderer.test.ts | 14 +- 10 files changed, 311 insertions(+), 96 deletions(-) create mode 100644 src/vs/editor/common/core/stringBuilder.ts diff --git a/src/vs/editor/browser/view/viewLayer.ts b/src/vs/editor/browser/view/viewLayer.ts index 61f6e0419e2..188c9961616 100644 --- a/src/vs/editor/browser/view/viewLayer.ts +++ b/src/vs/editor/browser/view/viewLayer.ts @@ -7,6 +7,7 @@ import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; +import { createStringBuilder, IStringBuilder } from 'vs/editor/common/core/stringBuilder'; /** * Represents a visible line @@ -22,7 +23,7 @@ export interface IVisibleLine { * Return null if the HTML should not be touched. * Return the new HTML otherwise. */ - renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData): string; + renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder): boolean; /** * Layout the line. @@ -503,12 +504,12 @@ class ViewLayerRenderer { ctx.lines.splice(removeIndex, removeCount); } - private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string[], wasNew: boolean[]): void { + private _finishRenderingNewLines(ctx: IRendererContext, domNodeIsEmpty: boolean, newLinesHTML: string, wasNew: boolean[]): void { let lastChild = this.domNode.lastChild; if (domNodeIsEmpty || !lastChild) { - this.domNode.innerHTML = newLinesHTML.join(''); + this.domNode.innerHTML = newLinesHTML; } else { - lastChild.insertAdjacentHTML('afterend', newLinesHTML.join('')); + lastChild.insertAdjacentHTML('afterend', newLinesHTML); } let currChild = this.domNode.lastChild; @@ -521,10 +522,10 @@ class ViewLayerRenderer { } } - private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string[], wasInvalid: boolean[]): void { + private _finishRenderingInvalidLines(ctx: IRendererContext, invalidLinesHTML: string, wasInvalid: boolean[]): void { let hugeDomNode = document.createElement('div'); - hugeDomNode.innerHTML = invalidLinesHTML.join(''); + hugeDomNode.innerHTML = invalidLinesHTML; for (let i = 0; i < ctx.linesLength; i++) { let line = ctx.lines[i]; @@ -537,14 +538,21 @@ class ViewLayerRenderer { } } + private static _sb1 = createStringBuilder(100000); + private static _sb2 = createStringBuilder(100000); + private _finishRendering(ctx: IRendererContext, domNodeIsEmpty: boolean, deltaTop: number[]): void { + const sb1 = ViewLayerRenderer._sb1; + const sb2 = ViewLayerRenderer._sb2; + + sb1.reset(); + sb2.reset(); + let hadNewLine = false; let wasNew: boolean[] = []; - let newLinesHTML: string[] = []; let hadInvalidLine = false; let wasInvalid: boolean[] = []; - let invalidLinesHTML: string[] = []; for (let i = 0, len = ctx.linesLength; i < len; i++) { let line = ctx.lines[i]; @@ -553,19 +561,19 @@ class ViewLayerRenderer { wasNew[i] = false; wasInvalid[i] = false; - let renderResult = line.renderLine(lineNumber, deltaTop[i], this.viewportData); + const lineDomNode = line.getDomNode(); - if (renderResult !== null) { - // Line needs rendering - let lineDomNode = line.getDomNode(); - if (!lineDomNode) { + if (!lineDomNode) { + let renderResult = line.renderLine(lineNumber, deltaTop[i], this.viewportData, sb1); + if (renderResult) { // Line is new - newLinesHTML.push(renderResult); wasNew[i] = true; hadNewLine = true; - } else { + } + } else { + let renderResult = line.renderLine(lineNumber, deltaTop[i], this.viewportData, sb2); + if (renderResult) { // Line is invalid - invalidLinesHTML.push(renderResult); wasInvalid[i] = true; hadInvalidLine = true; } @@ -573,11 +581,11 @@ class ViewLayerRenderer { } if (hadNewLine) { - this._finishRenderingNewLines(ctx, domNodeIsEmpty, newLinesHTML, wasNew); + this._finishRenderingNewLines(ctx, domNodeIsEmpty, sb1.build(), wasNew); } if (hadInvalidLine) { - this._finishRenderingInvalidLines(ctx, invalidLinesHTML, wasInvalid); + this._finishRenderingInvalidLines(ctx, sb2.build(), wasInvalid); } } } diff --git a/src/vs/editor/browser/view/viewOverlays.ts b/src/vs/editor/browser/view/viewOverlays.ts index 89c91ee3359..56e398547a0 100644 --- a/src/vs/editor/browser/view/viewOverlays.ts +++ b/src/vs/editor/browser/view/viewOverlays.ts @@ -14,6 +14,7 @@ import { RenderingContext, RestrictedRenderingContext } from 'vs/editor/common/v import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import * as viewEvents from 'vs/editor/common/view/viewEvents'; import { ViewPart } from 'vs/editor/browser/view/viewPart'; +import { IStringBuilder } from 'vs/editor/common/core/stringBuilder'; export class ViewOverlays extends ViewPart implements IVisibleLinesHost { @@ -178,7 +179,7 @@ export class ViewOverlayLine implements IVisibleLine { } } - public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData): string { + public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder): boolean { let result = ''; for (let i = 0, len = this._dynamicOverlays.length; i < len; i++) { let dynamicOverlay = this._dynamicOverlays[i]; @@ -187,12 +188,20 @@ export class ViewOverlayLine implements IVisibleLine { if (this._renderedContent === result) { // No rendering needed - return null; + return false; } this._renderedContent = result; - return `
${result}
`; + sb.appendASCIIString('
'); + sb.appendASCIIString(result); + sb.appendASCIIString('
'); + + return true; } public layoutLine(lineNumber: number, deltaTop: number): void { diff --git a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts index 455a49cb3fb..33874c27b51 100644 --- a/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts +++ b/src/vs/editor/browser/viewParts/glyphMargin/glyphMargin.ts @@ -142,12 +142,12 @@ export class GlyphMarginOverlay extends DedupOverlay { protected _getDecorations(ctx: RenderingContext): DecorationToRender[] { let decorations = ctx.getDecorationsInViewport(); - let r: DecorationToRender[] = []; + let r: DecorationToRender[] = [], rLen = 0; for (let i = 0, len = decorations.length; i < len; i++) { let d = decorations[i]; let glyphMarginClassName = d.source.options.glyphMarginClassName; if (glyphMarginClassName) { - r.push(new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName)); + r[rLen++] = new DecorationToRender(d.range.startLineNumber, d.range.endLineNumber, glyphMarginClassName); } } return r; diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 15dc26899b7..c7e00b01e7c 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -16,6 +16,7 @@ import { RangeUtil } from 'vs/editor/browser/viewParts/lines/rangeUtil'; import { HorizontalRange } from 'vs/editor/common/view/renderingContext'; import { ViewportData } from 'vs/editor/common/viewLayout/viewLinesViewportData'; import { ThemeType, HIGH_CONTRAST } from 'vs/platform/theme/common/themeService'; +import { IStringBuilder } from 'vs/editor/common/core/stringBuilder'; const canUseFastRenderedViewLine = (function () { if (platform.isNative) { @@ -156,10 +157,10 @@ export class ViewLine implements IVisibleLine { return false; } - public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData): string { + public renderLine(lineNumber: number, deltaTop: number, viewportData: ViewportData, sb: IStringBuilder): boolean { if (this._isMaybeInvalid === false) { // it appears that nothing relevant has changed - return null; + return false; } this._isMaybeInvalid = false; @@ -204,10 +205,20 @@ export class ViewLine implements IVisibleLine { if (this._renderedViewLine && this._renderedViewLine.input.equals(renderLineInput)) { // no need to do anything, we have the same render input - return null; + return false; } - const output = renderViewLine(renderLineInput); + sb.appendASCIIString('
'); + + const output = renderViewLine(renderLineInput, sb); + + sb.appendASCIIString('
'); let renderedViewLine: IRenderedViewLine = null; if (canUseFastRenderedViewLine && options.useMonospaceOptimizations && !output.containsForeignElements) { @@ -239,7 +250,7 @@ export class ViewLine implements IVisibleLine { this._renderedViewLine = renderedViewLine; - return `
${output.html}
`; + return true; } public layoutLine(lineNumber: number, deltaTop: number): void { diff --git a/src/vs/editor/browser/widget/diffEditorWidget.ts b/src/vs/editor/browser/widget/diffEditorWidget.ts index d2d51594629..3ae7a7ec473 100644 --- a/src/vs/editor/browser/widget/diffEditorWidget.ts +++ b/src/vs/editor/browser/widget/diffEditorWidget.ts @@ -42,6 +42,7 @@ import { ModelDecorationOptions } from 'vs/editor/common/model/textModelWithDeco import { DiffReview } from 'vs/editor/browser/widget/diffReview'; import URI from 'vs/base/common/uri'; import { IMessageService } from 'vs/platform/message/common/message'; +import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder'; interface IEditorDiffDecorations { decorations: editorCommon.IModelDeltaDecoration[]; @@ -1943,12 +1944,12 @@ class InlineViewZonesComputer extends ViewZonesComputer { } } - let html: string[] = []; + let sb = createStringBuilder(10000); let marginHTML: string[] = []; let lineDecorationsWidth = this.modifiedEditorConfiguration.layoutInfo.decorationsWidth; let lineHeight = this.modifiedEditorConfiguration.lineHeight; for (let lineNumber = lineChange.originalStartLineNumber; lineNumber <= lineChange.originalEndLineNumber; lineNumber++) { - html = html.concat(this.renderOriginalLine(lineNumber - lineChange.originalStartLineNumber, this.originalModel, this.modifiedEditorConfiguration, this.modifiedEditorTabSize, lineNumber, decorations)); + this.renderOriginalLine(lineNumber - lineChange.originalStartLineNumber, this.originalModel, this.modifiedEditorConfiguration, this.modifiedEditorTabSize, lineNumber, decorations, sb); if (this.renderIndicators) { let index = lineNumber - lineChange.originalStartLineNumber; @@ -1960,7 +1961,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { let domNode = document.createElement('div'); domNode.className = 'view-lines line-delete'; - domNode.innerHTML = html.join(''); + domNode.innerHTML = sb.build(); Configuration.applyFontInfoSlow(domNode, this.modifiedEditorConfiguration.fontInfo); let marginDomNode = document.createElement('div'); @@ -1977,7 +1978,7 @@ class InlineViewZonesComputer extends ViewZonesComputer { }; } - private renderOriginalLine(count: number, originalModel: editorCommon.IModel, config: editorOptions.InternalEditorOptions, tabSize: number, lineNumber: number, decorations: InlineDecoration[]): string[] { + private renderOriginalLine(count: number, originalModel: editorCommon.IModel, config: editorOptions.InternalEditorOptions, tabSize: number, lineNumber: number, decorations: InlineDecoration[], sb: IStringBuilder): void { let lineContent = originalModel.getLineContent(lineNumber); let actualDecorations = LineDecoration.filter(decorations, lineNumber, 1, lineContent.length + 1); @@ -1988,7 +1989,16 @@ class InlineViewZonesComputer extends ViewZonesComputer { | (ColorId.DefaultBackground << MetadataConsts.BACKGROUND_OFFSET) ) >>> 0; - let r = renderViewLine(new RenderLineInput( + sb.appendASCIIString('
'); + + renderViewLine(new RenderLineInput( (config.fontInfo.isMonospace && !config.viewInfo.disableMonospaceOptimizations), lineContent, originalModel.mightContainRTL(), @@ -2001,21 +2011,9 @@ class InlineViewZonesComputer extends ViewZonesComputer { config.viewInfo.renderWhitespace, config.viewInfo.renderControlCharacters, config.viewInfo.fontLigatures - )); - - let myResult: string[] = []; - myResult.push('
'); - myResult = myResult.concat(r.html); - myResult.push('
'); + ), sb); - return myResult; + sb.appendASCIIString('
'); } } diff --git a/src/vs/editor/browser/widget/diffReview.ts b/src/vs/editor/browser/widget/diffReview.ts index 777054eee06..b40b43f5d7f 100644 --- a/src/vs/editor/browser/widget/diffReview.ts +++ b/src/vs/editor/browser/widget/diffReview.ts @@ -10,7 +10,7 @@ import { Disposable } from 'vs/base/common/lifecycle'; import * as dom from 'vs/base/browser/dom'; import { FastDomNode, createFastDomNode } from 'vs/base/browser/fastDomNode'; import * as editorCommon from 'vs/editor/common/editorCommon'; -import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { renderViewLine2 as renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { Configuration } from 'vs/editor/browser/config/configuration'; import { Position } from 'vs/editor/common/core/position'; diff --git a/src/vs/editor/common/core/stringBuilder.ts b/src/vs/editor/common/core/stringBuilder.ts new file mode 100644 index 00000000000..31f49aab619 --- /dev/null +++ b/src/vs/editor/common/core/stringBuilder.ts @@ -0,0 +1,148 @@ +/*--------------------------------------------------------------------------------------------- + * 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 * as strings from 'vs/base/common/strings'; + +declare var TextDecoder: any; // TODO@TypeScript +interface TextDecoder { + decode(view: Uint16Array): string; +} + +export interface IStringBuilder { + build(): string; + reset(): void; + write1(charCode: number): void; + appendASCII(charCode: number): void; + appendASCIIString(str: string): void; +} + +export let createStringBuilder: (capacity: number) => IStringBuilder; + +if ((self).TextDecoder) { + createStringBuilder = (capacity) => new StringBuilder(capacity); +} else { + createStringBuilder = (capacity) => new CompatStringBuilder(); +} + +class StringBuilder implements IStringBuilder { + + private readonly _decoder: TextDecoder; + private readonly _capacity: number; + private readonly _buffer: Uint16Array; + + private _completedStrings: string[]; + private _bufferLength: number; + + constructor(capacity: number) { + this._decoder = new TextDecoder('UTF-16LE'); + this._capacity = capacity | 0; + this._buffer = new Uint16Array(this._capacity); + + this._completedStrings = null; + this._bufferLength = 0; + } + + public reset(): void { + this._completedStrings = null; + this._bufferLength = 0; + } + + public build(): string { + if (this._completedStrings !== null) { + this._flushBuffer(); + return this._completedStrings.join(''); + } + return this._buildBuffer(); + } + + private _buildBuffer(): string { + if (this._bufferLength === 0) { + return ''; + } + + const view = new Uint16Array(this._buffer.buffer, 0, this._bufferLength); + return this._decoder.decode(view); + } + + private _flushBuffer(): void { + const bufferString = this._buildBuffer(); + this._bufferLength = 0; + + if (this._completedStrings === null) { + this._completedStrings = [bufferString]; + } else { + this._completedStrings.push(bufferString); + } + } + + public write1(charCode: number): void { + const remainingSpace = this._capacity - this._bufferLength; + + if (remainingSpace <= 1) { + if (remainingSpace === 0 || strings.isHighSurrogate(charCode)) { + this._flushBuffer(); + } + } + + this._buffer[this._bufferLength++] = charCode; + } + + public appendASCII(charCode: number): void { + if (this._bufferLength === this._capacity) { + // buffer is full + this._flushBuffer(); + } + this._buffer[this._bufferLength++] = charCode; + } + + public appendASCIIString(str: string): void { + const strLen = str.length; + + if (this._bufferLength + strLen >= this._capacity) { + // This string does not fit in the remaining buffer space + + this._flushBuffer(); + this._completedStrings.push(str); + return; + } + + for (let i = 0; i < strLen; i++) { + this._buffer[this._bufferLength++] = str.charCodeAt(i); + } + } +} + +class CompatStringBuilder implements IStringBuilder { + + private _pieces: string[]; + private _piecesLen: number; + + constructor() { + this._pieces = []; + this._piecesLen = 0; + } + + public reset(): void { + this._pieces = []; + this._piecesLen = 0; + } + + public build(): string { + return this._pieces.join(''); + } + + public write1(charCode: number): void { + this._pieces[this._piecesLen++] = String.fromCharCode(charCode); + } + + public appendASCII(charCode: number): void { + this._pieces[this._piecesLen++] = String.fromCharCode(charCode); + } + + public appendASCIIString(str: string): void { + this._pieces[this._piecesLen++] = str; + } +} diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index efa94b6b44b..0d6ca556af3 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -8,6 +8,7 @@ import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { CharCode } from 'vs/base/common/charCode'; import { LineDecoration, LineDecorationsNormalizer } from 'vs/editor/common/viewLayout/lineDecorations'; import * as strings from 'vs/base/common/strings'; +import { IStringBuilder, createStringBuilder } from 'vs/editor/common/core/stringBuilder'; export const enum RenderWhitespace { None = 0, @@ -223,19 +224,17 @@ export class RenderLineOutput { _renderLineOutputBrand: void; readonly characterMapping: CharacterMapping; - readonly html: string; readonly containsRTL: boolean; readonly containsForeignElements: boolean; - constructor(characterMapping: CharacterMapping, html: string, containsRTL: boolean, containsForeignElements: boolean) { + constructor(characterMapping: CharacterMapping, containsRTL: boolean, containsForeignElements: boolean) { this.characterMapping = characterMapping; - this.html = html; this.containsRTL = containsRTL; this.containsForeignElements = containsForeignElements; } } -export function renderViewLine(input: RenderLineInput): RenderLineOutput { +export function renderViewLine(input: RenderLineInput, sb: IStringBuilder): RenderLineOutput { if (input.lineContent.length === 0) { let containsForeignElements = false; @@ -259,15 +258,31 @@ export function renderViewLine(input: RenderLineInput): RenderLineOutput { } } + sb.appendASCIIString(content); return new RenderLineOutput( new CharacterMapping(0, 0), - content, false, containsForeignElements ); } - return _renderLine(resolveRenderLineInput(input)); + return _renderLine(resolveRenderLineInput(input), sb); +} + +export class RenderLineOutput2 { + constructor( + public readonly characterMapping: CharacterMapping, + public readonly html: string, + public readonly containsRTL: boolean, + public readonly containsForeignElements: boolean + ) { + } +} + +export function renderViewLine2(input: RenderLineInput): RenderLineOutput2 { + let sb = createStringBuilder(10000); + let out = renderViewLine(input, sb); + return new RenderLineOutput2(out.characterMapping, sb.build(), out.containsRTL, out.containsForeignElements); } class ResolvedRenderLineInput { @@ -564,7 +579,7 @@ function _applyInlineDecorations(lineContent: string, len: number, tokens: LineP * 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: ResolvedRenderLineInput): RenderLineOutput { +function _renderLine(input: ResolvedRenderLineInput, sb: IStringBuilder): RenderLineOutput { const fontIsMonospace = input.fontIsMonospace; const containsForeignElements = input.containsForeignElements; const lineContent = input.lineContent; @@ -583,7 +598,8 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { let tabsCharDelta = 0; let charOffsetInPart = 0; - let out = ''; + sb.appendASCIIString(''); + for (let partIndex = 0, tokensLen = parts.length; partIndex < tokensLen; partIndex++) { const part = parts[partIndex]; const partEndIndex = part.endIndex; @@ -591,10 +607,42 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { const partRendersWhitespace = (renderWhitespace !== RenderWhitespace.None && (partType.indexOf('vs-whitespace') >= 0)); charOffsetInPart = 0; + sb.appendASCIIString(' 0) { - partContent += '→'; - partContentCnt++; + sb.appendASCIIString('→'); insertSpacesCount--; } while (insertSpacesCount > 0) { - partContent += ' '; - partContentCnt++; + sb.appendASCIIString(' '); insertSpacesCount--; } } else { // must be CharCode.Space - partContent += '·'; - partContentCnt++; + sb.appendASCIIString('·'); } charOffsetInPart++; } characterMapping.setPartLength(partIndex, partContentCnt); - if (fontIsMonospace || containsForeignElements) { - out += `${partContent}`; - } else { - out += `${partContent}`; - } } else { let partContentCnt = 0; - let partContent = ''; + + if (containsRTL) { + sb.appendASCIIString(' dir="ltr"'); + } + sb.appendASCII(CharCode.GreaterThan); for (; charIndex < partEndIndex; charIndex++) { characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); @@ -644,55 +688,55 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { tabsCharDelta += insertSpacesCount - 1; charOffsetInPart += insertSpacesCount - 1; while (insertSpacesCount > 0) { - partContent += ' '; + sb.appendASCIIString(' '); partContentCnt++; insertSpacesCount--; } break; case CharCode.Space: - partContent += ' '; + sb.appendASCIIString(' '); partContentCnt++; break; case CharCode.LessThan: - partContent += '<'; + sb.appendASCIIString('<'); partContentCnt++; break; case CharCode.GreaterThan: - partContent += '>'; + sb.appendASCIIString('>'); partContentCnt++; break; case CharCode.Ampersand: - partContent += '&'; + sb.appendASCIIString('&'); partContentCnt++; break; case CharCode.Null: - partContent += '�'; + sb.appendASCIIString('�'); partContentCnt++; break; case CharCode.UTF8_BOM: case CharCode.LINE_SEPARATOR_2028: - partContent += '\ufffd'; + sb.write1(0xfffd); partContentCnt++; break; case CharCode.CarriageReturn: // zero width space, because carriage return would introduce a line break - partContent += '​'; + sb.appendASCIIString('​'); partContentCnt++; break; default: if (renderControlCharacters && charCode < 32) { - partContent += String.fromCharCode(9216 + charCode); + sb.write1(9216 + charCode); partContentCnt++; } else { - partContent += String.fromCharCode(charCode); + sb.write1(charCode); partContentCnt++; } } @@ -701,13 +745,10 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { } characterMapping.setPartLength(partIndex, partContentCnt); - if (containsRTL) { - out += `${partContent}`; - } else { - out += `${partContent}`; - } - } + + sb.appendASCIIString(''); + } // When getting client rects for the last character, we will position the @@ -715,10 +756,10 @@ function _renderLine(input: ResolvedRenderLineInput): RenderLineOutput { characterMapping.setPartData(len, parts.length - 1, charOffsetInPart); if (isOverflowing) { - out += ``; + sb.appendASCIIString(''); } - out += ''; + sb.appendASCIIString(''); - return new RenderLineOutput(characterMapping, out, containsRTL, containsForeignElements); + return new RenderLineOutput(characterMapping, containsRTL, containsForeignElements); } diff --git a/src/vs/editor/standalone/browser/colorizer.ts b/src/vs/editor/standalone/browser/colorizer.ts index d3834a81578..c8fa5477679 100644 --- a/src/vs/editor/standalone/browser/colorizer.ts +++ b/src/vs/editor/standalone/browser/colorizer.ts @@ -9,7 +9,7 @@ import { TPromise } from 'vs/base/common/winjs.base'; import { IModel } from 'vs/editor/common/editorCommon'; import { ColorId, MetadataConsts, FontStyle, TokenizationRegistry, ITokenizationSupport } from 'vs/editor/common/modes'; import { IModeService } from 'vs/editor/common/services/modeService'; -import { renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { renderViewLine2 as renderViewLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { LineTokens } from 'vs/editor/common/core/lineTokens'; import * as strings from 'vs/base/common/strings'; diff --git a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts index 847031c48a1..f3d9bb997b5 100644 --- a/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts +++ b/src/vs/editor/test/common/viewLayout/viewLineRenderer.test.ts @@ -5,7 +5,7 @@ 'use strict'; import * as assert from 'assert'; -import { renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; +import { renderViewLine2 as renderViewLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer'; import { ViewLineToken } from 'vs/editor/common/core/viewLineToken'; import { CharCode } from 'vs/base/common/charCode'; import { MetadataConsts } from 'vs/editor/common/modes'; @@ -56,7 +56,7 @@ suite('viewLineRenderer.renderLine', () => { assertCharacterReplacement('a\0b', 4, 'a�b', [[0, 1, 2, 3]], [3]); assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]); assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [[0, 1, 2, 3]], [3]); - assertCharacterReplacement('a\rb', 4, 'a​b', [[0, 1, 2, 3]], [3]); + assertCharacterReplacement('a\rb', 4, 'a​b', [[0, 1, 2, 3]], [3]); }); test('handles tabs', () => { @@ -355,10 +355,10 @@ suite('viewLineRenderer.renderLine', () => { ]; let expectedOutput = [ - 'var', - ' קודמות = ', - '"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם"', - ';' + 'var', + ' קודמות = ', + '"מיותר קודמות צ\'ט של, אם לשון העברית שינויים ויש, אם"', + ';' ].join(''); let _actual = renderViewLine(new RenderLineInput( @@ -546,7 +546,7 @@ suite('viewLineRenderer.renderLine', () => { let lineText = 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.'; let lineParts = [createPart(lineText.length, 1)]; let expectedOutput = [ - 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.' + 'את גרמנית בהתייחסות שמו, שנתי המשפט אל חפש, אם כתב אחרים ולחבר. של התוכן אודות בויקיפדיה כלל, של עזרה כימיה היא. על עמוד יוצרים מיתולוגיה סדר, אם שכל שתפו לעברית שינויים, אם שאלות אנגלית עזה. שמות בקלות מה סדר.' ]; let actual = renderViewLine(new RenderLineInput( false, -- GitLab