提交 1d25a68c 编写于 作者: A Alex Dima

Introduce CharacterMapping that can transform char offsets to rendered view parts

上级 48c1c577
......@@ -7,15 +7,14 @@
import * as browser from 'vs/base/browser/browser';
import { FastDomNode, createFastDomNode } from 'vs/base/browser/styleMutator';
import { IConfigurationChangedEvent } from 'vs/editor/common/editorCommon';
import { createLineParts, getColumnOfLinePartOffset } from 'vs/editor/common/viewLayout/viewLineParts';
import { renderLine, RenderLineInput, RenderLineOutput } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { createLineParts } from 'vs/editor/common/viewLayout/viewLineParts';
import { renderLine, RenderLineInput, RenderLineOutput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { ClassNames } from 'vs/editor/browser/editorBrowser';
import { IVisibleLineData } from 'vs/editor/browser/view/viewLayer';
import { RangeUtil } from 'vs/editor/browser/viewParts/lines/rangeUtil';
import { ViewContext } from 'vs/editor/common/view/viewContext';
import { HorizontalRange } from 'vs/editor/common/view/renderingContext';
import { InlineDecoration } from 'vs/editor/common/viewModel/viewModel';
import { LineParts } from 'vs/editor/common/core/lineParts';
export class ViewLine implements IVisibleLineData {
......@@ -175,8 +174,7 @@ class RenderedViewLine {
public readonly input: RenderLineInput;
public readonly html: string;
protected readonly _charOffsetInPart: number[];
private readonly _lastRenderedPartIndex: number;
protected readonly _characterMapping: CharacterMapping;
private readonly _isWhitespaceOnly: boolean;
private _cachedWidth: number;
......@@ -189,8 +187,7 @@ class RenderedViewLine {
this.domNode = domNode;
this.input = renderLineInput;
this.html = renderLineOutput.output;
this._charOffsetInPart = renderLineOutput.charOffsetInPart;
this._lastRenderedPartIndex = renderLineOutput.lastRenderedPartIndex;
this._characterMapping = renderLineOutput.characterMapping;
this._isWhitespaceOnly = renderLineOutput.isWhitespaceOnly;
this._cachedWidth = -1;
......@@ -291,18 +288,19 @@ class RenderedViewLine {
private _actualReadPixelOffset(column: number, clientRectDeltaLeft: number, endNode: HTMLElement): number {
if (this._charOffsetInPart.length === 0) {
if (this._characterMapping.length === 0) {
// This line is empty
return 0;
}
if (column === this._charOffsetInPart.length && this._isWhitespaceOnly) {
if (column === this._characterMapping.length && this._isWhitespaceOnly) {
// This branch helps in the case of whitespace only lines which have a width set
return this.getWidth();
}
let partIndex = findIndexInArrayWithMax(this.input.lineParts, column - 1, this._lastRenderedPartIndex);
let charOffsetInPart = this._charOffsetInPart[column - 1];
let partData = this._characterMapping.charOffsetToPartData(column - 1);
let partIndex = CharacterMapping.getPartIndex(partData);
let charOffsetInPart = CharacterMapping.getCharIndex(partData);
let r = RangeUtil.readHorizontalRanges(this._getReadingTarget(), partIndex, charOffsetInPart, partIndex, charOffsetInPart, clientRectDeltaLeft, endNode);
if (!r || r.length === 0) {
......@@ -313,16 +311,19 @@ class RenderedViewLine {
private _readRawVisibleRangesForRange(startColumn: number, endColumn: number, clientRectDeltaLeft: number, endNode: HTMLElement): HorizontalRange[] {
if (startColumn === 1 && endColumn === this._charOffsetInPart.length) {
if (startColumn === 1 && endColumn === this._characterMapping.length) {
// This branch helps IE with bidi text & gives a performance boost to other browsers when reading visible ranges for an entire line
return [new HorizontalRange(0, this.getWidth())];
}
let startPartIndex = findIndexInArrayWithMax(this.input.lineParts, startColumn - 1, this._lastRenderedPartIndex);
let startCharOffsetInPart = this._charOffsetInPart[startColumn - 1];
let endPartIndex = findIndexInArrayWithMax(this.input.lineParts, endColumn - 1, this._lastRenderedPartIndex);
let endCharOffsetInPart = this._charOffsetInPart[endColumn - 1];
let startPartData = this._characterMapping.charOffsetToPartData(startColumn - 1);
let startPartIndex = CharacterMapping.getPartIndex(startPartData);
let startCharOffsetInPart = CharacterMapping.getCharIndex(startPartData);
let endPartData = this._characterMapping.charOffsetToPartData(endColumn - 1);
let endPartIndex = CharacterMapping.getPartIndex(endPartData);
let endCharOffsetInPart = CharacterMapping.getCharIndex(endPartData);
return RangeUtil.readHorizontalRanges(this._getReadingTarget(), startPartIndex, startCharOffsetInPart, endPartIndex, endCharOffsetInPart, clientRectDeltaLeft, endNode);
}
......@@ -338,17 +339,9 @@ class RenderedViewLine {
spanNode = <HTMLElement>spanNode.previousSibling;
spanIndex++;
}
let lineParts = this.input.lineParts.parts;
return getColumnOfLinePartOffset(
this.input.stopRenderingLineAfter,
lineParts,
this.input.lineParts.maxLineColumn,
this._charOffsetInPart,
spanIndex,
spanNodeTextContentLength,
offset
);
let charOffset = this._characterMapping.partDataToCharOffset(spanIndex, spanNodeTextContentLength, offset);
return charOffset + 1;
}
}
......@@ -356,7 +349,7 @@ class WebKitRenderedViewLine extends RenderedViewLine {
protected _readVisibleRangesForRange(startColumn: number, endColumn: number, clientRectDeltaLeft: number, endNode: HTMLElement): HorizontalRange[] {
let output = super._readVisibleRangesForRange(startColumn, endColumn, clientRectDeltaLeft, endNode);
if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._charOffsetInPart.length)) {
if (!output || output.length === 0 || startColumn === endColumn || (startColumn === 1 && endColumn === this._characterMapping.length)) {
return output;
}
......@@ -383,11 +376,6 @@ class WebKitRenderedViewLine extends RenderedViewLine {
}
}
function findIndexInArrayWithMax(lineParts: LineParts, desiredIndex: number, maxResult: number): number {
let r = lineParts.findIndexOfOffset(desiredIndex);
return r <= maxResult ? r : maxResult;
}
const createRenderedLine: (domNode: FastDomNode, renderLineInput: RenderLineInput, modelContainsRTL: boolean, renderLineOutput: RenderLineOutput) => RenderedViewLine = (function () {
if (browser.isWebKit) {
return createWebKitRenderedLine;
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
export namespace Arrays {
/**
* Given a sorted array of natural number segments, find the segment containing a natural number.
* For example, the segments [0, 5), [5, 9), [9, infinity) will be represented in the following manner:
* [{ startIndex: 0 }, { startIndex: 5 }, { startIndex: 9 }]
* Searching for 0, 1, 2, 3 or 4 will return 0.
* Searching for 5, 6, 7 or 8 will return 1.
* Searching for 9, 10, 11, ... will return 2.
* @param arr A sorted array representing natural number segments
* @param desiredIndex The search
* @return The index of the containing segment in the array.
*/
export function findIndexInSegmentsArray(arr: { readonly startIndex: number; }[], desiredIndex: number): number {
let low = 0;
let high = arr.length - 1;
if (high <= 0) {
return 0;
}
while (low < high) {
let mid = low + Math.ceil((high - low) / 2);
if (arr[mid].startIndex > desiredIndex) {
high = mid - 1;
} else {
low = mid;
}
}
return low;
}
}
......@@ -4,7 +4,6 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Arrays } from 'vs/editor/common/core/arrays';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
export class LineParts {
......@@ -24,8 +23,4 @@ export class LineParts {
&& ViewLineToken.equalsArray(this.parts, other.parts)
);
}
public findIndexOfOffset(offset: number): number {
return Arrays.findIndexInSegmentsArray(this.parts, offset);
}
}
......@@ -4,8 +4,6 @@
*--------------------------------------------------------------------------------------------*/
'use strict';
import { Arrays } from 'vs/editor/common/core/arrays';
/**
* A token on a line.
*/
......@@ -27,10 +25,6 @@ export class ViewLineToken {
);
}
public static findIndexInSegmentsArray(arr: ViewLineToken[], desiredIndex: number): number {
return Arrays.findIndexInSegmentsArray(arr, desiredIndex);
}
public static equalsArray(a: ViewLineToken[], b: ViewLineToken[]): boolean {
let aLen = a.length;
let bLen = b.length;
......@@ -78,8 +72,4 @@ export class ViewLineTokens {
&& ViewLineToken.equalsArray(this._lineTokens, other._lineTokens)
);
}
public findIndexOfOffset(offset: number): number {
return ViewLineToken.findIndexInSegmentsArray(this._lineTokens, offset);
}
}
......@@ -38,56 +38,6 @@ export function createLineParts(lineNumber: number, minLineColumn: number, lineC
}
}
export function getColumnOfLinePartOffset(stopRenderingLineAfter: number, lineParts: ViewLineToken[], lineMaxColumn: number, charOffsetInPart: number[], partIndex: number, partLength: number, offset: number): number {
if (partIndex >= lineParts.length) {
return stopRenderingLineAfter;
}
if (offset === 0) {
return lineParts[partIndex].startIndex + 1;
}
if (offset === partLength) {
return (partIndex + 1 < lineParts.length ? lineParts[partIndex + 1].startIndex + 1 : lineMaxColumn);
}
let originalMin = lineParts[partIndex].startIndex;
let originalMax = (partIndex + 1 < lineParts.length ? lineParts[partIndex + 1].startIndex : lineMaxColumn - 1);
let min = originalMin;
let max = originalMax;
// invariant: offsetOf(min) <= offset <= offsetOf(max)
while (min + 1 < max) {
let mid = ((min + max) >>> 1);
let midOffset = charOffsetInPart[mid];
if (midOffset === offset) {
return mid + 1;
} else if (midOffset > offset) {
max = mid;
} else {
min = mid;
}
}
if (min === max) {
return min + 1;
}
let minOffset = charOffsetInPart[min];
let maxOffset = (max < originalMax ? charOffsetInPart[max] : partLength);
let distanceToMin = offset - minOffset;
let distanceToMax = maxOffset - offset;
if (distanceToMin <= distanceToMax) {
return min + 1;
} else {
return max + 1;
}
}
function trimEmptyTrailingPart(parts: ViewLineToken[], lineContent: string): ViewLineToken[] {
if (parts.length <= 1) {
return parts;
......
......@@ -50,17 +50,126 @@ export class RenderLineInput {
}
}
export const enum CharacterMappingConstants {
PART_INDEX_MASK = 0b11111111111111110000000000000000,
CHAR_INDEX_MASK = 0b00000000000000001111111111111111,
CHAR_INDEX_OFFSET = 0,
PART_INDEX_OFFSET = 16
}
/**
* Provides a both direction mapping between a line's character and its rendered position.
*/
export class CharacterMapping {
public static getPartIndex(partData: number): number {
return (partData & CharacterMappingConstants.PART_INDEX_MASK) >>> CharacterMappingConstants.PART_INDEX_OFFSET;
}
public static getCharIndex(partData: number): number {
return (partData & CharacterMappingConstants.CHAR_INDEX_MASK) >>> CharacterMappingConstants.CHAR_INDEX_OFFSET;
}
private readonly _data: Uint32Array;
public readonly length: number;
constructor(length: number) {
this.length = length;
this._data = new Uint32Array(this.length);
}
public setPartData(charOffset: number, partIndex: number, charIndex: number): void {
let partData = (
(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
| (charIndex << CharacterMappingConstants.CHAR_INDEX_OFFSET)
) >>> 0;
this._data[charOffset] = partData;
}
public charOffsetToPartData(charOffset: number): number {
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];
}
public partDataToCharOffset(partIndex: number, partLength: number, charIndex: number): number {
if (this.length === 0) {
return 0;
}
let searchEntry = (
(partIndex << CharacterMappingConstants.PART_INDEX_OFFSET)
| (charIndex << CharacterMappingConstants.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: number;
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 {
_renderLineOutputBrand: void;
readonly charOffsetInPart: number[];
readonly lastRenderedPartIndex: number;
readonly characterMapping: CharacterMapping;
readonly output: string;
readonly isWhitespaceOnly: boolean;
constructor(charOffsetInPart: number[], lastRenderedPartIndex: number, output: string, isWhitespaceOnly: boolean) {
this.charOffsetInPart = charOffsetInPart;
this.lastRenderedPartIndex = lastRenderedPartIndex;
constructor(characterMapping: CharacterMapping, output: string, isWhitespaceOnly: boolean) {
this.characterMapping = characterMapping;
this.output = output;
this.isWhitespaceOnly = isWhitespaceOnly;
}
......@@ -78,8 +187,7 @@ export function renderLine(input: RenderLineInput): RenderLineOutput {
if (lineTextLength === 0) {
return new RenderLineOutput(
[],
0,
new CharacterMapping(0),
// This is basically for IE's hit test to work
'<span><span>&nbsp;</span></span>',
true
......@@ -113,11 +221,12 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
let charIndex = 0;
let out = '';
let charOffsetInPartArr: number[] = [];
let charOffsetInPart = 0;
let tabsCharDelta = 0;
let isWhitespaceOnly = /^\s*$/.test(lineText);
let characterMapping = new CharacterMapping(Math.min(lineTextLength, charBreakIndex) + 1);
out += '<span>';
for (let partIndex = 0, partIndexLen = actualLineParts.length; partIndex < partIndexLen; partIndex++) {
let part = actualLineParts[partIndex];
......@@ -136,7 +245,7 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
let partContentCnt = 0;
let partContent = '';
for (; charIndex < toCharIndex; charIndex++) {
charOffsetInPartArr[charIndex] = charOffsetInPart;
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
let charCode = lineText.charCodeAt(charIndex);
if (charCode === CharCode.Tab) {
......@@ -163,10 +272,9 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
if (charIndex >= charBreakIndex) {
out += `<span class="${part.type}" style="width:${(spaceWidth * partContentCnt)}px">${partContent}&hellip;</span></span>`;
charOffsetInPartArr[charIndex] = charOffsetInPart;
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
return new RenderLineOutput(
charOffsetInPartArr,
partIndex,
characterMapping,
out,
isWhitespaceOnly
);
......@@ -177,7 +285,7 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
out += `<span class="${part.type}">`;
for (; charIndex < toCharIndex; charIndex++) {
charOffsetInPartArr[charIndex] = charOffsetInPart;
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
let charCode = lineText.charCodeAt(charIndex);
switch (charCode) {
......@@ -233,10 +341,9 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
if (charIndex >= charBreakIndex) {
out += '&hellip;</span></span>';
charOffsetInPartArr[charIndex] = charOffsetInPart;
characterMapping.setPartData(charIndex, partIndex, charOffsetInPart);
return new RenderLineOutput(
charOffsetInPartArr,
partIndex,
characterMapping,
out,
isWhitespaceOnly
);
......@@ -251,11 +358,10 @@ function renderLineActual(lineText: string, lineTextLength: number, tabSize: num
// 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);
characterMapping.setPartData(lineTextLength, actualLineParts.length - 1, charOffsetInPart);
return new RenderLineOutput(
charOffsetInPartArr,
actualLineParts.length - 1,
characterMapping,
out,
isWhitespaceOnly
);
......
......@@ -5,7 +5,7 @@
'use strict';
import * as assert from 'assert';
import { DecorationSegment, LineDecorationsNormalizer, getColumnOfLinePartOffset, createLineParts } from 'vs/editor/common/viewLayout/viewLineParts';
import { DecorationSegment, LineDecorationsNormalizer, createLineParts } from 'vs/editor/common/viewLayout/viewLineParts';
import { Range } from 'vs/editor/common/core/range';
import { RenderLineInput, renderLine } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { ViewLineToken, ViewLineTokens } from 'vs/editor/common/core/viewLineToken';
......@@ -342,7 +342,8 @@ suite('Editor ViewLayout - ViewLineParts', () => {
let renderLineOutput = renderLine(new RenderLineInput(lineContent, tabSize, 10, -1, 'none', false, new LineParts(parts, lineContent.length + 1)));
return (partIndex: number, partLength: number, offset: number, expected: number) => {
let actual = getColumnOfLinePartOffset(-1, parts, lineContent.length + 1, renderLineOutput.charOffsetInPart, partIndex, partLength, offset);
let charOffset = renderLineOutput.characterMapping.partDataToCharOffset(partIndex, partLength, offset);
let actual = charOffset + 1;
assert.equal(actual, expected, 'getColumnOfLinePartOffset for ' + partIndex + ' @ ' + offset);
};
}
......
......@@ -5,7 +5,7 @@
'use strict';
import * as assert from 'assert';
import { renderLine, RenderLineInput } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { renderLine, RenderLineInput, CharacterMapping } from 'vs/editor/common/viewLayout/viewLineRenderer';
import { ViewLineToken } from 'vs/editor/common/core/viewLineToken';
import { CharCode } from 'vs/base/common/charCode';
import { LineParts } from 'vs/editor/common/core/lineParts';
......@@ -16,7 +16,7 @@ suite('viewLineRenderer.renderLine', () => {
return new ViewLineToken(startIndex, type);
}
function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[]): void {
function assertCharacterReplacement(lineContent: string, tabSize: number, expected: string, expectedCharOffsetInPart: number[][]): void {
let _actual = renderLine(new RenderLineInput(
lineContent,
tabSize,
......@@ -28,37 +28,37 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.equal(_actual.output, '<span><span class="">' + expected + '</span></span>');
assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart);
assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart);
}
test('replaces spaces', () => {
assertCharacterReplacement(' ', 4, '&nbsp;', [0, 1]);
assertCharacterReplacement(' ', 4, '&nbsp;&nbsp;', [0, 1, 2]);
assertCharacterReplacement('a b', 4, 'a&nbsp;&nbsp;b', [0, 1, 2, 3, 4]);
assertCharacterReplacement(' ', 4, '&nbsp;', [[0, 1]]);
assertCharacterReplacement(' ', 4, '&nbsp;&nbsp;', [[0, 1, 2]]);
assertCharacterReplacement('a b', 4, 'a&nbsp;&nbsp;b', [[0, 1, 2, 3, 4]]);
});
test('escapes HTML markup', () => {
assertCharacterReplacement('a<b', 4, 'a&lt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a>b', 4, 'a&gt;b', [0, 1, 2, 3]);
assertCharacterReplacement('a&b', 4, 'a&amp;b', [0, 1, 2, 3]);
assertCharacterReplacement('a<b', 4, 'a&lt;b', [[0, 1, 2, 3]]);
assertCharacterReplacement('a>b', 4, 'a&gt;b', [[0, 1, 2, 3]]);
assertCharacterReplacement('a&b', 4, 'a&amp;b', [[0, 1, 2, 3]]);
});
test('replaces some bad characters', () => {
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [0, 1, 2, 3]);
assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [0, 1, 2, 3]);
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [0, 1, 2, 3]);
assertCharacterReplacement('a\rb', 4, 'a&#8203b', [0, 1, 2, 3]);
assertCharacterReplacement('a\0b', 4, 'a&#00;b', [[0, 1, 2, 3]]);
assertCharacterReplacement('a' + String.fromCharCode(CharCode.UTF8_BOM) + 'b', 4, 'a\ufffdb', [[0, 1, 2, 3]]);
assertCharacterReplacement('a\u2028b', 4, 'a\ufffdb', [[0, 1, 2, 3]]);
assertCharacterReplacement('a\rb', 4, 'a&#8203b', [[0, 1, 2, 3]]);
});
test('handles tabs', () => {
assertCharacterReplacement('\t', 4, '&nbsp;&nbsp;&nbsp;&nbsp;', [0, 4]);
assertCharacterReplacement('x\t', 4, 'x&nbsp;&nbsp;&nbsp;', [0, 1, 4]);
assertCharacterReplacement('xx\t', 4, 'xx&nbsp;&nbsp;', [0, 1, 2, 4]);
assertCharacterReplacement('xxx\t', 4, 'xxx&nbsp;', [0, 1, 2, 3, 4]);
assertCharacterReplacement('xxxx\t', 4, 'xxxx&nbsp;&nbsp;&nbsp;&nbsp;', [0, 1, 2, 3, 4, 8]);
assertCharacterReplacement('\t', 4, '&nbsp;&nbsp;&nbsp;&nbsp;', [[0, 4]]);
assertCharacterReplacement('x\t', 4, 'x&nbsp;&nbsp;&nbsp;', [[0, 1, 4]]);
assertCharacterReplacement('xx\t', 4, 'xx&nbsp;&nbsp;', [[0, 1, 2, 4]]);
assertCharacterReplacement('xxx\t', 4, 'xxx&nbsp;', [[0, 1, 2, 3, 4]]);
assertCharacterReplacement('xxxx\t', 4, 'xxxx&nbsp;&nbsp;&nbsp;&nbsp;', [[0, 1, 2, 3, 4, 8]]);
});
function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[]): void {
function assertParts(lineContent: string, tabSize: number, parts: ViewLineToken[], expected: string, expectedCharOffsetInPart: number[][]): void {
let _actual = renderLine(new RenderLineInput(
lineContent,
tabSize,
......@@ -70,7 +70,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.equal(_actual.output, '<span>' + expected + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedCharOffsetInPart);
assertCharacterMapping(_actual.characterMapping, expectedCharOffsetInPart);
}
test('empty line', () => {
......@@ -78,15 +78,15 @@ suite('viewLineRenderer.renderLine', () => {
});
test('uses part type', () => {
assertParts('x', 4, [createPart(0, 'y')], '<span class="y">x</span>', [0, 1]);
assertParts('x', 4, [createPart(0, 'aAbBzZ0123456789-cC')], '<span class="aAbBzZ0123456789-cC">x</span>', [0, 1]);
assertParts('x', 4, [createPart(0, ' ')], '<span class=" ">x</span>', [0, 1]);
assertParts('x', 4, [createPart(0, 'y')], '<span class="y">x</span>', [[0, 1]]);
assertParts('x', 4, [createPart(0, 'aAbBzZ0123456789-cC')], '<span class="aAbBzZ0123456789-cC">x</span>', [[0, 1]]);
assertParts('x', 4, [createPart(0, ' ')], '<span class=" ">x</span>', [[0, 1]]);
});
test('two parts', () => {
assertParts('xy', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="a">x</span><span class="b">y</span>', [0, 0, 1]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="a">x</span><span class="b">yz</span>', [0, 0, 1, 2]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(2, 'b')], '<span class="a">xy</span><span class="b">z</span>', [0, 1, 0, 1]);
assertParts('xy', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="a">x</span><span class="b">y</span>', [[0], [0, 1]]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(1, 'b')], '<span class="a">x</span><span class="b">yz</span>', [[0], [0, 1, 2]]);
assertParts('xyz', 4, [createPart(0, 'a'), createPart(2, 'b')], '<span class="a">xy</span><span class="b">z</span>', [[0, 1], [0, 1]]);
});
test('overflow', () => {
......@@ -126,13 +126,13 @@ suite('viewLineRenderer.renderLine', () => {
].join('');
assert.equal(_actual.output, '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, [
0,
0,
0,
0,
0,
1
assertCharacterMapping(_actual.characterMapping, [
[0],
[0],
[0],
[0],
[0],
[1],
]);
});
......@@ -180,7 +180,6 @@ suite('viewLineRenderer.renderLine', () => {
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14],
[0, 1, 2, 3, 4, 5],
];
let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []);
let _actual = renderLine(new RenderLineInput(
lineText,
......@@ -193,7 +192,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.equal(_actual.output, '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedOffsets);
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr);
});
test('issue #2255: Weird line rendering part 1', () => {
......@@ -235,7 +234,6 @@ suite('viewLineRenderer.renderLine', () => {
[0], // 1 char
[0, 1] // 2 chars
];
let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []);
let _actual = renderLine(new RenderLineInput(
lineText,
......@@ -248,7 +246,7 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.equal(_actual.output, '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedOffsets);
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr);
});
test('issue #2255: Weird line rendering part 2', () => {
......@@ -290,7 +288,6 @@ suite('viewLineRenderer.renderLine', () => {
[0], // 1 char
[0, 1] // 2 chars
];
let expectedOffsets = expectedOffsetsArr.reduce((prev, curr) => prev.concat(curr), []);
let _actual = renderLine(new RenderLineInput(
lineText,
......@@ -303,8 +300,38 @@ suite('viewLineRenderer.renderLine', () => {
));
assert.equal(_actual.output, '<span>' + expectedOutput + '</span>');
assert.deepEqual(_actual.charOffsetInPart, expectedOffsets);
assertCharacterMapping(_actual.characterMapping, expectedOffsetsArr);
});
function assertCharacterMapping(actual: CharacterMapping, expected: number[][]): void {
let charOffset = 0;
for (let partIndex = 0; partIndex < expected.length; partIndex++) {
let part = expected[partIndex];
for (let i = 0; i < part.length; i++) {
let charIndex = part[i];
let _actualPartData = actual.charOffsetToPartData(charOffset);
let actualPartIndex = CharacterMapping.getPartIndex(_actualPartData);
let actualCharIndex = CharacterMapping.getCharIndex(_actualPartData);
assert.deepEqual(
{ partIndex: actualPartIndex, charIndex: actualCharIndex },
{ partIndex: partIndex, charIndex: charIndex },
`character mapping for offset ${charOffset}`
);
let actualOffset = actual.partDataToCharOffset(partIndex, part[part.length - 1] + 1, charIndex);
assert.equal(
actualOffset,
charOffset,
`character mapping for part ${partIndex}, ${charIndex}`
);
charOffset++;
}
}
assert.equal(actual.length, charOffset);
}
});
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册