mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Maintain decorations sorted by line
This commit is contained in:
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2018 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { SortedList } from 'common/SortedList';
|
||||
|
||||
const deepStrictEqual = assert.deepStrictEqual;
|
||||
|
||||
describe('SortedList', () => {
|
||||
let list: SortedList<number>;
|
||||
function assertList(expected: number[]): void {
|
||||
deepStrictEqual(Array.from(list.values()), expected);
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
list = new SortedList<number>(e => e);
|
||||
});
|
||||
|
||||
describe('insert', () => {
|
||||
it('should maintain sorted values', () => {
|
||||
list.insert(10);
|
||||
assertList([10]);
|
||||
list.insert(8);
|
||||
assertList([8, 10]);
|
||||
list.insert(15);
|
||||
assertList([8, 10, 15]);
|
||||
list.insert(2);
|
||||
assertList([2, 8, 10, 15]);
|
||||
list.insert(1);
|
||||
assertList([1, 2, 8, 10, 15]);
|
||||
list.insert(6);
|
||||
assertList([1, 2, 6, 8, 10, 15]);
|
||||
});
|
||||
it('should allow duplicates of the same key', () => {
|
||||
list.insert(5);
|
||||
assertList([5]);
|
||||
list.insert(5);
|
||||
assertList([5, 5]);
|
||||
list.insert(8);
|
||||
assertList([5, 5, 8]);
|
||||
list.insert(5);
|
||||
assertList([5, 5, 5, 8]);
|
||||
list.insert(8);
|
||||
assertList([5, 5, 5, 8, 8]);
|
||||
list.insert(6);
|
||||
assertList([5, 5, 5, 6, 8, 8]);
|
||||
});
|
||||
});
|
||||
it('delete', () => {
|
||||
list.insert(1);
|
||||
list.insert(2);
|
||||
list.insert(4);
|
||||
list.insert(3);
|
||||
list.insert(5);
|
||||
assertList([1, 2, 3, 4, 5]);
|
||||
list.delete(1);
|
||||
assertList([2, 3, 4, 5]);
|
||||
list.delete(3);
|
||||
assertList([2, 4, 5]);
|
||||
list.delete(4);
|
||||
assertList([2, 5]);
|
||||
list.delete(5);
|
||||
assertList([2]);
|
||||
list.delete(2);
|
||||
assertList([]);
|
||||
});
|
||||
it('getKeyIterator', () => {
|
||||
list.insert(5);
|
||||
list.insert(5);
|
||||
list.insert(8);
|
||||
list.insert(5);
|
||||
list.insert(8);
|
||||
list.insert(6);
|
||||
assertList([5, 5, 5, 6, 8, 8]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(5)), [5, 5, 5]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(6)), [6]);
|
||||
deepStrictEqual(Array.from(list.getKeyIterator(8)), [8, 8]);
|
||||
});
|
||||
it('clear', () => {
|
||||
list.insert(1);
|
||||
list.insert(2);
|
||||
list.insert(4);
|
||||
list.insert(3);
|
||||
list.insert(5);
|
||||
list.clear();
|
||||
assertList([]);
|
||||
});
|
||||
it('custom key', () => {
|
||||
const customList = new SortedList<{ key: number }>(e => e.key);
|
||||
customList.insert({ key: 5 });
|
||||
customList.insert({ key: 2 });
|
||||
customList.insert({ key: 10 });
|
||||
customList.insert({ key: 5 });
|
||||
customList.insert({ key: 6 });
|
||||
deepStrictEqual(Array.from(customList.values()), [
|
||||
{ key: 2 },
|
||||
{ key: 5 },
|
||||
{ key: 5 },
|
||||
{ key: 6 },
|
||||
{ key: 10 }
|
||||
]);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2022 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
export class SortedList<T> {
|
||||
private readonly _array: T[] = [];
|
||||
|
||||
constructor(
|
||||
private readonly _getKey: (value: T) => number
|
||||
) {
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._array.length = 0;
|
||||
}
|
||||
|
||||
public insert(value: T): void {
|
||||
if (this._array.length === 0) {
|
||||
this._array.push(value);
|
||||
return;
|
||||
}
|
||||
const i = this._search(this._getKey(value), 0, this._array.length - 1);
|
||||
this._array.splice(i, 0, value);
|
||||
}
|
||||
|
||||
public delete(value: T): boolean {
|
||||
if (this._array.length === 0) {
|
||||
return false;
|
||||
}
|
||||
const key = this._getKey(value);
|
||||
let i = this._search(key, 0, this._array.length - 1);
|
||||
if (this._getKey(this._array[i]) !== key) {
|
||||
return false;
|
||||
}
|
||||
do {
|
||||
if (this._array[i] === value) {
|
||||
this._array.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
|
||||
return false;
|
||||
}
|
||||
|
||||
public *getKeyIterator(key: number): IterableIterator<T> {
|
||||
if (this._array.length === 0) {
|
||||
return;
|
||||
}
|
||||
let i = this._search(key, 0, this._array.length - 1);
|
||||
if (this._getKey(this._array[i]) !== key) {
|
||||
return;
|
||||
}
|
||||
do {
|
||||
yield this._array[i];
|
||||
} while (++i < this._array.length && this._getKey(this._array[i]) === key);
|
||||
}
|
||||
|
||||
public values(): IterableIterator<T> {
|
||||
return this._array.values();
|
||||
}
|
||||
|
||||
private _search(key: number, min: number, max: number): number {
|
||||
if (max < min) {
|
||||
return min;
|
||||
}
|
||||
let mid = Math.floor((min + max) / 2);
|
||||
if (this._getKey(this._array[mid]) > key) {
|
||||
return this._search(key, min, mid - 1);
|
||||
}
|
||||
if (this._getKey(this._array[mid]) < key) {
|
||||
return this._search(key, mid + 1, max);
|
||||
}
|
||||
// Value found! Since keys can be duplicates, move the result index back to the lowest index
|
||||
// that matches the key.
|
||||
while (mid > 0 && this._getKey(this._array[mid - 1]) === key) {
|
||||
mid--;
|
||||
}
|
||||
return mid;
|
||||
}
|
||||
}
|
||||
@@ -7,13 +7,19 @@ import { css } from 'common/Color';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IDecorationService, IInternalDecoration } from 'common/services/Services';
|
||||
import { SortedList } from 'common/SortedList';
|
||||
import { IColor } from 'common/Types';
|
||||
import { IDecorationOptions, IDecoration, IMarker, IEvent } from 'xterm';
|
||||
|
||||
export class DecorationService extends Disposable implements IDecorationService {
|
||||
public serviceBrand: any;
|
||||
|
||||
private readonly _decorations: IInternalDecoration[] = [];
|
||||
/**
|
||||
* A list of all decorations, sorted by the marker's line value. This relies on the fact that
|
||||
* while marker line values do change, they should all change by the same amount so this should
|
||||
* never become out of order.
|
||||
*/
|
||||
private readonly _decorations: SortedList<IInternalDecoration> = new SortedList(e => e.marker.line);
|
||||
|
||||
private _onDecorationRegistered = this.register(new EventEmitter<IInternalDecoration>());
|
||||
public get onDecorationRegistered(): IEvent<IInternalDecoration> { return this._onDecorationRegistered.event; }
|
||||
@@ -35,56 +41,47 @@ export class DecorationService extends Disposable implements IDecorationService
|
||||
const markerDispose = decoration.marker.onDispose(() => decoration.dispose());
|
||||
decoration.onDispose(() => {
|
||||
if (decoration) {
|
||||
const index = this._decorations.indexOf(decoration);
|
||||
if (index >= 0) {
|
||||
this._decorations.splice(this._decorations.indexOf(decoration), 1);
|
||||
if (this._decorations.delete(decoration)) {
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
}
|
||||
markerDispose.dispose();
|
||||
}
|
||||
});
|
||||
this._decorations.push(decoration);
|
||||
this._decorations.insert(decoration);
|
||||
this._onDecorationRegistered.fire(decoration);
|
||||
}
|
||||
return decoration;
|
||||
}
|
||||
|
||||
public reset(): void {
|
||||
for (let i = 0; i < this._decorations.length; i++) {
|
||||
this._decorations[0].dispose();
|
||||
for (const d of this._decorations.values()) {
|
||||
d.dispose();
|
||||
}
|
||||
this._decorations.length = 0;
|
||||
this._decorations.clear();
|
||||
}
|
||||
|
||||
public *getDecorationsAtLine(line: number): IterableIterator<IInternalDecoration> {
|
||||
// TODO: This could be made much faster if _decorations was sorted by line (and col?)
|
||||
for (const d of this.decorations) {
|
||||
if (d.marker.line === line) {
|
||||
yield d;
|
||||
}
|
||||
}
|
||||
return this._decorations.getKeyIterator(line);
|
||||
}
|
||||
|
||||
public *getDecorationsAtCell(x: number, line: number): IterableIterator<IInternalDecoration> {
|
||||
let xmin = 0;
|
||||
let xmax = 0;
|
||||
for (const d of this.decorations) {
|
||||
if (d.marker.line === line) {
|
||||
xmin = d.options.x ?? 0;
|
||||
xmax = xmin + (d.options.width ?? 1);
|
||||
if (x >= xmin && x < xmax) {
|
||||
yield d;
|
||||
}
|
||||
for (const d of this._decorations.getKeyIterator(line)) {
|
||||
console.log('d', d);
|
||||
xmin = d.options.x ?? 0;
|
||||
xmax = xmin + (d.options.width ?? 1);
|
||||
if (x >= xmin && x < xmax) {
|
||||
yield d;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
for (const decoration of this._decorations) {
|
||||
this._onDecorationRemoved.fire(decoration);
|
||||
decoration.dispose();
|
||||
for (const d of this._decorations.values()) {
|
||||
this._onDecorationRemoved.fire(d);
|
||||
}
|
||||
this._decorations.length = 0;
|
||||
this.reset();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user