Merge branch 'master' into 1134_3

This commit is contained in:
Daniel Imms
2022-08-30 05:53:36 -07:00
committed by GitHub
23 changed files with 576 additions and 328 deletions
+69
View File
@@ -0,0 +1,69 @@
/**
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { assert } from 'chai';
import { FourKeyMap, TwoKeyMap } from 'common/MultiKeyMap';
const strictEqual = assert.strictEqual;
describe('TwoKeyMap', () => {
let map: TwoKeyMap<number | string, number | string, string>;
beforeEach(() => {
map = new TwoKeyMap();
});
it('set, get', () => {
strictEqual(map.get(1, 2), undefined);
map.set(1, 2, 'foo');
strictEqual(map.get(1, 2), 'foo');
map.set(1, 3, 'bar');
strictEqual(map.get(1, 2), 'foo');
strictEqual(map.get(1, 3), 'bar');
map.set(2, 2, 'foo2');
map.set(2, 3, 'bar2');
strictEqual(map.get(1, 2), 'foo');
strictEqual(map.get(1, 3), 'bar');
strictEqual(map.get(2, 2), 'foo2');
strictEqual(map.get(2, 3), 'bar2');
});
it('clear', () => {
strictEqual(map.get(1, 2), undefined);
map.set(1, 2, 'foo');
strictEqual(map.get(1, 2), 'foo');
map.clear();
strictEqual(map.get(1, 2), undefined);
});
});
describe('FourKeyMap', () => {
let map: FourKeyMap<number | string, number | string, number | string, number | string, string>;
beforeEach(() => {
map = new FourKeyMap();
});
it('set, get', () => {
strictEqual(map.get(1, 2, 3, 4), undefined);
map.set(1, 2, 3, 4, 'foo');
strictEqual(map.get(1, 2, 3, 4), 'foo');
map.set(1, 3, 3, 4, 'bar');
strictEqual(map.get(1, 2, 3, 4), 'foo');
strictEqual(map.get(1, 3, 3, 4), 'bar');
map.set(2, 2, 3, 4, 'foo2');
map.set(2, 3, 3, 4, 'bar2');
strictEqual(map.get(1, 2, 3, 4), 'foo');
strictEqual(map.get(1, 3, 3, 4), 'bar');
strictEqual(map.get(2, 2, 3, 4), 'foo2');
strictEqual(map.get(2, 3, 3, 4), 'bar2');
});
it('clear', () => {
strictEqual(map.get(1, 2, 3, 4), undefined);
map.set(1, 2, 3, 4, 'foo');
strictEqual(map.get(1, 2, 3, 4), 'foo');
map.clear();
strictEqual(map.get(1, 2, 3, 4), undefined);
});
});
+42
View File
@@ -0,0 +1,42 @@
/**
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
* @license MIT
*/
export class TwoKeyMap<TFirst extends string | number, TSecond extends string | number, TValue> {
private _data: { [bg: string | number]: { [fg: string | number]: TValue | undefined } | undefined } = {};
public set(first: TFirst, second: TSecond, value: TValue): void {
if (!this._data[first]) {
this._data[first] = {};
}
this._data[first as string | number]![second] = value;
}
public get(first: TFirst, second: TSecond): TValue | undefined {
return this._data[first as string | number] ? this._data[first as string | number]![second] : undefined;
}
public clear(): void {
this._data = {};
}
}
export class FourKeyMap<TFirst extends string | number, TSecond extends string | number, TThird extends string | number, TFourth extends string | number, TValue> {
private _data: TwoKeyMap<TFirst, TSecond, TwoKeyMap<TThird, TFourth, TValue>> = new TwoKeyMap();
public set(first: TFirst, second: TSecond, third: TThird, fourth: TFourth, value: TValue): void {
if (!this._data.get(first, second)) {
this._data.set(first, second, new TwoKeyMap());
}
this._data.get(first, second)!.set(third, fourth, value);
}
public get(first: TFirst, second: TSecond, third: TThird, fourth: TFourth): TValue | undefined {
return this._data.get(first, second)?.get(third, fourth);
}
public clear(): void {
this._data.clear();
}
}
+22 -3
View File
@@ -3,6 +3,9 @@
* @license MIT
*/
// Work variables to avoid garbage collection.
let i = 0;
/**
* A generic list that is maintained in sorted order and allows values with duplicate keys. This
* list is based on binary search and as such locating a key will take O(log n) amortized, this
@@ -25,7 +28,7 @@ export class SortedList<T> {
this._array.push(value);
return;
}
const i = this._search(this._getKey(value), 0, this._array.length - 1);
i = this._search(this._getKey(value), 0, this._array.length - 1);
this._array.splice(i, 0, value);
}
@@ -37,7 +40,7 @@ export class SortedList<T> {
if (key === undefined) {
return false;
}
let i = this._search(key, 0, this._array.length - 1);
i = this._search(key, 0, this._array.length - 1);
if (i === -1) {
return false;
}
@@ -57,7 +60,7 @@ export class SortedList<T> {
if (this._array.length === 0) {
return;
}
let i = this._search(key, 0, this._array.length - 1);
i = this._search(key, 0, this._array.length - 1);
if (i < 0 || i >= this._array.length) {
return;
}
@@ -69,6 +72,22 @@ export class SortedList<T> {
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
}
public forEachByKey(key: number, callback: (value: T) => void): void {
if (this._array.length === 0) {
return;
}
i = this._search(key, 0, this._array.length - 1);
if (i < 0 || i >= this._array.length) {
return;
}
if (this._getKey(this._array[i]) !== key) {
return;
}
do {
callback(this._array[i]);
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
}
public values(): IterableIterator<T> {
return this._array.values();
}
+1 -2
View File
@@ -173,7 +173,6 @@ export class MockDecorationService implements IDecorationService {
public onDecorationRemoved = new EventEmitter<IInternalDecoration>().event;
public registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined { return undefined; }
public reset(): void { }
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> { }
public *getDecorationsAtCell(x: number, line: number): IterableIterator<IInternalDecoration> { }
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void { }
public dispose(): void { }
}
+9 -4
View File
@@ -37,6 +37,11 @@ const enum Cell {
export const DEFAULT_ATTR_DATA = Object.freeze(new AttributeData());
/** Work variables to avoid garbage collection. */
const w: { startIndex: number } = {
startIndex: 0
};
/**
* Typed array based bufferline implementation.
*
@@ -168,10 +173,10 @@ export class BufferLine implements IBufferLine {
* to GC as it significantly reduced the amount of new objects/references needed.
*/
public loadCell(index: number, cell: ICellData): ICellData {
const startIndex = index * CELL_SIZE;
cell.content = this._data[startIndex + Cell.CONTENT];
cell.fg = this._data[startIndex + Cell.FG];
cell.bg = this._data[startIndex + Cell.BG];
w.startIndex = index * CELL_SIZE;
cell.content = this._data[w.startIndex + Cell.CONTENT];
cell.fg = this._data[w.startIndex + Cell.FG];
cell.bg = this._data[w.startIndex + Cell.BG];
if (cell.content & Content.IS_COMBINED_MASK) {
cell.combinedData = this._combined[index];
}
+2 -2
View File
@@ -58,14 +58,14 @@ export class BufferSet extends Disposable implements IBufferSet {
}
/**
* Returns the normal Buffer of the BufferSet
* Returns the currently active Buffer of the BufferSet
*/
public get active(): Buffer {
return this._activeBuffer;
}
/**
* Returns the currently active Buffer of the BufferSet
* Returns the normal Buffer of the BufferSet
*/
public get normal(): Buffer {
return this._normal;
+16 -4
View File
@@ -11,6 +11,12 @@ import { SortedList } from 'common/SortedList';
import { IColor } from 'common/Types';
import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm';
/** Work variables to avoid garbage collection. */
const w = {
xmin: 0,
xmax: 0
};
export class DecorationService extends Disposable implements IDecorationService {
public serviceBrand: any;
@@ -56,10 +62,6 @@ export class DecorationService extends Disposable implements IDecorationService
this._decorations.clear();
}
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> {
return this._decorations.getKeyIterator(line);
}
public *getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration> {
let xmin = 0;
let xmax = 0;
@@ -72,6 +74,16 @@ export class DecorationService extends Disposable implements IDecorationService
}
}
public forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void {
this._decorations.forEachByKey(line, d => {
w.xmin = d.options.x ?? 0;
w.xmax = w.xmin + (d.options.width ?? 1);
if (x >= w.xmin && x < w.xmax && (!layer || (d.options.layer ?? 'bottom') === layer)) {
callback(d);
}
});
}
public dispose(): void {
for (const d of this._decorations.values()) {
this._onDecorationRemoved.fire(d);
+5 -4
View File
@@ -321,10 +321,11 @@ export interface IDecorationService extends IDisposable {
readonly onDecorationRemoved: IEvent<IInternalDecoration>;
registerDecoration(decorationOptions: IDecorationOptions): IDecoration | undefined;
reset(): void;
/** Iterates over the decorations at a line (in no particular order). */
getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration>;
/** Iterates over the decorations at a cell (in no particular order). */
getDecorationsAtCell(x: number, line: number, layer?: 'bottom' | 'top'): IterableIterator<IInternalDecoration>;
/**
* Trigger a callback over the decoration at a cell (in no particular order). This uses a callback
* instead of an iterator as it's typically used in hot code paths.
*/
forEachDecorationAtCell(x: number, line: number, layer: 'bottom' | 'top' | undefined, callback: (decoration: IInternalDecoration) => void): void;
}
export interface IInternalDecoration extends IDecoration {
readonly options: IDecorationOptions;