mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into feature/lint-by-putout
This commit is contained in:
@@ -99,6 +99,26 @@ The full API for xterm.js is contained within the [TypeScript declaration file](
|
||||
|
||||
Note that some APIs are marked *experimental*, these are added to enable experimentation with new ideas without committing to support it like a normal [semver](https://semver.org/) API. Note that these APIs can change radically between versions, so be sure to read release notes if you plan on using experimental APIs.
|
||||
|
||||
## Releases
|
||||
|
||||
Xterm.js follows a monthly release cycle roughly.
|
||||
|
||||
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||
|
||||
### Beta builds
|
||||
|
||||
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
|
||||
|
||||
```bash
|
||||
npm install -S xterm@beta
|
||||
```
|
||||
|
||||
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
|
||||
|
||||
## Contributing
|
||||
|
||||
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development.
|
||||
|
||||
## Real-world uses
|
||||
Xterm.js is used in several world-class applications to provide great terminal experiences.
|
||||
|
||||
@@ -197,26 +217,6 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
|
||||
Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it on our list. Note: Please add any new contributions to the end of the list only.
|
||||
|
||||
## Releases
|
||||
|
||||
Xterm.js follows a monthly release cycle roughly.
|
||||
|
||||
All current and past releases are available on this repo's [Releases page](https://github.com/sourcelair/xterm.js/releases), you can view the [high-level roadmap on the wiki](https://github.com/xtermjs/xterm.js/wiki/Roadmap) and see what we're working on now by looking through [Milestones](https://github.com/sourcelair/xterm.js/milestones).
|
||||
|
||||
### Beta builds
|
||||
|
||||
Our CI releases beta builds to npm for every change that goes into master. Install the latest beta build with:
|
||||
|
||||
```bash
|
||||
npm install -S xterm@beta
|
||||
```
|
||||
|
||||
These should generally be stable, but some bugs may slip in. We recommend using the beta build primarily to test out new features and to verify bug fixes.
|
||||
|
||||
## Contributing
|
||||
|
||||
You can read the [guide on the wiki](https://github.com/xtermjs/xterm.js/wiki/Contributing) to learn how to contribute and set up xterm.js for development.
|
||||
|
||||
## License Agreement
|
||||
|
||||
If you contribute code to this project, you implicitly allow your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work.
|
||||
|
||||
@@ -14,7 +14,7 @@ import { IColorSet, ILinkifier2 } from 'browser/Types';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService } from 'browser/services/Services';
|
||||
import { IBufferService, IOptionsService, IDecorationService, ICoreService } from 'common/services/Services';
|
||||
import { removeTerminalFromCache } from './atlas/CharAtlasCache';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { initEvent, EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver';
|
||||
|
||||
let nextRendererId = 1;
|
||||
@@ -27,8 +27,7 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
|
||||
public dimensions: IRenderDimensions;
|
||||
|
||||
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
|
||||
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
|
||||
public readonly onRequestRedraw = initEvent<IRequestRedrawEvent>();
|
||||
|
||||
constructor(
|
||||
private _colors: IColorSet,
|
||||
@@ -48,7 +47,7 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
new TextRenderLayer(this._screenElement, 0, this._colors, allowTransparency, this._id, this._bufferService, this._optionsService, characterJoinerService, decorationService, this._coreBrowserService),
|
||||
new SelectionRenderLayer(this._screenElement, 1, this._colors, this._id, this._bufferService, this._coreBrowserService, decorationService, this._optionsService),
|
||||
new LinkRenderLayer(this._screenElement, 2, this._colors, this._id, linkifier2, this._bufferService, this._optionsService, decorationService, this._coreBrowserService),
|
||||
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this._onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService)
|
||||
new CursorRenderLayer(this._screenElement, 3, this._colors, this._id, this.onRequestRedraw, this._bufferService, this._optionsService, coreService, this._coreBrowserService, decorationService)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: 0,
|
||||
@@ -128,7 +127,7 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode));
|
||||
// Selection foreground requires a full re-render
|
||||
if (this._colors.selectionForeground) {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -201,6 +200,6 @@ export class CanvasRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
private _requestRedrawViewport(): void {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
this.onRequestRedraw.fire({ start: 0, end: this._bufferService.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,8 +45,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
a.allowTransparency === b.allowTransparency &&
|
||||
a.scaledCharWidth === b.scaledCharWidth &&
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
a.colors.foreground.rgba === b.colors.foreground.rgba &&
|
||||
a.colors.background.rgba === b.colors.background.rgba;
|
||||
}
|
||||
|
||||
export function is256Color(colorCode: number): boolean {
|
||||
|
||||
@@ -96,7 +96,10 @@ export class DynamicCharAtlas extends BaseCharAtlas {
|
||||
const tmpCanvas = document.createElement('canvas');
|
||||
tmpCanvas.width = this._config.scaledCharWidth;
|
||||
tmpCanvas.height = this._config.scaledCharHeight;
|
||||
this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
|
||||
this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', {
|
||||
alpha: this._config.allowTransparency,
|
||||
willReadFrequently: true
|
||||
}));
|
||||
|
||||
this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth);
|
||||
this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight);
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { Terminal, IDisposable, ITerminalAddon, IBufferRange, IDecoration } from 'xterm';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { initEvent } from 'common/EventEmitter';
|
||||
|
||||
export interface ISearchOptions {
|
||||
regex?: boolean;
|
||||
@@ -72,8 +72,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
|
||||
private _resultIndex: number | undefined;
|
||||
|
||||
private readonly _onDidChangeResults = new EventEmitter<{ resultIndex: number, resultCount: number } | undefined>();
|
||||
public readonly onDidChangeResults = this._onDidChangeResults.event;
|
||||
public readonly onDidChangeResults = initEvent<{ resultIndex: number, resultCount: number } | undefined>();
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
this._terminal = terminal;
|
||||
@@ -89,7 +88,7 @@ export class SearchAddon implements ITerminalAddon {
|
||||
this._highlightTimeout = setTimeout(() => {
|
||||
this.findPrevious(this._cachedSearchTerm!, { ...this._lastSearchOptions, incremental: true, noScroll: true });
|
||||
this._resultIndex = this._searchResults ? this._searchResults.size - 1 : -1;
|
||||
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 });
|
||||
this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults?.size ?? -1 });
|
||||
}, 200);
|
||||
}
|
||||
}
|
||||
@@ -325,9 +324,9 @@ export class SearchAddon implements ITerminalAddon {
|
||||
private _fireResults(term: string, found: boolean, searchOptions?: ISearchOptions): boolean {
|
||||
if (searchOptions?.decorations) {
|
||||
if (this._resultIndex !== undefined && this._searchResults?.size !== undefined) {
|
||||
this._onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size });
|
||||
this.onDidChangeResults.fire({ resultIndex: this._resultIndex, resultCount: this._searchResults.size });
|
||||
} else {
|
||||
this._onDidChangeResults.fire(undefined);
|
||||
this.onDidChangeResults.fire(undefined);
|
||||
}
|
||||
}
|
||||
this._cachedSearchTerm = term;
|
||||
|
||||
@@ -78,7 +78,7 @@ describe('xterm-addon-serialize', () => {
|
||||
terminal.loadAddon(serializeAddon);
|
||||
|
||||
selectionService = new TestSelectionService((terminal as any)._core._bufferService);
|
||||
cm = new ColorManager(document, false);
|
||||
cm = new ColorManager();
|
||||
(terminal as any)._core._colorManager = cm;
|
||||
(terminal as any)._core._selectionService = selectionService;
|
||||
});
|
||||
|
||||
@@ -70,13 +70,11 @@ const INDICES_PER_CELL = 10;
|
||||
const BYTES_PER_CELL = INDICES_PER_CELL * Float32Array.BYTES_PER_ELEMENT;
|
||||
const CELL_POSITION_INDICES = 2;
|
||||
|
||||
/** Work variables to avoid garbage collection. */
|
||||
const w: { i: number, glyph: IRasterizedGlyph | undefined, leftCellPadding: number, clippedPixels: number } = {
|
||||
i: 0,
|
||||
glyph: undefined,
|
||||
leftCellPadding: 0,
|
||||
clippedPixels: 0
|
||||
};
|
||||
// Work variables to avoid garbage collection
|
||||
let $i = 0;
|
||||
let $glyph: IRasterizedGlyph | undefined = undefined;
|
||||
let $leftCellPadding = 0;
|
||||
let $clippedPixels = 0;
|
||||
|
||||
export class GlyphRenderer extends Disposable {
|
||||
private _atlas: WebglCharAtlas | undefined;
|
||||
@@ -186,12 +184,12 @@ export class GlyphRenderer extends Disposable {
|
||||
}
|
||||
|
||||
private _updateCell(array: Float32Array, x: number, y: number, code: number | undefined, bg: number, fg: number, ext: number, chars: string, lastBg: number): void {
|
||||
w.i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
|
||||
$i = (y * this._terminal.cols + x) * INDICES_PER_CELL;
|
||||
|
||||
// Exit early if this is a null character, allow space character to continue as it may have
|
||||
// underline/strikethrough styles
|
||||
if (code === NULL_CELL_CODE || code === undefined/* This is used for the right side of wide chars */) {
|
||||
fill(array, 0, w.i, w.i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
|
||||
fill(array, 0, $i, $i + INDICES_PER_CELL - 1 - CELL_POSITION_INDICES);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -201,39 +199,39 @@ export class GlyphRenderer extends Disposable {
|
||||
|
||||
// Get the glyph
|
||||
if (chars && chars.length > 1) {
|
||||
w.glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext);
|
||||
$glyph = this._atlas.getRasterizedGlyphCombinedChar(chars, bg, fg, ext);
|
||||
} else {
|
||||
w.glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext);
|
||||
$glyph = this._atlas.getRasterizedGlyph(code, bg, fg, ext);
|
||||
}
|
||||
|
||||
w.leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2);
|
||||
if (bg !== lastBg && w.glyph.offset.x > w.leftCellPadding) {
|
||||
w.clippedPixels = w.glyph.offset.x - w.leftCellPadding;
|
||||
$leftCellPadding = Math.floor((this._dimensions.scaledCellWidth - this._dimensions.scaledCharWidth) / 2);
|
||||
if (bg !== lastBg && $glyph.offset.x > $leftCellPadding) {
|
||||
$clippedPixels = $glyph.offset.x - $leftCellPadding;
|
||||
// a_origin
|
||||
array[w.i ] = -(w.glyph.offset.x - w.clippedPixels) + this._dimensions.scaledCharLeft;
|
||||
array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop;
|
||||
array[$i ] = -($glyph.offset.x - $clippedPixels) + this._dimensions.scaledCharLeft;
|
||||
array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop;
|
||||
// a_size
|
||||
array[w.i + 2] = (w.glyph.size.x - w.clippedPixels) / this._dimensions.scaledCanvasWidth;
|
||||
array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight;
|
||||
array[$i + 2] = ($glyph.size.x - $clippedPixels) / this._dimensions.scaledCanvasWidth;
|
||||
array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight;
|
||||
// a_texcoord
|
||||
array[w.i + 4] = w.glyph.texturePositionClipSpace.x + w.clippedPixels / this._atlas.cacheCanvas.width;
|
||||
array[w.i + 5] = w.glyph.texturePositionClipSpace.y;
|
||||
array[$i + 4] = $glyph.texturePositionClipSpace.x + $clippedPixels / this._atlas.cacheCanvas.width;
|
||||
array[$i + 5] = $glyph.texturePositionClipSpace.y;
|
||||
// a_texsize
|
||||
array[w.i + 6] = w.glyph.sizeClipSpace.x - w.clippedPixels / this._atlas.cacheCanvas.width;
|
||||
array[w.i + 7] = w.glyph.sizeClipSpace.y;
|
||||
array[$i + 6] = $glyph.sizeClipSpace.x - $clippedPixels / this._atlas.cacheCanvas.width;
|
||||
array[$i + 7] = $glyph.sizeClipSpace.y;
|
||||
} else {
|
||||
// a_origin
|
||||
array[w.i ] = -w.glyph.offset.x + this._dimensions.scaledCharLeft;
|
||||
array[w.i + 1] = -w.glyph.offset.y + this._dimensions.scaledCharTop;
|
||||
array[$i ] = -$glyph.offset.x + this._dimensions.scaledCharLeft;
|
||||
array[$i + 1] = -$glyph.offset.y + this._dimensions.scaledCharTop;
|
||||
// a_size
|
||||
array[w.i + 2] = w.glyph.size.x / this._dimensions.scaledCanvasWidth;
|
||||
array[w.i + 3] = w.glyph.size.y / this._dimensions.scaledCanvasHeight;
|
||||
array[$i + 2] = $glyph.size.x / this._dimensions.scaledCanvasWidth;
|
||||
array[$i + 3] = $glyph.size.y / this._dimensions.scaledCanvasHeight;
|
||||
// a_texcoord
|
||||
array[w.i + 4] = w.glyph.texturePositionClipSpace.x;
|
||||
array[w.i + 5] = w.glyph.texturePositionClipSpace.y;
|
||||
array[$i + 4] = $glyph.texturePositionClipSpace.x;
|
||||
array[$i + 5] = $glyph.texturePositionClipSpace.y;
|
||||
// a_texsize
|
||||
array[w.i + 6] = w.glyph.sizeClipSpace.x;
|
||||
array[w.i + 7] = w.glyph.sizeClipSpace.y;
|
||||
array[$i + 6] = $glyph.sizeClipSpace.x;
|
||||
array[$i + 7] = $glyph.sizeClipSpace.y;
|
||||
}
|
||||
// a_cellpos only changes on resize
|
||||
}
|
||||
|
||||
@@ -58,17 +58,15 @@ const BYTES_PER_RECTANGLE = INDICES_PER_RECTANGLE * Float32Array.BYTES_PER_ELEME
|
||||
|
||||
const INITIAL_BUFFER_RECTANGLE_CAPACITY = 20 * INDICES_PER_RECTANGLE;
|
||||
|
||||
/** Work variables to avoid garbage collection. */
|
||||
const w: { rgba: number, isDefault: boolean, x1: number, y1: number, r: number, g: number, b: number, a: number } = {
|
||||
rgba: 0,
|
||||
isDefault: false,
|
||||
x1: 0,
|
||||
y1: 0,
|
||||
r: 0,
|
||||
g: 0,
|
||||
b: 0,
|
||||
a: 0
|
||||
};
|
||||
// Work variables to avoid garbage collection
|
||||
let $rgba = 0;
|
||||
let $isDefault = false;
|
||||
let $x1 = 0;
|
||||
let $y1 = 0;
|
||||
let $r = 0;
|
||||
let $g = 0;
|
||||
let $b = 0;
|
||||
let $a = 0;
|
||||
|
||||
export class RectangleRenderer extends Disposable {
|
||||
|
||||
@@ -232,47 +230,47 @@ export class RectangleRenderer extends Disposable {
|
||||
}
|
||||
|
||||
private _updateRectangle(vertices: IVertices, offset: number, fg: number, bg: number, startX: number, endX: number, y: number): void {
|
||||
w.isDefault = false;
|
||||
$isDefault = false;
|
||||
if (fg & FgFlags.INVERSE) {
|
||||
switch (fg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
w.rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;
|
||||
$rgba = this._colors.ansi[fg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
w.rgba = (fg & Attributes.RGB_MASK) << 8;
|
||||
$rgba = (fg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
w.rgba = this._colors.foreground.rgba;
|
||||
$rgba = this._colors.foreground.rgba;
|
||||
}
|
||||
} else {
|
||||
switch (bg & Attributes.CM_MASK) {
|
||||
case Attributes.CM_P16:
|
||||
case Attributes.CM_P256:
|
||||
w.rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;
|
||||
$rgba = this._colors.ansi[bg & Attributes.PCOLOR_MASK].rgba;
|
||||
break;
|
||||
case Attributes.CM_RGB:
|
||||
w.rgba = (bg & Attributes.RGB_MASK) << 8;
|
||||
$rgba = (bg & Attributes.RGB_MASK) << 8;
|
||||
break;
|
||||
case Attributes.CM_DEFAULT:
|
||||
default:
|
||||
w.rgba = this._colors.background.rgba;
|
||||
w.isDefault = true;
|
||||
$rgba = this._colors.background.rgba;
|
||||
$isDefault = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertices.attributes.length < offset + 4) {
|
||||
vertices.attributes = expandFloat32Array(vertices.attributes, this._terminal.rows * this._terminal.cols * INDICES_PER_RECTANGLE);
|
||||
}
|
||||
w.x1 = startX * this._dimensions.scaledCellWidth;
|
||||
w.y1 = y * this._dimensions.scaledCellHeight;
|
||||
w.r = ((w.rgba >> 24) & 0xFF) / 255;
|
||||
w.g = ((w.rgba >> 16) & 0xFF) / 255;
|
||||
w.b = ((w.rgba >> 8 ) & 0xFF) / 255;
|
||||
w.a = (!w.isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1;
|
||||
$x1 = startX * this._dimensions.scaledCellWidth;
|
||||
$y1 = y * this._dimensions.scaledCellHeight;
|
||||
$r = (($rgba >> 24) & 0xFF) / 255;
|
||||
$g = (($rgba >> 16) & 0xFF) / 255;
|
||||
$b = (($rgba >> 8 ) & 0xFF) / 255;
|
||||
$a = (!$isDefault && bg & BgFlags.DIM) ? DIM_OPACITY : 1;
|
||||
|
||||
this._addRectangle(vertices.attributes, offset, w.x1, w.y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, w.r, w.g, w.b, w.a);
|
||||
this._addRectangle(vertices.attributes, offset, $x1, $y1, (endX - startX) * this._dimensions.scaledCellWidth, this._dimensions.scaledCellHeight, $r, $g, $b, $a);
|
||||
}
|
||||
|
||||
private _addRectangle(array: Float32Array, offset: number, x1: number, y1: number, width: number, height: number, r: number, g: number, b: number, a: number): void {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Terminal, ITerminalAddon, IEvent } from 'xterm';
|
||||
import { WebglRenderer } from './WebglRenderer';
|
||||
import { ICharacterJoinerService, ICoreBrowserService, IRenderService } from 'browser/services/Services';
|
||||
import { IColorSet } from 'browser/Types';
|
||||
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
|
||||
import { EventEmitter, forwardEvent, initEvent } from 'common/EventEmitter';
|
||||
import { isSafari } from 'common/Platform';
|
||||
import { ICoreService, IDecorationService } from 'common/services/Services';
|
||||
|
||||
@@ -15,14 +15,12 @@ export class WebglAddon implements ITerminalAddon {
|
||||
private _terminal?: Terminal;
|
||||
private _renderer?: WebglRenderer;
|
||||
|
||||
private _onChangeTextureAtlas = new EventEmitter<HTMLElement>();
|
||||
public get onChangeTextureAtlas(): IEvent<HTMLElement> { return this._onChangeTextureAtlas.event; }
|
||||
private _onContextLoss = new EventEmitter<void>();
|
||||
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
|
||||
public readonly onChangeTextureAtlas = initEvent<HTMLElement>();
|
||||
public readonly onContextLoss = initEvent<void>();
|
||||
|
||||
constructor(
|
||||
private _preserveDrawingBuffer?: boolean
|
||||
) {}
|
||||
) { }
|
||||
|
||||
public activate(terminal: Terminal): void {
|
||||
if (!terminal.element) {
|
||||
@@ -39,8 +37,8 @@ export class WebglAddon implements ITerminalAddon {
|
||||
const decorationService: IDecorationService = (terminal as any)._core._decorationService;
|
||||
const colors: IColorSet = (terminal as any)._core._colorManager.colors;
|
||||
this._renderer = new WebglRenderer(terminal, colors, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer);
|
||||
forwardEvent(this._renderer.onContextLoss, this._onContextLoss);
|
||||
forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas);
|
||||
forwardEvent(this._renderer.onContextLoss, this.onContextLoss);
|
||||
forwardEvent(this._renderer.onChangeTextureAtlas, this.onChangeTextureAtlas);
|
||||
renderService.setRenderer(this._renderer);
|
||||
}
|
||||
|
||||
|
||||
@@ -18,7 +18,7 @@ import { IRenderLayer } from './renderLayer/Types';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { observeDevicePixelDimensions } from 'browser/renderer/DevicePixelObserver';
|
||||
import { ITerminal, IColorSet } from 'browser/Types';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { EventEmitter, initEvent } from 'common/EventEmitter';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
import { ICharacterJoinerService, ICoreBrowserService } from 'browser/services/Services';
|
||||
@@ -26,14 +26,12 @@ import { CharData, IBufferLine, ICellData } from 'common/Types';
|
||||
import { AttributeData } from 'common/buffer/AttributeData';
|
||||
import { ICoreService, IDecorationService } from 'common/services/Services';
|
||||
|
||||
/** Work variables to avoid garbage collection. */
|
||||
const w: { fg: number, bg: number, hasFg: boolean, hasBg: boolean, isSelected: boolean } = {
|
||||
fg: 0,
|
||||
bg: 0,
|
||||
hasFg: false,
|
||||
hasBg: false,
|
||||
isSelected: false
|
||||
};
|
||||
// Work variables to avoid garbage collection
|
||||
let $fg = 0;
|
||||
let $bg = 0;
|
||||
let $hasFg = false;
|
||||
let $hasBg = false;
|
||||
let $isSelected = false;
|
||||
|
||||
export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _renderLayers: IRenderLayer[];
|
||||
@@ -55,13 +53,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
private _isAttached: boolean;
|
||||
private _contextRestorationTimeout: number | undefined;
|
||||
|
||||
private _onChangeTextureAtlas = new EventEmitter<HTMLCanvasElement>();
|
||||
public get onChangeTextureAtlas(): IEvent<HTMLCanvasElement> { return this._onChangeTextureAtlas.event; }
|
||||
private _onRequestRedraw = new EventEmitter<IRequestRedrawEvent>();
|
||||
public get onRequestRedraw(): IEvent<IRequestRedrawEvent> { return this._onRequestRedraw.event; }
|
||||
|
||||
private _onContextLoss = new EventEmitter<void>();
|
||||
public get onContextLoss(): IEvent<void> { return this._onContextLoss.event; }
|
||||
public readonly onChangeTextureAtlas = initEvent<HTMLCanvasElement>();
|
||||
public readonly onRequestRedraw = initEvent<IRequestRedrawEvent>();
|
||||
public readonly onContextLoss = initEvent<void>();
|
||||
|
||||
constructor(
|
||||
private _terminal: Terminal,
|
||||
@@ -78,7 +72,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
this._renderLayers = [
|
||||
new LinkRenderLayer(this._core.screenElement!, 2, this._colors, this._core, this._coreBrowserService),
|
||||
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this._onRequestRedraw, this._coreBrowserService, coreService)
|
||||
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._colors, this.onRequestRedraw, this._coreBrowserService, coreService)
|
||||
];
|
||||
this.dimensions = {
|
||||
scaledCharWidth: 0,
|
||||
@@ -118,7 +112,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
this._contextRestorationTimeout = setTimeout(() => {
|
||||
this._contextRestorationTimeout = undefined;
|
||||
console.warn('webgl context not restored; firing onContextLoss');
|
||||
this._onContextLoss.fire(e);
|
||||
this.onContextLoss.fire(e);
|
||||
}, 3000 /* ms */);
|
||||
}));
|
||||
this.register(addDisposableDomListener(this._canvas, 'webglcontextrestored', (e) => {
|
||||
@@ -286,7 +280,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
throw new Error('The webgl renderer only works with the webgl char atlas');
|
||||
}
|
||||
if (this._charAtlas !== atlas) {
|
||||
this._onChangeTextureAtlas.fire(atlas.cacheCanvas);
|
||||
this.onChangeTextureAtlas.fire(atlas.cacheCanvas);
|
||||
}
|
||||
this._charAtlas = atlas;
|
||||
this._charAtlas.warmUp();
|
||||
@@ -425,9 +419,9 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
|
||||
// Nothing has changed, no updates needed
|
||||
if (this._model.cells[i] === code &&
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg &&
|
||||
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) {
|
||||
this._model.cells[i + RENDER_MODEL_BG_OFFSET] === this._workColors.bg &&
|
||||
this._model.cells[i + RENDER_MODEL_FG_OFFSET] === this._workColors.fg &&
|
||||
this._model.cells[i + RENDER_MODEL_EXT_OFFSET] === this._workColors.ext) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -475,89 +469,89 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
// override logic throughout the different sub-renderers
|
||||
|
||||
// Reset overrides work variables
|
||||
w.bg = 0;
|
||||
w.fg = 0;
|
||||
w.hasBg = false;
|
||||
w.hasFg = false;
|
||||
w.isSelected = false;
|
||||
$bg = 0;
|
||||
$fg = 0;
|
||||
$hasBg = false;
|
||||
$hasFg = false;
|
||||
$isSelected = false;
|
||||
|
||||
// Apply decorations on the bottom layer
|
||||
this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => {
|
||||
if (d.backgroundColorRGB) {
|
||||
w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
w.hasBg = true;
|
||||
$bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
$hasBg = true;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
w.hasFg = true;
|
||||
$fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
$hasFg = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Apply the selection color if needed
|
||||
w.isSelected = this._isCellSelected(x, y);
|
||||
if (w.isSelected) {
|
||||
w.bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF;
|
||||
w.hasBg = true;
|
||||
$isSelected = this._isCellSelected(x, y);
|
||||
if ($isSelected) {
|
||||
$bg = (this._coreBrowserService.isFocused ? this._colors.selectionBackgroundOpaque : this._colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF;
|
||||
$hasBg = true;
|
||||
if (this._colors.selectionForeground) {
|
||||
w.fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
|
||||
w.hasFg = true;
|
||||
$fg = this._colors.selectionForeground.rgba >> 8 & 0xFFFFFF;
|
||||
$hasFg = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Apply decorations on the top layer
|
||||
this._decorationService.forEachDecorationAtCell(x, y, 'top', d => {
|
||||
if (d.backgroundColorRGB) {
|
||||
w.bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
w.hasBg = true;
|
||||
$bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
$hasBg = true;
|
||||
}
|
||||
if (d.foregroundColorRGB) {
|
||||
w.fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
w.hasFg = true;
|
||||
$fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF;
|
||||
$hasFg = true;
|
||||
}
|
||||
});
|
||||
|
||||
// Convert any overrides from rgba to the fg/bg packed format. This resolves the inverse flag
|
||||
// ahead of time in order to use the correct cache key
|
||||
if (w.hasBg) {
|
||||
if (w.isSelected) {
|
||||
if ($hasBg) {
|
||||
if ($isSelected) {
|
||||
// Non-RGB attributes from model + force non-dim + override + force RGB color mode
|
||||
w.bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | w.bg | Attributes.CM_RGB;
|
||||
$bg = (this._workCell.bg & ~Attributes.RGB_MASK & ~BgFlags.DIM) | $bg | Attributes.CM_RGB;
|
||||
} else {
|
||||
// Non-RGB attributes from model + override + force RGB color mode
|
||||
w.bg = (this._workCell.bg & ~Attributes.RGB_MASK) | w.bg | Attributes.CM_RGB;
|
||||
$bg = (this._workCell.bg & ~Attributes.RGB_MASK) | $bg | Attributes.CM_RGB;
|
||||
}
|
||||
}
|
||||
if (w.hasFg) {
|
||||
if ($hasFg) {
|
||||
// Non-RGB attributes from model + force disable inverse + override + force RGB color mode
|
||||
w.fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | w.fg | Attributes.CM_RGB;
|
||||
$fg = (this._workCell.fg & ~Attributes.RGB_MASK & ~FgFlags.INVERSE) | $fg | Attributes.CM_RGB;
|
||||
}
|
||||
|
||||
// Handle case where inverse was specified by only one of bg override or fg override was set,
|
||||
// resolving the other inverse color and setting the inverse flag if needed.
|
||||
if (this._workColors.fg & FgFlags.INVERSE) {
|
||||
if (w.hasBg && !w.hasFg) {
|
||||
if ($hasBg && !$hasFg) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
$fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | ((this._colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
w.fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
$fg = (this._workColors.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this._workColors.bg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
w.hasFg = true;
|
||||
$hasFg = true;
|
||||
}
|
||||
if (!w.hasBg && w.hasFg) {
|
||||
if (!$hasBg && $hasFg) {
|
||||
// Resolve bg color type (default color has a different meaning in fg vs bg)
|
||||
if ((this._workColors.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) {
|
||||
w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
$bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | ((this._colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB;
|
||||
} else {
|
||||
w.bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
$bg = (this._workColors.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this._workColors.fg & (Attributes.RGB_MASK | Attributes.CM_MASK);
|
||||
}
|
||||
w.hasBg = true;
|
||||
$hasBg = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Use the override if it exists
|
||||
this._workColors.bg = w.hasBg ? w.bg : this._workColors.bg;
|
||||
this._workColors.fg = w.hasFg ? w.fg : this._workColors.fg;
|
||||
this._workColors.bg = $hasBg ? $bg : this._workColors.bg;
|
||||
this._workColors.fg = $hasFg ? $fg : this._workColors.fg;
|
||||
}
|
||||
|
||||
private _isCellSelected(x: number, y: number): boolean {
|
||||
@@ -679,7 +673,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
|
||||
}
|
||||
|
||||
private _requestRedrawViewport(): void {
|
||||
this._onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
this.onRequestRedraw.fire({ start: 0, end: this._terminal.rows - 1 });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ export function configEquals(a: ICharAtlasConfig, b: ICharAtlasConfig): boolean
|
||||
a.scaledCharHeight === b.scaledCharHeight &&
|
||||
a.drawBoldTextInBrightColors === b.drawBoldTextInBrightColors &&
|
||||
a.minimumContrastRatio === b.minimumContrastRatio &&
|
||||
a.colors.foreground === b.colors.foreground &&
|
||||
a.colors.background === b.colors.background;
|
||||
a.colors.foreground.rgba === b.colors.foreground.rgba &&
|
||||
a.colors.background.rgba === b.colors.background.rgba;
|
||||
}
|
||||
|
||||
export function is256Color(colorCode: number): boolean {
|
||||
|
||||
@@ -16,6 +16,7 @@ import { tryDrawCustomChar } from 'browser/renderer/CustomGlyphs';
|
||||
import { excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph } from 'browser/renderer/RendererUtils';
|
||||
import { IUnicodeService } from 'common/services/Services';
|
||||
import { FourKeyMap } from 'common/MultiKeyMap';
|
||||
import { IdleTaskQueue } from 'common/TaskQueue';
|
||||
|
||||
// For debugging purposes, it can be useful to set this to a really tiny value,
|
||||
// to verify that LRU eviction works.
|
||||
@@ -53,10 +54,8 @@ interface ICharAtlasActiveRow {
|
||||
height: number;
|
||||
}
|
||||
|
||||
/** Work variables to avoid garbage collection. */
|
||||
const w: { glyph: IRasterizedGlyph | undefined } = {
|
||||
glyph: undefined
|
||||
};
|
||||
// Work variables to avoid garbage collection
|
||||
let $glyph = undefined;
|
||||
|
||||
export class WebglCharAtlas implements IDisposable {
|
||||
private _didWarmUp: boolean = false;
|
||||
@@ -110,7 +109,10 @@ export class WebglCharAtlas implements IDisposable {
|
||||
this._tmpCanvas = document.createElement('canvas');
|
||||
this._tmpCanvas.width = this._config.scaledCellWidth * 4 + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCanvas.height = this._config.scaledCellHeight + TMP_CANVAS_GLYPH_PADDING * 2;
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', { alpha: this._config.allowTransparency }));
|
||||
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {
|
||||
alpha: this._config.allowTransparency,
|
||||
willReadFrequently: true
|
||||
}));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
@@ -127,10 +129,15 @@ export class WebglCharAtlas implements IDisposable {
|
||||
}
|
||||
|
||||
private _doWarmUp(): void {
|
||||
// Pre-fill with ASCII 33-126
|
||||
// Pre-fill with ASCII 33-126, this is not urgent and done in idle callbacks
|
||||
const queue = new IdleTaskQueue();
|
||||
for (let i = 33; i < 126; i++) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT);
|
||||
this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph);
|
||||
queue.enqueue(() => {
|
||||
if (!this._cacheMap.get(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT)) {
|
||||
const rasterizedGlyph = this._drawToCache(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT);
|
||||
this._cacheMap.set(i, DEFAULT_COLOR, DEFAULT_COLOR, DEFAULT_EXT, rasterizedGlyph);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -175,12 +182,12 @@ export class WebglCharAtlas implements IDisposable {
|
||||
fg: number,
|
||||
ext: number
|
||||
): IRasterizedGlyph {
|
||||
w.glyph = cacheMap.get(key, bg, fg, ext);
|
||||
if (!w.glyph) {
|
||||
w.glyph = this._drawToCache(key, bg, fg, ext);
|
||||
cacheMap.set(key, bg, fg, ext, w.glyph);
|
||||
$glyph = cacheMap.get(key, bg, fg, ext);
|
||||
if (!$glyph) {
|
||||
$glyph = this._drawToCache(key, bg, fg, ext);
|
||||
cacheMap.set(key, bg, fg, ext, $glyph);
|
||||
}
|
||||
return w.glyph;
|
||||
return $glyph;
|
||||
}
|
||||
|
||||
private _getColorFromAnsiIndex(idx: number): IColor {
|
||||
|
||||
+2
-2
@@ -15,12 +15,12 @@ declare module 'xterm-addon-webgl' {
|
||||
/**
|
||||
* An event that is fired when the renderer loses its canvas context.
|
||||
*/
|
||||
public get onContextLoss(): IEvent<void>;
|
||||
public readonly onContextLoss: IEvent<void>;
|
||||
|
||||
/**
|
||||
* An event that is fired when the texture atlas of the renderer changes.
|
||||
*/
|
||||
public get onChangeTextureAtlas(): IEvent<HTMLCanvasElement>;
|
||||
public readonly onChangeTextureAtlas: IEvent<HTMLCanvasElement>;
|
||||
|
||||
constructor(preserveDrawingBuffer?: boolean);
|
||||
|
||||
|
||||
+28
-18
@@ -16,8 +16,7 @@ function startServer() {
|
||||
var app = express();
|
||||
expressWs(app);
|
||||
|
||||
var terminals = {},
|
||||
logs = {};
|
||||
var terminals = {};
|
||||
|
||||
app.use('/xterm.css', express.static(__dirname + '/../css/xterm.css'));
|
||||
app.get('/logo.png', (req, res) => {
|
||||
@@ -55,10 +54,6 @@ function startServer() {
|
||||
|
||||
console.log('Created terminal with PID: ' + term.pid);
|
||||
terminals[term.pid] = term;
|
||||
logs[term.pid] = '';
|
||||
term.on('data', function(data) {
|
||||
logs[term.pid] += data;
|
||||
});
|
||||
res.send(term.pid.toString());
|
||||
res.end();
|
||||
});
|
||||
@@ -77,15 +72,25 @@ function startServer() {
|
||||
app.ws('/terminals/:pid', function (ws, req) {
|
||||
var term = terminals[parseInt(req.params.pid)];
|
||||
console.log('Connected to terminal ' + term.pid);
|
||||
ws.send(logs[term.pid]);
|
||||
|
||||
// unbuffered delivery after user input
|
||||
let userInput = false;
|
||||
|
||||
// string message buffering
|
||||
function buffer(socket, timeout) {
|
||||
function buffer(socket, timeout, maxSize) {
|
||||
let s = '';
|
||||
let sender = null;
|
||||
return (data) => {
|
||||
s += data;
|
||||
if (!sender) {
|
||||
if (s.length > maxSize || userInput) {
|
||||
userInput = false;
|
||||
socket.send(s);
|
||||
s = '';
|
||||
if (sender) {
|
||||
clearTimeout(sender);
|
||||
sender = null;
|
||||
}
|
||||
} else if (!sender) {
|
||||
sender = setTimeout(() => {
|
||||
socket.send(s);
|
||||
s = '';
|
||||
@@ -95,14 +100,23 @@ function startServer() {
|
||||
};
|
||||
}
|
||||
// binary message buffering
|
||||
function bufferUtf8(socket, timeout) {
|
||||
function bufferUtf8(socket, timeout, maxSize) {
|
||||
let buffer = [];
|
||||
let sender = null;
|
||||
let length = 0;
|
||||
return (data) => {
|
||||
buffer.push(data);
|
||||
length += data.length;
|
||||
if (!sender) {
|
||||
if (length > maxSize || userInput) {
|
||||
userInput = false;
|
||||
socket.send(Buffer.concat(buffer, length));
|
||||
buffer = [];
|
||||
length = 0;
|
||||
if (sender) {
|
||||
clearTimeout(sender);
|
||||
sender = null;
|
||||
}
|
||||
} else if (!sender) {
|
||||
sender = setTimeout(() => {
|
||||
socket.send(Buffer.concat(buffer, length));
|
||||
buffer = [];
|
||||
@@ -112,27 +126,23 @@ function startServer() {
|
||||
}
|
||||
};
|
||||
}
|
||||
const send = USE_BINARY ? bufferUtf8(ws, 5) : buffer(ws, 5);
|
||||
const send = (USE_BINARY ? bufferUtf8 : buffer)(ws, 5, 262144);
|
||||
|
||||
// WARNING: This is a naive implementation that will not throttle the flow of data. This means
|
||||
// it could flood the communication channel and make the terminal unresponsive. Learn more about
|
||||
// the problem and how to implement flow control at https://xtermjs.org/docs/guides/flowcontrol/
|
||||
term.on('data', function(data) {
|
||||
try {
|
||||
send(data);
|
||||
} catch (ex) {
|
||||
// The WebSocket is not open, ignore
|
||||
}
|
||||
send(data);
|
||||
});
|
||||
ws.on('message', function(msg) {
|
||||
term.write(msg);
|
||||
userInput = true;
|
||||
});
|
||||
ws.on('close', function () {
|
||||
term.kill();
|
||||
console.log('Closed terminal ' + term.pid);
|
||||
// Clean things up
|
||||
delete terminals[term.pid];
|
||||
delete logs[term.pid];
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -28,7 +28,7 @@ describe('ColorManager', () => {
|
||||
return {data: [0, 0, 0, 0xFF]};
|
||||
}
|
||||
});
|
||||
cm = new ColorManager(document, false);
|
||||
cm = new ColorManager();
|
||||
});
|
||||
|
||||
describe('constructor', () => {
|
||||
|
||||
+14
-81
@@ -80,22 +80,11 @@ export const DEFAULT_ANSI_COLORS = Object.freeze((() => {
|
||||
*/
|
||||
export class ColorManager implements IColorManager {
|
||||
public colors: IColorSet;
|
||||
private _ctx: CanvasRenderingContext2D;
|
||||
private _litmusColor: CanvasGradient;
|
||||
|
||||
private _contrastCache: IColorContrastCache;
|
||||
private _restoreColors!: IRestoreColorSet;
|
||||
|
||||
constructor(document: Document, public allowTransparency: boolean) {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 1;
|
||||
canvas.height = 1;
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Could not get rendering context');
|
||||
}
|
||||
this._ctx = ctx;
|
||||
this._ctx.globalCompositeOperation = 'copy';
|
||||
this._litmusColor = this._ctx.createLinearGradient(0, 0, 1, 1);
|
||||
constructor() {
|
||||
this._contrastCache = new ColorContrastCache();
|
||||
this.colors = {
|
||||
foreground: DEFAULT_FOREGROUND,
|
||||
@@ -118,9 +107,6 @@ export class ColorManager implements IColorManager {
|
||||
case 'minimumContrastRatio':
|
||||
this._contrastCache.clear();
|
||||
break;
|
||||
case 'allowTransparency':
|
||||
this.allowTransparency = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,11 +118,11 @@ export class ColorManager implements IColorManager {
|
||||
public setTheme(theme: ITheme = {}): void {
|
||||
this.colors.foreground = this._parseColor(theme.foreground, DEFAULT_FOREGROUND);
|
||||
this.colors.background = this._parseColor(theme.background, DEFAULT_BACKGROUND);
|
||||
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR, true);
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT, true);
|
||||
this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION, true);
|
||||
this.colors.cursor = this._parseColor(theme.cursor, DEFAULT_CURSOR);
|
||||
this.colors.cursorAccent = this._parseColor(theme.cursorAccent, DEFAULT_CURSOR_ACCENT);
|
||||
this.colors.selectionBackgroundTransparent = this._parseColor(theme.selectionBackground, DEFAULT_SELECTION);
|
||||
this.colors.selectionBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionBackgroundTransparent);
|
||||
this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent, true);
|
||||
this.colors.selectionInactiveBackgroundTransparent = this._parseColor(theme.selectionInactiveBackground, this.colors.selectionBackgroundTransparent);
|
||||
this.colors.selectionInactiveBackgroundOpaque = color.blend(this.colors.background, this.colors.selectionInactiveBackgroundTransparent);
|
||||
const nullColor: IColor = {
|
||||
css: '',
|
||||
@@ -220,69 +206,16 @@ export class ColorManager implements IColorManager {
|
||||
}
|
||||
|
||||
private _parseColor(
|
||||
css: string | undefined,
|
||||
fallback: IColor,
|
||||
allowTransparency: boolean = this.allowTransparency
|
||||
cssString: string | undefined,
|
||||
fallback: IColor
|
||||
): IColor {
|
||||
if (css === undefined) {
|
||||
return fallback;
|
||||
}
|
||||
|
||||
// If parsing the value results in failure, then it must be ignored, and the attribute must
|
||||
// retain its previous value.
|
||||
// -- https://html.spec.whatwg.org/multipage/canvas.html#fill-and-stroke-styles
|
||||
this._ctx.fillStyle = this._litmusColor;
|
||||
this._ctx.fillStyle = css;
|
||||
if (typeof this._ctx.fillStyle !== 'string') {
|
||||
console.warn(`Color: ${css} is invalid using fallback ${fallback.css}`);
|
||||
return fallback;
|
||||
}
|
||||
|
||||
this._ctx.fillRect(0, 0, 1, 1);
|
||||
const data = this._ctx.getImageData(0, 0, 1, 1).data;
|
||||
|
||||
// Check if the printed color was transparent
|
||||
if (data[3] !== 0xFF) {
|
||||
if (!allowTransparency) {
|
||||
// Ideally we'd just ignore the alpha channel, but...
|
||||
//
|
||||
// Browsers may not give back exactly the same RGB values we put in, because most/all
|
||||
// convert the color to a pre-multiplied representation. getImageData converts that back to
|
||||
// a un-premultipled representation, but the precision loss may make the RGB channels unuable
|
||||
// on their own.
|
||||
//
|
||||
// E.g. In Chrome #12345610 turns into #10305010, and in the extreme case, 0xFFFFFF00 turns
|
||||
// into 0x00000000.
|
||||
//
|
||||
// "Note: Due to the lossy nature of converting to and from premultiplied alpha color values,
|
||||
// pixels that have just been set using putImageData() might be returned to an equivalent
|
||||
// getImageData() as different values."
|
||||
// -- https://html.spec.whatwg.org/multipage/canvas.html#pixel-manipulation
|
||||
//
|
||||
// So let's just use the fallback color in this case instead.
|
||||
console.warn(
|
||||
`Color: ${css} is using transparency, but allowTransparency is false. ` +
|
||||
`Using fallback ${fallback.css}.`
|
||||
);
|
||||
return fallback;
|
||||
if (cssString !== undefined) {
|
||||
try {
|
||||
return css.toColor(cssString);
|
||||
} catch {
|
||||
// no-op
|
||||
}
|
||||
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
|
||||
// the color value has alpha less than 1.0, and the string is the color value in the CSS rgba()
|
||||
const [r, g, b, a] = this._ctx.fillStyle.substring(5, this._ctx.fillStyle.length - 1).split(',').map(component => Number(component));
|
||||
const alpha = Math.round(a * 255);
|
||||
const rgba: number = channels.toRgba(r, g, b, alpha);
|
||||
return {
|
||||
rgba,
|
||||
css
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
// https://html.spec.whatwg.org/multipage/canvas.html#serialisation-of-a-color
|
||||
// if it has alpha equal to 1.0, then the string is a lowercase six-digit hex value, prefixed with a "#" character
|
||||
css: this._ctx.fillStyle,
|
||||
rgba: channels.toRgba(data[0], data[1], data[2], data[3])
|
||||
};
|
||||
return fallback;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import { ILinkifier2, ILinkProvider, IBufferCellPosition, ILink, ILinkifierEvent
|
||||
import { IDisposable } from 'common/Types';
|
||||
import { IMouseService, IRenderService } from './services/Services';
|
||||
import { IBufferService } from 'common/services/Services';
|
||||
import { EventEmitter, IEvent } from 'common/EventEmitter';
|
||||
import { EventEmitter, IEvent, initEvent } from 'common/EventEmitter';
|
||||
import { Disposable, getDisposeArrayDisposable, disposeArray } from 'common/Lifecycle';
|
||||
import { addDisposableDomListener } from 'browser/Lifecycle';
|
||||
|
||||
@@ -26,10 +26,8 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
private _activeProviderReplies: Map<Number, ILinkWithState[] | undefined> | undefined;
|
||||
private _activeLine: number = -1;
|
||||
|
||||
private _onShowLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
|
||||
public get onShowLinkUnderline(): IEvent<ILinkifierEvent> { return this._onShowLinkUnderline.event; }
|
||||
private _onHideLinkUnderline = this.register(new EventEmitter<ILinkifierEvent>());
|
||||
public get onHideLinkUnderline(): IEvent<ILinkifierEvent> { return this._onHideLinkUnderline.event; }
|
||||
public readonly onShowLinkUnderline = this.register(initEvent<ILinkifierEvent>());
|
||||
public readonly onHideLinkUnderline = this.register(initEvent<ILinkifierEvent>());
|
||||
|
||||
constructor(
|
||||
@IBufferService private readonly _bufferService: IBufferService
|
||||
@@ -343,7 +341,7 @@ export class Linkifier2 extends Disposable implements ILinkifier2 {
|
||||
const range = link.range;
|
||||
const scrollOffset = this._bufferService.buffer.ydisp;
|
||||
const event = this._createLinkUnderlineEvent(range.start.x - 1, range.start.y - scrollOffset - 1, range.end.x, range.end.y - scrollOffset - 1, undefined);
|
||||
const emitter = showEvent ? this._onShowLinkUnderline : this._onHideLinkUnderline;
|
||||
const emitter = showEvent ? this.onShowLinkUnderline : this.onHideLinkUnderline;
|
||||
emitter.fire(event);
|
||||
}
|
||||
|
||||
|
||||
+23
-34
@@ -37,7 +37,7 @@ import { ITheme, IMarker, IDisposable, ILinkProvider, IDecorationOptions, IDecor
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { EventEmitter, IEvent, forwardEvent, initEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { ColorManager } from 'browser/ColorManager';
|
||||
import { RenderService } from 'browser/services/RenderService';
|
||||
@@ -122,27 +122,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
private _colorManager: ColorManager | undefined;
|
||||
private _theme: ITheme | undefined;
|
||||
|
||||
private _onCursorMove = new EventEmitter<void>();
|
||||
public get onCursorMove(): IEvent<void> { return this._onCursorMove.event; }
|
||||
private _onKey = new EventEmitter<{ key: string, domEvent: KeyboardEvent }>();
|
||||
public get onKey(): IEvent<{ key: string, domEvent: KeyboardEvent }> { return this._onKey.event; }
|
||||
private _onRender = new EventEmitter<{ start: number, end: number }>();
|
||||
public get onRender(): IEvent<{ start: number, end: number }> { return this._onRender.event; }
|
||||
private _onSelectionChange = new EventEmitter<void>();
|
||||
public get onSelectionChange(): IEvent<void> { return this._onSelectionChange.event; }
|
||||
private _onTitleChange = new EventEmitter<string>();
|
||||
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
|
||||
private _onBell = new EventEmitter<void>();
|
||||
public get onBell(): IEvent<void> { return this._onBell.event; }
|
||||
|
||||
private _onFocus = new EventEmitter<void>();
|
||||
public get onFocus(): IEvent<void> { return this._onFocus.event; }
|
||||
private _onBlur = new EventEmitter<void>();
|
||||
public get onBlur(): IEvent<void> { return this._onBlur.event; }
|
||||
private _onA11yCharEmitter = new EventEmitter<string>();
|
||||
public get onA11yChar(): IEvent<string> { return this._onA11yCharEmitter.event; }
|
||||
private _onA11yTabEmitter = new EventEmitter<number>();
|
||||
public get onA11yTab(): IEvent<number> { return this._onA11yTabEmitter.event; }
|
||||
public readonly onCursorMove = initEvent<void>();
|
||||
public readonly onKey = initEvent<{ key: string, domEvent: KeyboardEvent }>();
|
||||
public readonly onRender = initEvent<{ start: number, end: number }>();
|
||||
public readonly onSelectionChange = initEvent<void>();
|
||||
public readonly onTitleChange = initEvent<string>();
|
||||
public readonly onBell = initEvent<void>();
|
||||
public readonly onFocus = initEvent<void>();
|
||||
public readonly onBlur = initEvent<void>();
|
||||
public readonly onA11yChar = initEvent<string>();
|
||||
public readonly onA11yTab = initEvent<number>();
|
||||
|
||||
/**
|
||||
* Creates a new `Terminal` object.
|
||||
@@ -169,16 +158,16 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._instantiationService.setService(IDecorationService, this._decorationService);
|
||||
|
||||
// Setup InputHandler listeners
|
||||
this.register(this._inputHandler.onRequestBell(() => this._onBell.fire()));
|
||||
this.register(this._inputHandler.onRequestBell(() => this.onBell.fire()));
|
||||
this.register(this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end)));
|
||||
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
|
||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
||||
this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
|
||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yTab, this._onA11yTabEmitter));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this.onCursorMove));
|
||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this.onTitleChange));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this.onA11yChar));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yTab, this.onA11yTab));
|
||||
|
||||
// Setup listeners
|
||||
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
|
||||
@@ -326,7 +315,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.updateCursorStyle(ev);
|
||||
this.element!.classList.add('focus');
|
||||
this._showCursor();
|
||||
this._onFocus.fire();
|
||||
this.onFocus.fire();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -349,7 +338,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.coreService.triggerDataEvent(C0.ESC + '[O');
|
||||
}
|
||||
this.element!.classList.remove('focus');
|
||||
this._onBlur.fire();
|
||||
this.onBlur.fire();
|
||||
}
|
||||
|
||||
private _syncTextArea(): void {
|
||||
@@ -502,7 +491,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this._instantiationService.setService(ICharSizeService, this._charSizeService);
|
||||
|
||||
this._theme = this.options.theme || this._theme;
|
||||
this._colorManager = new ColorManager(document, this.options.allowTransparency);
|
||||
this._colorManager = new ColorManager();
|
||||
this.register(this.optionsService.onOptionChange(e => this._colorManager!.onOptionsChange(e, this.optionsService.rawOptions[e])));
|
||||
this._colorManager.setTheme(this._theme);
|
||||
|
||||
@@ -512,7 +501,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
const renderer = this._createRenderer();
|
||||
this._renderService = this.register(this._instantiationService.createInstance(RenderService, renderer, this.rows, this.screenElement));
|
||||
this._instantiationService.setService(IRenderService, this._renderService);
|
||||
this.register(this._renderService.onRenderedViewportChange(e => this._onRender.fire(e)));
|
||||
this.register(this._renderService.onRenderedViewportChange(e => this.onRender.fire(e)));
|
||||
this.onResize(e => this._renderService!.resize(e.cols, e.rows));
|
||||
|
||||
this._compositionView = document.createElement('div');
|
||||
@@ -552,7 +541,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
));
|
||||
this._instantiationService.setService(ISelectionService, this._selectionService);
|
||||
this.register(this._selectionService.onRequestScrollLines(e => this.scrollLines(e.amount, e.suppressScrollEvent)));
|
||||
this.register(this._selectionService.onSelectionChange(() => this._onSelectionChange.fire()));
|
||||
this.register(this._selectionService.onSelectionChange(() => this.onSelectionChange.fire()));
|
||||
this.register(this._selectionService.onRequestRedraw(e => this._renderService!.onSelectionChanged(e.start, e.end, e.columnSelectMode)));
|
||||
this.register(this._selectionService.onLinuxMouseSelection(text => {
|
||||
// If there's a new selection, put it into the textarea, focus and select it
|
||||
@@ -1101,7 +1090,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.textarea!.value = '';
|
||||
}
|
||||
|
||||
this._onKey.fire({ key: result.key, domEvent: event });
|
||||
this.onKey.fire({ key: result.key, domEvent: event });
|
||||
this._showCursor();
|
||||
this.coreService.triggerDataEvent(result.key, true);
|
||||
|
||||
@@ -1184,7 +1173,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
key = String.fromCharCode(key);
|
||||
|
||||
this._onKey.fire({ key, domEvent: ev });
|
||||
this.onKey.fire({ key, domEvent: ev });
|
||||
this._showCursor();
|
||||
this.coreService.triggerDataEvent(key, true);
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IDisposable, IMarker, ILinkProvider, IDecorationOptions, IDecoration } from 'xterm';
|
||||
import { IEvent, EventEmitter } from 'common/EventEmitter';
|
||||
import { IEvent, EventEmitter, initEvent } from 'common/EventEmitter';
|
||||
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IMouseService, IRenderService, ISelectionService } from 'browser/services/Services';
|
||||
import { IRenderDimensions, IRenderer, IRequestRedrawEvent } from 'browser/renderer/Types';
|
||||
import { IColorSet, ITerminal, ILinkifier2, IBrowser, IViewport, IColorManager, ICompositionHelper, CharacterJoinerHandler, IBufferRange } from 'browser/Types';
|
||||
@@ -352,7 +352,7 @@ export class MockCoreBrowserService implements ICoreBrowserService {
|
||||
export class MockCharSizeService implements ICharSizeService {
|
||||
public serviceBrand: undefined;
|
||||
public get hasValidSize(): boolean { return this.width > 0 && this.height > 0; }
|
||||
public onCharSizeChange: IEvent<void> = new EventEmitter<void>().event;
|
||||
public onCharSizeChange: IEvent<void> = initEvent<void>();
|
||||
constructor(public width: number, public height: number) {}
|
||||
public measure(): void {}
|
||||
}
|
||||
@@ -370,10 +370,10 @@ export class MockMouseService implements IMouseService {
|
||||
|
||||
export class MockRenderService implements IRenderService {
|
||||
public serviceBrand: undefined;
|
||||
public onDimensionsChange: IEvent<IRenderDimensions> = new EventEmitter<IRenderDimensions>().event;
|
||||
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onRender: IEvent<{ start: number, end: number }, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = new EventEmitter<{ start: number, end: number }>().event;
|
||||
public onDimensionsChange: IEvent<IRenderDimensions> = initEvent<IRenderDimensions>();
|
||||
public onRenderedViewportChange: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>();
|
||||
public onRender: IEvent<{ start: number, end: number }, void> = initEvent<{ start: number, end: number }>();
|
||||
public onRefreshRequest: IEvent<{ start: number, end: number}, void> = initEvent<{ start: number, end: number }>();
|
||||
public dimensions: IRenderDimensions = {
|
||||
scaledCharWidth: 0,
|
||||
scaledCharHeight: 0,
|
||||
@@ -457,10 +457,10 @@ export class MockSelectionService implements ISelectionService {
|
||||
public hasSelection: boolean = false;
|
||||
public selectionStart: [number, number] | undefined;
|
||||
public selectionEnd: [number, number] | undefined;
|
||||
public onLinuxMouseSelection = new EventEmitter<string>().event;
|
||||
public onRequestRedraw = new EventEmitter<ISelectionRedrawRequestEvent>().event;
|
||||
public onRequestScrollLines = new EventEmitter<ISelectionRequestScrollLinesEvent>().event;
|
||||
public onSelectionChange = new EventEmitter<void>().event;
|
||||
public onLinuxMouseSelection = initEvent<string>();
|
||||
public onRequestRedraw = initEvent<ISelectionRedrawRequestEvent>();
|
||||
public onRequestScrollLines = initEvent<ISelectionRequestScrollLinesEvent>();
|
||||
public onSelectionChange = initEvent<void>();
|
||||
public disable(): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
// outside of viewport
|
||||
if (decoration.element) {
|
||||
decoration.element.style.display = 'none';
|
||||
decoration.onRenderEmitter.fire(decoration.element);
|
||||
decoration.onRender.fire(decoration.element);
|
||||
}
|
||||
} else {
|
||||
let element = this._decorationElements.get(decoration);
|
||||
@@ -108,7 +108,7 @@ export class BufferDecorationRenderer extends Disposable {
|
||||
}
|
||||
element.style.top = `${line * this._renderService.dimensions.actualCellHeight}px`;
|
||||
element.style.display = this._altBufferIsActive ? 'none' : 'block';
|
||||
decoration.onRenderEmitter.fire(element);
|
||||
decoration.onRender.fire(element);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user