diff --git a/src/vs/editor/browser/viewParts/lines/viewLine.ts b/src/vs/editor/browser/viewParts/lines/viewLine.ts index 7f278afa2f05ad86692b94cdbf64ec5dd00d77db..a9802d855c6f0b8de750029ba860d98bcb9d52fd 100644 --- a/src/vs/editor/browser/viewParts/lines/viewLine.ts +++ b/src/vs/editor/browser/viewParts/lines/viewLine.ts @@ -122,10 +122,13 @@ export class ViewLine implements IVisibleLineData { return false; } + let isWhitespaceOnly = /^\s*$/.test(renderLineInput.lineContent); + this._renderedViewLine = createRenderedLine( this._renderedViewLine ? this._renderedViewLine.domNode : null, renderLineInput, this._context.model.mightContainRTL(), + isWhitespaceOnly, renderLine(renderLineInput) ); return true; @@ -183,12 +186,12 @@ class RenderedViewLine { */ private _pixelOffsetCache: number[]; - constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput) { + constructor(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput) { this.domNode = domNode; this.input = renderLineInput; this.html = renderLineOutput.output; this._characterMapping = renderLineOutput.characterMapping; - this._isWhitespaceOnly = renderLineOutput.isWhitespaceOnly; + this._isWhitespaceOnly = isWhitespaceOnly; this._cachedWidth = -1; this._pixelOffsetCache = null; @@ -376,17 +379,17 @@ class WebKitRenderedViewLine extends RenderedViewLine { } } -const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput) => RenderedViewLine = (function () { +const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput) => RenderedViewLine = (function () { if (browser.isWebKit) { return createWebKitRenderedLine; } return createNormalRenderedLine; })(); -function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { - return new WebKitRenderedViewLine(domNode, renderLineInput, modelContainsRTL, renderLineOutput); +function createWebKitRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { + return new WebKitRenderedViewLine(domNode, renderLineInput, modelContainsRTL, isWhitespaceOnly, renderLineOutput); } -function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { - return new RenderedViewLine(domNode, renderLineInput, modelContainsRTL, renderLineOutput); +function createNormalRenderedLine(domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, isWhitespaceOnly: boolean, renderLineOutput: RenderLineOutput): RenderedViewLine { + return new RenderedViewLine(domNode, renderLineInput, modelContainsRTL, isWhitespaceOnly, renderLineOutput); } diff --git a/src/vs/editor/common/viewLayout/viewLineRenderer.ts b/src/vs/editor/common/viewLayout/viewLineRenderer.ts index 58621061fbc794ed4bfaab5373907272f833febb..1bc4ee9443e754456e2ccdbf299b8c9158a6fe6e 100644 --- a/src/vs/editor/common/viewLayout/viewLineRenderer.ts +++ b/src/vs/editor/common/viewLayout/viewLineRenderer.ts @@ -166,12 +166,10 @@ export class RenderLineOutput { readonly characterMapping: CharacterMapping; readonly output: string; - readonly isWhitespaceOnly: boolean; - constructor(characterMapping: CharacterMapping, output: string, isWhitespaceOnly: boolean) { + constructor(characterMapping: CharacterMapping, output: string) { this.characterMapping = characterMapping; this.output = output; - this.isWhitespaceOnly = isWhitespaceOnly; } } @@ -189,8 +187,7 @@ export function renderLine(input: RenderLineInput): RenderLineOutput { return new RenderLineOutput( new CharacterMapping(0), // This is basically for IE's hit test to work - ' ', - true + ' ' ); } @@ -198,7 +195,10 @@ export function renderLine(input: RenderLineInput): RenderLineOutput { throw new Error('Cannot render non empty line without line parts!'); } - return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts, renderWhitespace, renderControlCharacters, charBreakIndex); + let viewParts = toViewParts(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts, renderWhitespace, renderControlCharacters, charBreakIndex); + return renderViewParts(viewParts); + + // return renderLineActual(lineText, lineTextLength, tabSize, spaceWidth, actualLineParts, renderWhitespace, renderControlCharacters, charBreakIndex); } function isWhitespace(type: string): boolean { @@ -214,20 +214,40 @@ function controlCharacterToPrintable(characterCode: number): string { return String.fromCharCode(_controlCharacterSequenceConversionStart + characterCode); } -function renderLineActual(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): RenderLineOutput { +class ViewPart2 { + public readonly className: string; + public readonly htmlContent: string; + public readonly forceWidth: number; + + constructor(className: string, htmlContent: string, forceWidth: number) { + this.className = className; + this.htmlContent = htmlContent; + this.forceWidth = forceWidth; + } +} + +class ViewParts2 { + public readonly parts: ViewPart2[]; + public readonly characterMapping: CharacterMapping; + + constructor(parts: ViewPart2[], characterMapping: CharacterMapping) { + this.parts = parts; + this.characterMapping = characterMapping; + } +} + +function toViewParts(lineText: string, lineTextLength: number, tabSize: number, spaceWidth: number, actualLineParts: ViewLineToken[], renderWhitespace: 'none' | 'boundary' | 'all', renderControlCharacters: boolean, charBreakIndex: number): ViewParts2 { lineTextLength = +lineTextLength; tabSize = +tabSize; charBreakIndex = +charBreakIndex; let charIndex = 0; - let out = ''; let charOffsetInPart = 0; let tabsCharDelta = 0; - let isWhitespaceOnly = /^\s*$/.test(lineText); let characterMapping = new CharacterMapping(Math.min(lineTextLength, charBreakIndex) + 1); - out += ''; + let result: ViewPart2[] = [], resultLen = 0; for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) { let part = actualLineParts[partIndex]; @@ -271,18 +291,14 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num charOffsetInPart++; if (charIndex >= charBreakIndex) { - out += `${partContent}…`; + result[resultLen++] = new ViewPart2(part.type, partContent + '…', 0); characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); + return new ViewParts2(result, characterMapping); } } - out += `${partContent}`; + result[resultLen++] = new ViewPart2(part.type, partContent, (spaceWidth * partContentCnt)); } else { - out += ``; + let partContent = ''; for (; charIndex < toCharIndex; charIndex++) { characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); @@ -294,75 +310,81 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num tabsCharDelta += insertSpacesCount - 1; charOffsetInPart += insertSpacesCount - 1; while (insertSpacesCount > 0) { - out += ' '; + partContent += ' '; insertSpacesCount--; } break; case CharCode.Space: - out += ' '; + partContent += ' '; break; case CharCode.LessThan: - out += '<'; + partContent += '<'; break; case CharCode.GreaterThan: - out += '>'; + partContent += '>'; break; case CharCode.Ampersand: - out += '&'; + partContent += '&'; break; case CharCode.Null: - out += '�'; + partContent += '�'; break; case CharCode.UTF8_BOM: case CharCode.LINE_SEPARATOR_2028: - out += '\ufffd'; + partContent += '\ufffd'; break; case CharCode.CarriageReturn: // zero width space, because carriage return would introduce a line break - out += '​'; + partContent += '​'; break; default: if (renderControlCharacters && isControlCharacter(charCode)) { - out += controlCharacterToPrintable(charCode); + partContent += controlCharacterToPrintable(charCode); } else { - out += lineText.charAt(charIndex); + partContent += lineText.charAt(charIndex); } } charOffsetInPart++; if (charIndex >= charBreakIndex) { - out += '…'; + result[resultLen++] = new ViewPart2(part.type, partContent + '…', 0); characterMapping.setPartData(charIndex, partIndex, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); + return new ViewParts2(result, characterMapping); } } - - out += ''; + result[resultLen++] = new ViewPart2(part.type, partContent, 0); } - } - 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 characterMapping.setPartData(lineTextLength, actualLineParts.length - 1, charOffsetInPart); - return new RenderLineOutput( - characterMapping, - out, - isWhitespaceOnly - ); + return new ViewParts2(result, characterMapping); +} + +function renderViewParts(viewParts: ViewParts2): RenderLineOutput { + const parts = viewParts.parts; + + let out = ''; + for (let i = 0, len = parts.length; i < len; i++) { + let part = parts[i]; + if (part.forceWidth) { + out += `${part.htmlContent}`; + } else { + out += `${part.htmlContent}`; + } + } + out += ''; + + return new RenderLineOutput(viewParts.characterMapping, out); }