mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into typedarray_BufferLine
This commit is contained in:
+5
-3
@@ -25,9 +25,6 @@ jobs:
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
- script: |
|
||||
yarn test-coverage
|
||||
displayName: 'Generate and publish coverage'
|
||||
|
||||
- job: macOS
|
||||
pool:
|
||||
@@ -46,6 +43,11 @@ jobs:
|
||||
- script: |
|
||||
yarn lint
|
||||
displayName: 'Lint'
|
||||
- script: |
|
||||
yarn test-coverage
|
||||
export COVERALLS_GIT_BRANCH=$BUILD_SOURCEBRANCH
|
||||
yarn coveralls
|
||||
displayName: 'Generate and publish coverage'
|
||||
|
||||
- job: Windows
|
||||
pool:
|
||||
|
||||
+2
-2
@@ -33,7 +33,6 @@
|
||||
"merge-stream": "^1.0.1",
|
||||
"node-pty": "0.7.6",
|
||||
"nodemon": "1.10.2",
|
||||
"npm-run-all": "^4.1.2",
|
||||
"nyc": "^11.8.0",
|
||||
"sorcery": "^0.10.0",
|
||||
"source-map-loader": "^0.2.3",
|
||||
@@ -51,7 +50,8 @@
|
||||
"start": "node demo/start",
|
||||
"start-zmodem": "node demo/zmodem/app",
|
||||
"lint": "tslint 'src/**/*.ts' './demo/**/*.ts'",
|
||||
"test": "npm-run-all mocha lint",
|
||||
"test": "npm run mocha",
|
||||
"posttest": "npm run lint",
|
||||
"test-debug": "node --inspect-brk node_modules/.bin/gulp test",
|
||||
"test-suite": "gulp mocha-suite --test",
|
||||
"test-coverage": "nyc -x gulpfile.js -x '**/*test*' npm run mocha",
|
||||
|
||||
@@ -42,6 +42,7 @@ describe('Linkifier', () => {
|
||||
beforeEach(() => {
|
||||
terminal = new MockTerminal();
|
||||
terminal.cols = 100;
|
||||
terminal.rows = 10;
|
||||
terminal.buffer = new MockBuffer();
|
||||
(<MockBuffer>terminal.buffer).setLines(new CircularList<IBufferLine>(20));
|
||||
terminal.buffer.ydisp = 0;
|
||||
@@ -64,6 +65,7 @@ describe('Linkifier', () => {
|
||||
function assertLinkifiesRow(rowText: string, linkMatcherRegex: RegExp, links: {x: number, length: number}[], done: MochaDone): void {
|
||||
addRow(rowText);
|
||||
linkifier.registerLinkMatcher(linkMatcherRegex, () => {});
|
||||
terminal.rows = terminal.buffer.lines.length - 1;
|
||||
linkifier.linkifyRows();
|
||||
// Allow linkify to happen
|
||||
setTimeout(() => {
|
||||
|
||||
+8
-4
@@ -81,18 +81,22 @@ export class Linkifier extends EventEmitter implements ILinkifier {
|
||||
*/
|
||||
private _linkifyRows(): void {
|
||||
this._rowsTimeoutId = null;
|
||||
const buffer = this._terminal.buffer;
|
||||
|
||||
// Ensure the row exists
|
||||
const absoluteRowIndexStart = this._terminal.buffer.ydisp + this._rowsToLinkify.start;
|
||||
if (absoluteRowIndexStart >= this._terminal.buffer.lines.length) {
|
||||
// Ensure the start row exists
|
||||
const absoluteRowIndexStart = buffer.ydisp + this._rowsToLinkify.start;
|
||||
if (absoluteRowIndexStart >= buffer.lines.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate bad end row values (if a resize happened)
|
||||
const absoluteRowIndexEnd = buffer.ydisp + Math.min(this._rowsToLinkify.end, this._terminal.rows) + 1;
|
||||
|
||||
// iterate over the range of unwrapped content strings within start..end (excluding)
|
||||
// _doLinkifyRow gets full unwrapped lines with the start row as buffer offset for every matcher
|
||||
// for wrapped content over several rows the iterator might return rows outside the viewport
|
||||
// we skip those later in _doLinkifyRow
|
||||
const iterator = this._terminal.buffer.iterator(false, absoluteRowIndexStart, this._terminal.buffer.ydisp + this._rowsToLinkify.end + 1);
|
||||
const iterator = buffer.iterator(false, absoluteRowIndexStart, absoluteRowIndexEnd);
|
||||
while (iterator.hasNext()) {
|
||||
const lineData: IBufferStringIteratorResult = iterator.next();
|
||||
for (let i = 0; i < this._linkMatchers.length; i++) {
|
||||
|
||||
+31
-10
@@ -22,6 +22,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
private _lastRecordedViewportHeight: number = 0;
|
||||
private _lastRecordedBufferHeight: number = 0;
|
||||
private _lastTouchY: number;
|
||||
private _lastScrollTop: number = 0;
|
||||
|
||||
// Stores a partial line amount when scrolling, this is used to keep track of how much of a line
|
||||
// is scrolled so we can "scroll" over partial lines and feel natural on touchpads. This is a
|
||||
@@ -97,18 +98,36 @@ export class Viewport extends Disposable implements IViewport {
|
||||
* Updates dimensions and synchronizes the scroll area if necessary.
|
||||
*/
|
||||
public syncScrollArea(): void {
|
||||
// If buffer height changed
|
||||
if (this._lastRecordedBufferLength !== this._terminal.buffer.lines.length) {
|
||||
// If buffer height changed
|
||||
this._lastRecordedBufferLength = this._terminal.buffer.lines.length;
|
||||
this._refresh();
|
||||
} else if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
|
||||
// If viewport height changed
|
||||
return;
|
||||
}
|
||||
|
||||
// If viewport height changed
|
||||
if (this._lastRecordedViewportHeight !== (<any>this._terminal).renderer.dimensions.canvasHeight) {
|
||||
this._refresh();
|
||||
} else {
|
||||
// If size has changed, refresh viewport
|
||||
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
this._refresh();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// If the buffer position doesn't match last scroll top
|
||||
const newScrollTop = this._terminal.buffer.ydisp * this._currentRowHeight;
|
||||
if (this._lastScrollTop !== newScrollTop) {
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If element's scroll top changed, this can happen when hiding the element
|
||||
if (this._lastScrollTop !== this._viewportElement.scrollTop) {
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
// If row height changed
|
||||
if (this._terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio !== this._currentRowHeight) {
|
||||
this._refresh();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,6 +137,9 @@ export class Viewport extends Disposable implements IViewport {
|
||||
* @param ev The scroll event.
|
||||
*/
|
||||
private _onScroll(ev: Event): void {
|
||||
// Record current scroll top position
|
||||
this._lastScrollTop = this._viewportElement.scrollTop;
|
||||
|
||||
// Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt
|
||||
// which causes the terminal to scroll the buffer to the top
|
||||
if (!this._viewportElement.offsetParent) {
|
||||
@@ -130,8 +152,7 @@ export class Viewport extends Disposable implements IViewport {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
const newRow = Math.round(this._viewportElement.scrollTop / this._currentRowHeight);
|
||||
const newRow = Math.round(this._lastScrollTop / this._currentRowHeight);
|
||||
const diff = newRow - this._terminal.buffer.ydisp;
|
||||
this._terminal.scrollLines(diff, true);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { IRenderLayer, IColorSet, IRenderDimensions } from './Types';
|
||||
import { CharData, ITerminal } from '../Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from './atlas/Types';
|
||||
import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, IGlyphIdentifier } from './atlas/Types';
|
||||
import BaseCharAtlas from './atlas/BaseCharAtlas';
|
||||
import { acquireCharAtlas } from './atlas/CharAtlasCache';
|
||||
import { CHAR_DATA_CHAR_INDEX } from '../Buffer';
|
||||
@@ -22,6 +22,19 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
protected _charAtlas: BaseCharAtlas;
|
||||
|
||||
/**
|
||||
* An object that's reused when drawing glyphs in order to reduce GC.
|
||||
*/
|
||||
private _currentGlyphIdentifier: IGlyphIdentifier = {
|
||||
chars: '',
|
||||
code: 0,
|
||||
bg: 0,
|
||||
fg: 0,
|
||||
bold: false,
|
||||
dim: false,
|
||||
italic: false
|
||||
};
|
||||
|
||||
constructor(
|
||||
private _container: HTMLElement,
|
||||
id: string,
|
||||
@@ -38,6 +51,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
|
||||
public dispose(): void {
|
||||
this._container.removeChild(this._canvas);
|
||||
this._charAtlas.dispose();
|
||||
}
|
||||
|
||||
private _initCanvas(): void {
|
||||
@@ -245,9 +259,16 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && bold && fg < 8 && fg !== INVERTED_DEFAULT_COLOR;
|
||||
|
||||
fg += drawInBrightColor ? 8 : 0;
|
||||
this._currentGlyphIdentifier.chars = chars;
|
||||
this._currentGlyphIdentifier.code = code;
|
||||
this._currentGlyphIdentifier.bg = bg;
|
||||
this._currentGlyphIdentifier.fg = fg;
|
||||
this._currentGlyphIdentifier.bold = bold && terminal.options.enableBold;
|
||||
this._currentGlyphIdentifier.dim = dim;
|
||||
this._currentGlyphIdentifier.italic = italic;
|
||||
const atlasDidDraw = this._charAtlas && this._charAtlas.draw(
|
||||
this._ctx,
|
||||
{chars, code, bg, fg, bold: bold && terminal.options.enableBold, dim, italic},
|
||||
this._currentGlyphIdentifier,
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop
|
||||
);
|
||||
|
||||
@@ -4,10 +4,13 @@
|
||||
*/
|
||||
|
||||
import { IGlyphIdentifier } from './Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
|
||||
export default abstract class BaseCharAtlas {
|
||||
export default abstract class BaseCharAtlas implements IDisposable {
|
||||
private _didWarmUp: boolean = false;
|
||||
|
||||
public dispose(): void { }
|
||||
|
||||
/**
|
||||
* Perform any work needed to warm the cache before it can be used. May be called multiple times.
|
||||
* Implement _doWarmUp instead if you only want to get called once.
|
||||
|
||||
@@ -10,6 +10,7 @@ import BaseCharAtlas from './BaseCharAtlas';
|
||||
import { DEFAULT_ANSI_COLORS } from '../ColorManager';
|
||||
import { clearColor } from '../../shared/atlas/CharAtlasGenerator';
|
||||
import LRUMap from './LRUMap';
|
||||
import { isFirefox, isSafari } from '../../shared/utils/Browser';
|
||||
|
||||
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
|
||||
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
|
||||
@@ -29,14 +30,29 @@ const TRANSPARENT_COLOR = {
|
||||
// cache.
|
||||
const FRAME_CACHE_DRAW_LIMIT = 100;
|
||||
|
||||
/**
|
||||
* The number of milliseconds to wait before generating the ImageBitmap, this is to debounce/batch
|
||||
* the operation as window.createImageBitmap is asynchronous.
|
||||
*/
|
||||
const GLYPH_BITMAP_COMMIT_DELAY = 100;
|
||||
|
||||
interface IGlyphCacheValue {
|
||||
index: number;
|
||||
isEmpty: boolean;
|
||||
inBitmap: boolean;
|
||||
}
|
||||
|
||||
function getGlyphCacheKey(glyph: IGlyphIdentifier): string {
|
||||
const styleFlags = (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);
|
||||
return `${glyph.bg}_${glyph.fg}_${styleFlags}${glyph.chars}`;
|
||||
function getGlyphCacheKey(glyph: IGlyphIdentifier): number {
|
||||
// Note that this only returns a valid key when code < 256
|
||||
// Layout:
|
||||
// 0b00000000000000000000000000000001: italic (1)
|
||||
// 0b00000000000000000000000000000010: dim (1)
|
||||
// 0b00000000000000000000000000000100: bold (1)
|
||||
// 0b00000000000000000000111111111000: fg (9)
|
||||
// 0b00000000000111111111000000000000: bg (9)
|
||||
// 0b00011111111000000000000000000000: code (8)
|
||||
// 0b11100000000000000000000000000000: unused (3)
|
||||
return glyph.code << 21 | glyph.bg << 12 | glyph.fg << 3 | (glyph.bold ? 0 : 4) + (glyph.dim ? 0 : 2) + (glyph.italic ? 0 : 1);
|
||||
}
|
||||
|
||||
export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
@@ -57,6 +73,15 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
|
||||
private _drawToCacheCount: number = 0;
|
||||
|
||||
// An array of glyph keys that are waiting on the bitmap to be generated.
|
||||
private _glyphsWaitingOnBitmap: IGlyphCacheValue[] = [];
|
||||
|
||||
// The timeout that is used to batch bitmap generation so it's not requested for every new glyph.
|
||||
private _bitmapCommitTimeout: number | null = null;
|
||||
|
||||
// The bitmap to draw from, this is much faster on other browsers than others.
|
||||
private _bitmap: ImageBitmap | null = null;
|
||||
|
||||
constructor(document: Document, private _config: ICharAtlasConfig) {
|
||||
super();
|
||||
this._cacheCanvas = document.createElement('canvas');
|
||||
@@ -82,6 +107,13 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
// document.body.appendChild(this._cacheCanvas);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
if (this._bitmapCommitTimeout !== null) {
|
||||
window.clearTimeout(this._bitmapCommitTimeout);
|
||||
this._bitmapCommitTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
public beginFrame(): void {
|
||||
this._drawToCacheCount = 0;
|
||||
}
|
||||
@@ -92,6 +124,11 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
x: number,
|
||||
y: number
|
||||
): boolean {
|
||||
// Space is always an empty cell, special case this as it's so common
|
||||
if (glyph.code === 32) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const glyphKey = getGlyphCacheKey(glyph);
|
||||
const cacheValue = this._cacheMap.get(glyphKey);
|
||||
if (cacheValue !== null && cacheValue !== undefined) {
|
||||
@@ -124,11 +161,12 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
return glyph.code < 256;
|
||||
}
|
||||
|
||||
private _toCoordinates(index: number): [number, number] {
|
||||
return [
|
||||
(index % this._width) * this._config.scaledCharWidth,
|
||||
Math.floor(index / this._width) * this._config.scaledCharHeight
|
||||
];
|
||||
private _toCoordinateX(index: number): number {
|
||||
return (index % this._width) * this._config.scaledCharWidth;
|
||||
}
|
||||
|
||||
private _toCoordinateY(index: number): number {
|
||||
return Math.floor(index / this._width) * this._config.scaledCharHeight;
|
||||
}
|
||||
|
||||
private _drawFromCache(
|
||||
@@ -141,9 +179,10 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
if (cacheValue.isEmpty) {
|
||||
return;
|
||||
}
|
||||
const [cacheX, cacheY] = this._toCoordinates(cacheValue.index);
|
||||
const cacheX = this._toCoordinateX(cacheValue.index);
|
||||
const cacheY = this._toCoordinateY(cacheValue.index);
|
||||
ctx.drawImage(
|
||||
this._cacheCanvas,
|
||||
cacheValue.inBitmap ? this._bitmap : this._cacheCanvas,
|
||||
cacheX,
|
||||
cacheY,
|
||||
this._config.scaledCharWidth,
|
||||
@@ -230,13 +269,58 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
}
|
||||
|
||||
// copy the data from imageData to _cacheCanvas
|
||||
const [x, y] = this._toCoordinates(index);
|
||||
const x = this._toCoordinateX(index);
|
||||
const y = this._toCoordinateY(index);
|
||||
// putImageData doesn't do any blending, so it will overwrite any existing cache entry for us
|
||||
this._cacheCtx.putImageData(imageData, x, y);
|
||||
|
||||
return {
|
||||
// Add the glyph and queue it to the bitmap (if the browser supports it)
|
||||
const cacheValue = {
|
||||
index,
|
||||
isEmpty
|
||||
isEmpty,
|
||||
inBitmap: false
|
||||
};
|
||||
this._addGlyphToBitmap(cacheValue);
|
||||
|
||||
return cacheValue;
|
||||
}
|
||||
|
||||
private _addGlyphToBitmap(cacheValue: IGlyphCacheValue): void {
|
||||
// Support is patchy for createImageBitmap at the moment, pass a canvas back
|
||||
// if support is lacking as drawImage works there too. Firefox is also
|
||||
// included here as ImageBitmap appears both buggy and has horrible
|
||||
// performance (tested on v55).
|
||||
if (!('createImageBitmap' in window) || isFirefox || isSafari) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Add the glyph to the queue
|
||||
this._glyphsWaitingOnBitmap.push(cacheValue);
|
||||
|
||||
// Check if bitmap generation timeout already exists
|
||||
if (this._bitmapCommitTimeout !== null) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._bitmapCommitTimeout = window.setTimeout(() => this._generateBitmap(), GLYPH_BITMAP_COMMIT_DELAY);
|
||||
}
|
||||
|
||||
private _generateBitmap(): void {
|
||||
const glyphsMovingToBitmap = this._glyphsWaitingOnBitmap;
|
||||
this._glyphsWaitingOnBitmap = [];
|
||||
window.createImageBitmap(this._cacheCanvas).then(bitmap => {
|
||||
// Set bitmap
|
||||
this._bitmap = bitmap;
|
||||
|
||||
// Mark all new glyphs as in bitmap, excluding glyphs that came in after
|
||||
// the bitmap was requested
|
||||
for (let i = 0; i < glyphsMovingToBitmap.length; i++) {
|
||||
const value = glyphsMovingToBitmap[i];
|
||||
// It doesn't matter if the value was already evicted, it will be
|
||||
// released from memory after this block if so.
|
||||
value.inBitmap = true;
|
||||
}
|
||||
});
|
||||
this._bitmapCommitTimeout = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,57 +9,57 @@ import LRUMap from './LRUMap';
|
||||
describe('LRUMap', () => {
|
||||
it('can be used to store and retrieve values', () => {
|
||||
const map = new LRUMap(10);
|
||||
map.set('keya', 'valuea');
|
||||
map.set('keyb', 'valueb');
|
||||
map.set('keyc', 'valuec');
|
||||
assert.strictEqual(map.get('keya'), 'valuea');
|
||||
assert.strictEqual(map.get('keyb'), 'valueb');
|
||||
assert.strictEqual(map.get('keyc'), 'valuec');
|
||||
map.set(1, 'valuea');
|
||||
map.set(2, 'valueb');
|
||||
map.set(3, 'valuec');
|
||||
assert.strictEqual(map.get(1), 'valuea');
|
||||
assert.strictEqual(map.get(2), 'valueb');
|
||||
assert.strictEqual(map.get(3), 'valuec');
|
||||
});
|
||||
|
||||
it('maintains a size from insertions', () => {
|
||||
const map = new LRUMap(10);
|
||||
assert.strictEqual(map.size, 0);
|
||||
map.set('a', 'value');
|
||||
map.set(1, 'value');
|
||||
assert.strictEqual(map.size, 1);
|
||||
map.set('b', 'value');
|
||||
map.set(2, 'value');
|
||||
assert.strictEqual(map.size, 2);
|
||||
});
|
||||
|
||||
it('deletes the oldest entry when the capacity is exceeded', () => {
|
||||
const map = new LRUMap(4);
|
||||
map.set('a', 'value');
|
||||
map.set('b', 'value');
|
||||
map.set('c', 'value');
|
||||
map.set('d', 'value');
|
||||
map.set('e', 'value');
|
||||
assert.isNull(map.get('a'));
|
||||
assert.isNotNull(map.get('b'));
|
||||
assert.isNotNull(map.get('c'));
|
||||
assert.isNotNull(map.get('d'));
|
||||
assert.isNotNull(map.get('e'));
|
||||
map.set(1, 'value');
|
||||
map.set(2, 'value');
|
||||
map.set(3, 'value');
|
||||
map.set(4, 'value');
|
||||
map.set(5, 'value');
|
||||
assert.isNull(map.get(1));
|
||||
assert.isNotNull(map.get(2));
|
||||
assert.isNotNull(map.get(3));
|
||||
assert.isNotNull(map.get(4));
|
||||
assert.isNotNull(map.get(5));
|
||||
assert.strictEqual(map.size, 4);
|
||||
});
|
||||
|
||||
it('prevents a recently accessed entry from getting deleted', () => {
|
||||
const map = new LRUMap(2);
|
||||
map.set('a', 'value');
|
||||
map.set('b', 'value');
|
||||
map.get('a');
|
||||
map.set(1, 'value');
|
||||
map.set(2, 'value');
|
||||
map.get(1);
|
||||
// a would normally get deleted here, except that we called get()
|
||||
map.set('c', 'value');
|
||||
assert.isNotNull(map.get('a'));
|
||||
map.set(3, 'value');
|
||||
assert.isNotNull(map.get(1));
|
||||
// b got deleted instead of a
|
||||
assert.isNull(map.get('b'));
|
||||
assert.isNotNull(map.get('c'));
|
||||
assert.isNull(map.get(2));
|
||||
assert.isNotNull(map.get(3));
|
||||
});
|
||||
|
||||
it('supports mutation', () => {
|
||||
const map = new LRUMap(10);
|
||||
map.set('keya', 'oldvalue');
|
||||
map.set('keya', 'newvalue');
|
||||
map.set(1, 'oldvalue');
|
||||
map.set(1, 'newvalue');
|
||||
// mutation doesn't change the size
|
||||
assert.strictEqual(map.size, 1);
|
||||
assert.strictEqual(map.get('keya'), 'newvalue');
|
||||
assert.strictEqual(map.get(1), 'newvalue');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
interface ILinkedListNode<T> {
|
||||
prev: ILinkedListNode<T>;
|
||||
next: ILinkedListNode<T>;
|
||||
key: string;
|
||||
key: number;
|
||||
value: T;
|
||||
}
|
||||
|
||||
export default class LRUMap<T> {
|
||||
private _map: { [key: string]: ILinkedListNode<T> } = {};
|
||||
private _map: { [key: number]: ILinkedListNode<T> } = {};
|
||||
private _head: ILinkedListNode<T> = null;
|
||||
private _tail: ILinkedListNode<T> = null;
|
||||
private _nodePool: ILinkedListNode<T>[] = [];
|
||||
@@ -68,7 +68,7 @@ export default class LRUMap<T> {
|
||||
}
|
||||
}
|
||||
|
||||
public get(key: string): T | null {
|
||||
public get(key: number): T | null {
|
||||
// This is unsafe: We're assuming our keyspace doesn't overlap with Object.prototype. However,
|
||||
// it's faster than calling hasOwnProperty, and in our case, it would never overlap.
|
||||
const node = this._map[key];
|
||||
@@ -80,12 +80,23 @@ export default class LRUMap<T> {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a value from a key without marking it as the most recently used item.
|
||||
*/
|
||||
public peekValue(key: number): T | null {
|
||||
const node = this._map[key];
|
||||
if (node !== undefined) {
|
||||
return node.value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public peek(): T | null {
|
||||
const head = this._head;
|
||||
return head === null ? null : head.value;
|
||||
}
|
||||
|
||||
public set(key: string, value: T): void {
|
||||
public set(key: number, value: T): void {
|
||||
// This is unsafe: See note above.
|
||||
let node = this._map[key];
|
||||
if (node !== undefined) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IRenderer, IRenderDimensions, IColorSet } from '../Types';
|
||||
import { ITerminal, CharacterJoinerHandler } from '../../Types';
|
||||
import { ILinkHoverEvent, ITerminal, CharacterJoinerHandler, LinkHoverEventTypes } from '../../Types';
|
||||
import { ITheme } from 'xterm';
|
||||
import { EventEmitter } from '../../common/EventEmitter';
|
||||
import { ColorManager } from '../ColorManager';
|
||||
@@ -79,6 +79,9 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
this._terminal.element.classList.add(TERMINAL_CLASS_PREFIX + this._terminalClass);
|
||||
this._terminal.screenElement.appendChild(this._rowContainer);
|
||||
this._terminal.screenElement.appendChild(this._selectionContainer);
|
||||
|
||||
this._terminal.linkifier.on(LinkHoverEventTypes.HOVER, (e: ILinkHoverEvent) => this._onLinkHover(e));
|
||||
this._terminal.linkifier.on(LinkHoverEventTypes.LEAVE, (e: ILinkHoverEvent) => this._onLinkLeave(e));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -338,4 +341,23 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
|
||||
public registerCharacterJoiner(handler: CharacterJoinerHandler): number { return -1; }
|
||||
public deregisterCharacterJoiner(joinerId: number): boolean { return false; }
|
||||
|
||||
private _onLinkHover(e: ILinkHoverEvent): void {
|
||||
this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, true);
|
||||
}
|
||||
|
||||
private _onLinkLeave(e: ILinkHoverEvent): void {
|
||||
this._setCellUnderline(e.x1, e.x2, e.y1, e.y2, e.cols, false);
|
||||
}
|
||||
|
||||
private _setCellUnderline(x: number, x2: number, y: number, y2: number, cols: number, enabled: boolean): void {
|
||||
while (x !== x2 || y !== y2) {
|
||||
const span = <HTMLElement>this._rowElements[y].children[x];
|
||||
span.style.textDecoration = enabled ? 'underline' : 'none';
|
||||
x = (x + 1) % cols;
|
||||
if (x === 0) {
|
||||
y++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
-1
@@ -159,7 +159,6 @@ declare module 'xterm' {
|
||||
* when canvas is too slow for the environment. The following features do
|
||||
* not work when the DOM renderer is used:
|
||||
*
|
||||
* - Link underlines
|
||||
* - Line height
|
||||
* - Letter spacing
|
||||
* - Cursor blink
|
||||
|
||||
@@ -338,7 +338,7 @@ ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
||||
ansi-styles@^3.2.0, ansi-styles@^3.2.1:
|
||||
ansi-styles@^3.2.1:
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
|
||||
dependencies:
|
||||
@@ -942,7 +942,7 @@ chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.3:
|
||||
strip-ansi "^3.0.0"
|
||||
supports-color "^2.0.0"
|
||||
|
||||
chalk@^2.0.0, chalk@^2.1.0, chalk@^2.3.0, chalk@^2.4.1:
|
||||
chalk@^2.0.0, chalk@^2.3.0, chalk@^2.4.1:
|
||||
version "2.4.1"
|
||||
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
|
||||
dependencies:
|
||||
@@ -1333,7 +1333,7 @@ cross-spawn@^5.0.1:
|
||||
shebang-command "^1.2.0"
|
||||
which "^1.2.9"
|
||||
|
||||
cross-spawn@^6.0.4, cross-spawn@^6.0.5:
|
||||
cross-spawn@^6.0.5:
|
||||
version "6.0.5"
|
||||
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
|
||||
dependencies:
|
||||
@@ -1490,13 +1490,6 @@ defaults@^1.0.0:
|
||||
dependencies:
|
||||
clone "^1.0.2"
|
||||
|
||||
define-properties@^1.1.2:
|
||||
version "1.1.2"
|
||||
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94"
|
||||
dependencies:
|
||||
foreach "^2.0.5"
|
||||
object-keys "^1.0.8"
|
||||
|
||||
define-property@^0.2.5:
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116"
|
||||
@@ -1701,24 +1694,6 @@ error-ex@^1.2.0, error-ex@^1.3.1:
|
||||
dependencies:
|
||||
is-arrayish "^0.2.1"
|
||||
|
||||
es-abstract@^1.4.3:
|
||||
version "1.12.0"
|
||||
resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.12.0.tgz#9dbbdd27c6856f0001421ca18782d786bf8a6165"
|
||||
dependencies:
|
||||
es-to-primitive "^1.1.1"
|
||||
function-bind "^1.1.1"
|
||||
has "^1.0.1"
|
||||
is-callable "^1.1.3"
|
||||
is-regex "^1.0.4"
|
||||
|
||||
es-to-primitive@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.1.1.tgz#45355248a88979034b6792e19bb81f2b7975dd0d"
|
||||
dependencies:
|
||||
is-callable "^1.1.1"
|
||||
is-date-object "^1.0.1"
|
||||
is-symbol "^1.0.1"
|
||||
|
||||
es5-ext@^0.10.35, es5-ext@^0.10.9, es5-ext@~0.10.14:
|
||||
version "0.10.45"
|
||||
resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.45.tgz#0bfdf7b473da5919d5adf3bd25ceb754fccc3653"
|
||||
@@ -2119,10 +2094,6 @@ for-own@^1.0.0:
|
||||
dependencies:
|
||||
for-in "^1.0.1"
|
||||
|
||||
foreach@^2.0.5:
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/foreach/-/foreach-2.0.5.tgz#0bee005018aeb260d0a3af3ae658dd0136ec1b99"
|
||||
|
||||
foreground-child@^1.5.3, foreground-child@^1.5.6:
|
||||
version "1.5.6"
|
||||
resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-1.5.6.tgz#4fd71ad2dfde96789b980a5c0a295937cb2f5ce9"
|
||||
@@ -2201,7 +2172,7 @@ fsevents@^1.0.0, fsevents@^1.2.2:
|
||||
nan "^2.9.2"
|
||||
node-pre-gyp "^0.10.0"
|
||||
|
||||
function-bind@^1.0.2, function-bind@^1.1.1:
|
||||
function-bind@^1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||
|
||||
@@ -2638,7 +2609,7 @@ has-values@^1.0.0:
|
||||
is-number "^3.0.0"
|
||||
kind-of "^4.0.0"
|
||||
|
||||
has@^1.0.0, has@^1.0.1:
|
||||
has@^1.0.0:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
|
||||
dependencies:
|
||||
@@ -2897,10 +2868,6 @@ is-builtin-module@^1.0.0:
|
||||
dependencies:
|
||||
builtin-modules "^1.0.0"
|
||||
|
||||
is-callable@^1.1.1, is-callable@^1.1.3:
|
||||
version "1.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75"
|
||||
|
||||
is-data-descriptor@^0.1.4:
|
||||
version "0.1.4"
|
||||
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
|
||||
@@ -2913,10 +2880,6 @@ is-data-descriptor@^1.0.0:
|
||||
dependencies:
|
||||
kind-of "^6.0.0"
|
||||
|
||||
is-date-object@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16"
|
||||
|
||||
is-descriptor@^0.1.0:
|
||||
version "0.1.6"
|
||||
resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca"
|
||||
@@ -3037,12 +3000,6 @@ is-redirect@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-redirect/-/is-redirect-1.0.0.tgz#1d03dded53bd8db0f30c26e4f95d36fc7c87dc24"
|
||||
|
||||
is-regex@^1.0.4:
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491"
|
||||
dependencies:
|
||||
has "^1.0.1"
|
||||
|
||||
is-relative@^1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-relative/-/is-relative-1.0.0.tgz#a1bb6935ce8c5dba1e8b9754b9b2dcc020e2260d"
|
||||
@@ -3053,10 +3010,6 @@ is-stream@^1.0.0, is-stream@^1.0.1, is-stream@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44"
|
||||
|
||||
is-symbol@^1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.1.tgz#3cc59f00025194b6ab2e38dbae6689256b660572"
|
||||
|
||||
is-typedarray@~1.0.0:
|
||||
version "1.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
|
||||
@@ -3715,10 +3668,6 @@ memory-fs@^0.4.0, memory-fs@~0.4.1:
|
||||
errno "^0.1.3"
|
||||
readable-stream "^2.0.1"
|
||||
|
||||
memorystream@^0.3.1:
|
||||
version "0.3.1"
|
||||
resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
|
||||
|
||||
merge-descriptors@1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61"
|
||||
@@ -4107,20 +4056,6 @@ npm-packlist@^1.1.6:
|
||||
ignore-walk "^3.0.1"
|
||||
npm-bundled "^1.0.1"
|
||||
|
||||
npm-run-all@^4.1.2:
|
||||
version "4.1.3"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.3.tgz#49f15b55a66bb4101664ce270cb18e7103f8f185"
|
||||
dependencies:
|
||||
ansi-styles "^3.2.0"
|
||||
chalk "^2.1.0"
|
||||
cross-spawn "^6.0.4"
|
||||
memorystream "^0.3.1"
|
||||
minimatch "^3.0.4"
|
||||
ps-tree "^1.1.0"
|
||||
read-pkg "^3.0.0"
|
||||
shell-quote "^1.6.1"
|
||||
string.prototype.padend "^3.0.0"
|
||||
|
||||
npm-run-path@^2.0.0:
|
||||
version "2.0.2"
|
||||
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
|
||||
@@ -4200,10 +4135,6 @@ object-copy@^0.1.0:
|
||||
define-property "^0.2.5"
|
||||
kind-of "^3.0.3"
|
||||
|
||||
object-keys@^1.0.8:
|
||||
version "1.0.12"
|
||||
resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.0.12.tgz#09c53855377575310cca62f55bb334abff7b3ed2"
|
||||
|
||||
object-visit@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb"
|
||||
@@ -4645,7 +4576,7 @@ prr@~1.0.1:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
|
||||
|
||||
ps-tree@^1.0.1, ps-tree@^1.1.0:
|
||||
ps-tree@^1.0.1:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/ps-tree/-/ps-tree-1.1.0.tgz#b421b24140d6203f1ed3c76996b4427b08e8c014"
|
||||
dependencies:
|
||||
@@ -5504,14 +5435,6 @@ string-width@^1.0.1:
|
||||
is-fullwidth-code-point "^2.0.0"
|
||||
strip-ansi "^4.0.0"
|
||||
|
||||
string.prototype.padend@^3.0.0:
|
||||
version "3.0.0"
|
||||
resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.0.0.tgz#f3aaef7c1719f170c5eab1c32bf780d96e21f2f0"
|
||||
dependencies:
|
||||
define-properties "^1.1.2"
|
||||
es-abstract "^1.4.3"
|
||||
function-bind "^1.0.2"
|
||||
|
||||
string_decoder@^1.0.0, string_decoder@~1.1.1:
|
||||
version "1.1.1"
|
||||
resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8"
|
||||
|
||||
Reference in New Issue
Block a user