Move char joiner registry to browser layer
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { ICharacterJoinerRegistry } from 'browser/renderer/Types';
|
||||
import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry';
|
||||
import { BufferLine } from 'common/buffer/BufferLine';
|
||||
import { IBufferLine } from 'common/Types';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { MockBufferService } from 'common/TestUtils.test';
|
||||
|
||||
describe('CharacterJoinerRegistry', () => {
|
||||
let registry: ICharacterJoinerRegistry;
|
||||
|
||||
beforeEach(() => {
|
||||
const bufferService = new MockBufferService(16, 10);
|
||||
const lines = bufferService.buffer.lines;
|
||||
lines.set(0, lineData([['a -> b -> c -> d']]));
|
||||
lines.set(1, lineData([['a -> b => c -> d']]));
|
||||
lines.set(2, lineData([['a -> b -', 0xFFFFFFFF], ['> c -> d', 0]]));
|
||||
|
||||
lines.set(3, lineData([['no joined ranges']]));
|
||||
lines.set(4, new BufferLine(0));
|
||||
lines.set(5, lineData([['a', 0x11111111], [' -> b -> c -> '], ['d', 0x22222222]]));
|
||||
const line6 = lineData([['wi']]);
|
||||
line6.resize(line6.length + 1, CellData.fromCharData([0, '¥', 2, '¥'.charCodeAt(0)]));
|
||||
line6.resize(line6.length + 1, CellData.fromCharData([0, '', 0, 0]));
|
||||
let sub = lineData([['deemo']]);
|
||||
let oldSize = line6.length;
|
||||
line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0]));
|
||||
for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));
|
||||
line6.resize(line6.length + 1, CellData.fromCharData([0, '\xf0\x9f\x98\x81', 1, 128513]));
|
||||
line6.resize(line6.length + 1, CellData.fromCharData([0, ' ', 1, ' '.charCodeAt(0)]));
|
||||
sub = lineData([['jiabc']]);
|
||||
oldSize = line6.length;
|
||||
line6.resize(oldSize + sub.length, CellData.fromCharData([0, '', 0, 0]));
|
||||
for (let i = 0; i < sub.length; ++i) line6.setCell(i + oldSize, sub.loadCell(i, new CellData()));
|
||||
lines.set(6, line6);
|
||||
|
||||
registry = new CharacterJoinerRegistry(bufferService);
|
||||
});
|
||||
|
||||
it('has no joiners upon creation', () => {
|
||||
assert.deepEqual(registry.getJoinedCharacters(0), []);
|
||||
});
|
||||
|
||||
it('returns ranges matched by the registered joiners', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[2, 4], [7, 9], [12, 14]]
|
||||
);
|
||||
});
|
||||
|
||||
it('processes the input using all provided joiners', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(1),
|
||||
[[2, 4], [12, 14]]
|
||||
);
|
||||
|
||||
registry.registerCharacterJoiner(substringJoiner('=>'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(1),
|
||||
[[2, 4], [7, 9], [12, 14]]
|
||||
);
|
||||
});
|
||||
|
||||
it('removes deregistered joiners from future calls', () => {
|
||||
const joiner1 = registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
const joiner2 = registry.registerCharacterJoiner(substringJoiner('=>'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(1),
|
||||
[[2, 4], [7, 9], [12, 14]]
|
||||
);
|
||||
|
||||
registry.deregisterCharacterJoiner(joiner1);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(1),
|
||||
[[7, 9]]
|
||||
);
|
||||
|
||||
registry.deregisterCharacterJoiner(joiner2);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(1),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('doesn\'t process joins on differently-styled characters', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(2),
|
||||
[[2, 4], [12, 14]]
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty list of ranges if there is nothing to be joined', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(3),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('returns an empty list of ranges if the line is empty', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(4),
|
||||
[]
|
||||
);
|
||||
});
|
||||
|
||||
it('returns false when trying to deregister a joiner that does not exist', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
assert.deepEqual(registry.deregisterCharacterJoiner(123), false);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[2, 4], [7, 9], [12, 14]]
|
||||
);
|
||||
});
|
||||
|
||||
it('doesn\'t process same-styled ranges that only have one character', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('a'));
|
||||
registry.registerCharacterJoiner(substringJoiner('b'));
|
||||
registry.registerCharacterJoiner(substringJoiner('d'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(5),
|
||||
[[5, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles ranges that extend all the way to the end of the line', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('-> d'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(2),
|
||||
[[12, 16]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles adjacent ranges', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('->'));
|
||||
registry.registerCharacterJoiner(substringJoiner('> c '));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(2),
|
||||
[[2, 4], [8, 12], [12, 14]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles fullwidth characters in the middle of ranges', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('wi¥de'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(6),
|
||||
[[0, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles fullwidth characters at the end of ranges', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('wi¥'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(6),
|
||||
[[0, 4]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles emojis in the middle of ranges', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 ji'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(6),
|
||||
[[6, 13]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles emojis at the end of ranges', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('emo\xf0\x9f\x98\x81 '));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(6),
|
||||
[[6, 11]]
|
||||
);
|
||||
});
|
||||
|
||||
it('handles ranges after wide and emoji characters', () => {
|
||||
registry.registerCharacterJoiner(substringJoiner('abc'));
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(6),
|
||||
[[13, 16]]
|
||||
);
|
||||
});
|
||||
|
||||
describe('range merging', () => {
|
||||
it('inserts a new range before the existing ones', () => {
|
||||
registry.registerCharacterJoiner(() => [[1, 2], [2, 3]]);
|
||||
registry.registerCharacterJoiner(() => [[0, 1]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 1], [1, 2], [2, 3]]
|
||||
);
|
||||
});
|
||||
|
||||
it('inserts in between two ranges', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[2, 4]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 2], [2, 4], [4, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('inserts after the last range', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[6, 8]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 2], [4, 6], [6, 8]]
|
||||
);
|
||||
});
|
||||
|
||||
it('extends the beginning of a range', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[3, 5]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 2], [3, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('extends the end of a range', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[1, 4]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 4], [4, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('extends the last range', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[5, 7]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 2], [4, 7]]
|
||||
);
|
||||
});
|
||||
|
||||
it('connects two ranges', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6]]);
|
||||
registry.registerCharacterJoiner(() => [[1, 5]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 6]]
|
||||
);
|
||||
});
|
||||
|
||||
it('connects more than two ranges', () => {
|
||||
registry.registerCharacterJoiner(() => [[0, 2], [4, 6], [8, 10], [12, 14]]);
|
||||
registry.registerCharacterJoiner(() => [[1, 10]]);
|
||||
assert.deepEqual(
|
||||
registry.getJoinedCharacters(0),
|
||||
[[0, 10], [12, 14]]
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
type IPartialLineData = ([string] | [string, number]);
|
||||
|
||||
function lineData(data: IPartialLineData[]): IBufferLine {
|
||||
const tline = new BufferLine(0);
|
||||
for (let i = 0; i < data.length; ++i) {
|
||||
const line = data[i][0];
|
||||
const attr = <number>(data[i][1] || 0);
|
||||
const offset = tline.length;
|
||||
tline.resize(tline.length + line.split('').length, CellData.fromCharData([0, '', 0, 0]));
|
||||
line.split('').map((char, idx) => tline.setCell(idx + offset, CellData.fromCharData([attr, char, 1, char.charCodeAt(0)])));
|
||||
}
|
||||
return tline;
|
||||
}
|
||||
|
||||
function substringJoiner(substring: string): (sequence: string) => [number, number][] {
|
||||
return (sequence: string): [number, number][] => {
|
||||
const ranges: [number, number][] = [];
|
||||
let searchIndex = 0;
|
||||
let matchIndex = -1;
|
||||
|
||||
while ((matchIndex = sequence.indexOf(substring, searchIndex)) !== -1) {
|
||||
const matchEndIndex = matchIndex + substring.length;
|
||||
searchIndex = matchEndIndex;
|
||||
ranges.push([matchIndex, matchEndIndex]);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,327 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IBufferLine, ICellData, CharData } from 'common/Types';
|
||||
import { ICharacterJoinerRegistry, ICharacterJoiner } from 'browser/renderer/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
|
||||
export class JoinedCellData extends AttributeData implements ICellData {
|
||||
private _width: number;
|
||||
// .content carries no meaning for joined CellData, simply nullify it
|
||||
// thus we have to overload all other .content accessors
|
||||
public content: number = 0;
|
||||
public fg: number;
|
||||
public bg: number;
|
||||
public combinedData: string = '';
|
||||
|
||||
constructor(firstCell: ICellData, chars: string, width: number) {
|
||||
super();
|
||||
this.fg = firstCell.fg;
|
||||
this.bg = firstCell.bg;
|
||||
this.combinedData = chars;
|
||||
this._width = width;
|
||||
}
|
||||
|
||||
public isCombined(): number {
|
||||
// always mark joined cell data as combined
|
||||
return Content.IS_COMBINED_MASK;
|
||||
}
|
||||
|
||||
public getWidth(): number {
|
||||
return this._width;
|
||||
}
|
||||
|
||||
public getChars(): string {
|
||||
return this.combinedData;
|
||||
}
|
||||
|
||||
public getCode(): number {
|
||||
// code always gets the highest possible fake codepoint (read as -1)
|
||||
// this is needed as code is used by caches as identifier
|
||||
return 0x1FFFFF;
|
||||
}
|
||||
|
||||
public setFromCharData(value: CharData): void {
|
||||
throw new Error('not implemented');
|
||||
}
|
||||
|
||||
public getAsCharData(): CharData {
|
||||
return [this.fg, this.getChars(), this.getWidth(), this.getCode()];
|
||||
}
|
||||
}
|
||||
|
||||
export class CharacterJoinerRegistry implements ICharacterJoinerRegistry {
|
||||
|
||||
private _characterJoiners: ICharacterJoiner[] = [];
|
||||
private _nextCharacterJoinerId: number = 0;
|
||||
private _workCell: CellData = new CellData();
|
||||
|
||||
constructor(private _bufferService: IBufferService) { }
|
||||
|
||||
public registerCharacterJoiner(handler: (text: string) => [number, number][]): number {
|
||||
const joiner: ICharacterJoiner = {
|
||||
id: this._nextCharacterJoinerId++,
|
||||
handler
|
||||
};
|
||||
|
||||
this._characterJoiners.push(joiner);
|
||||
return joiner.id;
|
||||
}
|
||||
|
||||
public deregisterCharacterJoiner(joinerId: number): boolean {
|
||||
for (let i = 0; i < this._characterJoiners.length; i++) {
|
||||
if (this._characterJoiners[i].id === joinerId) {
|
||||
this._characterJoiners.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public getJoinedCharacters(row: number): [number, number][] {
|
||||
if (this._characterJoiners.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const line = this._bufferService.buffer.lines.get(row);
|
||||
if (!line || line.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const ranges: [number, number][] = [];
|
||||
const lineStr = line.translateToString(true);
|
||||
|
||||
// Because some cells can be represented by multiple javascript characters,
|
||||
// we track the cell and the string indexes separately. This allows us to
|
||||
// translate the string ranges we get from the joiners back into cell ranges
|
||||
// for use when rendering
|
||||
let rangeStartColumn = 0;
|
||||
let currentStringIndex = 0;
|
||||
let rangeStartStringIndex = 0;
|
||||
let rangeAttrFG = line.getFg(0);
|
||||
let rangeAttrBG = line.getBg(0);
|
||||
|
||||
for (let x = 0; x < line.getTrimmedLength(); x++) {
|
||||
line.loadCell(x, this._workCell);
|
||||
|
||||
if (this._workCell.getWidth() === 0) {
|
||||
// If this character is of width 0, skip it.
|
||||
continue;
|
||||
}
|
||||
|
||||
// End of range
|
||||
if (this._workCell.fg !== rangeAttrFG || this._workCell.bg !== rangeAttrBG) {
|
||||
// If we ended up with a sequence of more than one character,
|
||||
// look for ranges to join.
|
||||
if (x - rangeStartColumn > 1) {
|
||||
const joinedRanges = this._getJoinedRanges(
|
||||
lineStr,
|
||||
rangeStartStringIndex,
|
||||
currentStringIndex,
|
||||
line,
|
||||
rangeStartColumn
|
||||
);
|
||||
for (let i = 0; i < joinedRanges.length; i++) {
|
||||
ranges.push(joinedRanges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
// Reset our markers for a new range.
|
||||
rangeStartColumn = x;
|
||||
rangeStartStringIndex = currentStringIndex;
|
||||
rangeAttrFG = this._workCell.fg;
|
||||
rangeAttrBG = this._workCell.bg;
|
||||
}
|
||||
|
||||
currentStringIndex += this._workCell.getChars().length || WHITESPACE_CELL_CHAR.length;
|
||||
}
|
||||
|
||||
// Process any trailing ranges.
|
||||
if (this._bufferService.cols - rangeStartColumn > 1) {
|
||||
const joinedRanges = this._getJoinedRanges(
|
||||
lineStr,
|
||||
rangeStartStringIndex,
|
||||
currentStringIndex,
|
||||
line,
|
||||
rangeStartColumn
|
||||
);
|
||||
for (let i = 0; i < joinedRanges.length; i++) {
|
||||
ranges.push(joinedRanges[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a segment of a line of text, find all ranges of text that should be
|
||||
* joined in a single rendering unit. Ranges are internally converted to
|
||||
* column ranges, rather than string ranges.
|
||||
* @param line String representation of the full line of text
|
||||
* @param startIndex Start position of the range to search in the string (inclusive)
|
||||
* @param endIndex End position of the range to search in the string (exclusive)
|
||||
*/
|
||||
private _getJoinedRanges(line: string, startIndex: number, endIndex: number, lineData: IBufferLine, startCol: number): [number, number][] {
|
||||
const text = line.substring(startIndex, endIndex);
|
||||
// At this point we already know that there is at least one joiner so
|
||||
// we can just pull its value and assign it directly rather than
|
||||
// merging it into an empty array, which incurs unnecessary writes.
|
||||
const joinedRanges: [number, number][] = this._characterJoiners[0].handler(text);
|
||||
for (let i = 1; i < this._characterJoiners.length; i++) {
|
||||
// We merge any overlapping ranges across the different joiners
|
||||
const joinerRanges = this._characterJoiners[i].handler(text);
|
||||
for (let j = 0; j < joinerRanges.length; j++) {
|
||||
CharacterJoinerRegistry._mergeRanges(joinedRanges, joinerRanges[j]);
|
||||
}
|
||||
}
|
||||
this._stringRangesToCellRanges(joinedRanges, lineData, startCol);
|
||||
return joinedRanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies the provided ranges in-place to adjust for variations between
|
||||
* string length and cell width so that the range represents a cell range,
|
||||
* rather than the string range the joiner provides.
|
||||
* @param ranges String ranges containing start (inclusive) and end (exclusive) index
|
||||
* @param line Cell data for the relevant line in the terminal
|
||||
* @param startCol Offset within the line to start from
|
||||
*/
|
||||
private _stringRangesToCellRanges(ranges: [number, number][], line: IBufferLine, startCol: number): void {
|
||||
let currentRangeIndex = 0;
|
||||
let currentRangeStarted = false;
|
||||
let currentStringIndex = 0;
|
||||
let currentRange = ranges[currentRangeIndex];
|
||||
|
||||
// If we got through all of the ranges, stop searching
|
||||
if (!currentRange) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (let x = startCol; x < this._bufferService.cols; x++) {
|
||||
const width = line.getWidth(x);
|
||||
const length = line.getString(x).length || WHITESPACE_CELL_CHAR.length;
|
||||
|
||||
// We skip zero-width characters when creating the string to join the text
|
||||
// so we do the same here
|
||||
if (width === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Adjust the start of the range
|
||||
if (!currentRangeStarted && currentRange[0] <= currentStringIndex) {
|
||||
currentRange[0] = x;
|
||||
currentRangeStarted = true;
|
||||
}
|
||||
|
||||
// Adjust the end of the range
|
||||
if (currentRange[1] <= currentStringIndex) {
|
||||
currentRange[1] = x;
|
||||
|
||||
// We're finished with this range, so we move to the next one
|
||||
currentRange = ranges[++currentRangeIndex];
|
||||
|
||||
// If there are no more ranges left, stop searching
|
||||
if (!currentRange) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Ranges can be on adjacent characters. Because the end index of the
|
||||
// ranges are exclusive, this means that the index for the start of a
|
||||
// range can be the same as the end index of the previous range. To
|
||||
// account for the start of the next range, we check here just in case.
|
||||
if (currentRange[0] <= currentStringIndex) {
|
||||
currentRange[0] = x;
|
||||
currentRangeStarted = true;
|
||||
} else {
|
||||
currentRangeStarted = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Adjust the string index based on the character length to line up with
|
||||
// the column adjustment
|
||||
currentStringIndex += length;
|
||||
}
|
||||
|
||||
// If there is still a range left at the end, it must extend all the way to
|
||||
// the end of the line.
|
||||
if (currentRange) {
|
||||
currentRange[1] = this._bufferService.cols;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the range defined by the provided start and end into the list of
|
||||
* existing ranges. The merge is done in place on the existing range for
|
||||
* performance and is also returned.
|
||||
* @param ranges Existing range list
|
||||
* @param newRange Tuple of two numbers representing the new range to merge in.
|
||||
* @returns The ranges input with the new range merged in place
|
||||
*/
|
||||
private static _mergeRanges(ranges: [number, number][], newRange: [number, number]): [number, number][] {
|
||||
let inRange = false;
|
||||
for (let i = 0; i < ranges.length; i++) {
|
||||
const range = ranges[i];
|
||||
if (!inRange) {
|
||||
if (newRange[1] <= range[0]) {
|
||||
// Case 1: New range is before the search range
|
||||
ranges.splice(i, 0, newRange);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 2: New range is either wholly contained within the
|
||||
// search range or overlaps with the front of it
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[0] < range[1]) {
|
||||
// Case 3: New range either wholly contains the search range
|
||||
// or overlaps with the end of it
|
||||
range[0] = Math.min(newRange[0], range[0]);
|
||||
inRange = true;
|
||||
}
|
||||
|
||||
// Case 4: New range starts after the search range
|
||||
continue;
|
||||
} else {
|
||||
if (newRange[1] <= range[0]) {
|
||||
// Case 5: New range extends from previous range but doesn't
|
||||
// reach the current one
|
||||
ranges[i - 1][1] = newRange[1];
|
||||
return ranges;
|
||||
}
|
||||
|
||||
if (newRange[1] <= range[1]) {
|
||||
// Case 6: New range extends from prvious range into the
|
||||
// current range
|
||||
ranges[i - 1][1] = Math.max(newRange[1], range[1]);
|
||||
ranges.splice(i, 1);
|
||||
inRange = false;
|
||||
return ranges;
|
||||
}
|
||||
|
||||
// Case 7: New range extends from previous range past the
|
||||
// end of the current range
|
||||
ranges.splice(i, 1);
|
||||
i--;
|
||||
}
|
||||
}
|
||||
|
||||
if (inRange) {
|
||||
// Case 8: New range extends past the last existing range
|
||||
ranges[ranges.length - 1][1] = newRange[1];
|
||||
} else {
|
||||
// Case 9: New range starts after the last existing range
|
||||
ranges.push(newRange);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
}
|
||||
Vendored
+11
@@ -45,3 +45,14 @@ export interface IRenderer extends IDisposable {
|
||||
registerCharacterJoiner(handler: CharacterJoinerHandler): number;
|
||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
||||
}
|
||||
|
||||
export interface ICharacterJoiner {
|
||||
id: number;
|
||||
handler: CharacterJoinerHandler;
|
||||
}
|
||||
|
||||
export interface ICharacterJoinerRegistry {
|
||||
registerCharacterJoiner(handler: (text: string) => [number, number][]): number;
|
||||
deregisterCharacterJoiner(joinerId: number): boolean;
|
||||
getJoinedCharacters(row: number): [number, number][];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user