diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index df0cdbab..0e2d645e 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -4,16 +4,12 @@ */ import { IRenderLayer } from './Types'; -import { ICellData } from 'common/Types'; -import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; import { IGlyphIdentifier } from '../atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; -import { AttributeData } from 'common/buffer/AttributeData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; import { throwIfFalsy } from '../WebglUtils'; diff --git a/bin/test.js b/bin/test.js index 89f0d61e..775ec004 100644 --- a/bin/test.js +++ b/bin/test.js @@ -21,7 +21,7 @@ if (process.argv.length > 2) { testFiles = process.argv.slice(2); } -cp.spawnSync( +const run = cp.spawnSync( path.resolve(__dirname, '../node_modules/.bin/mocha'), testFiles, { @@ -30,3 +30,5 @@ cp.spawnSync( stdio: 'inherit' } ); + +process.exit(run.status); \ No newline at end of file diff --git a/src/InputHandler.ts b/src/InputHandler.ts index cc767a16..f47085d4 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -79,7 +79,7 @@ class DECRQSS implements IDcsHandler { return this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`); default: // invalid: DCS 0 $ r Pt ST (xterm) - this._logService.error('Unknown DCS $q %s', data); + this._logService.debug('Unknown DCS $q %s', data); this._coreService.triggerDataEvent(`${C0.ESC}P0$r${C0.ESC}\\`); } } @@ -144,16 +144,16 @@ export class InputHandler extends Disposable implements IInputHandler { * custom fallback handlers */ this._parser.setCsiHandlerFallback((collect: string, params: IParams, flag: number) => { - this._logService.error('Unknown CSI code: ', { collect, params: params.toArray(), flag: String.fromCharCode(flag) }); + this._logService.debug('Unknown CSI code: ', { collect, params: params.toArray(), flag: String.fromCharCode(flag) }); }); this._parser.setEscHandlerFallback((collect: string, flag: number) => { - this._logService.error('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) }); + this._logService.debug('Unknown ESC code: ', { collect, flag: String.fromCharCode(flag) }); }); this._parser.setExecuteHandlerFallback((code: number) => { - this._logService.error('Unknown EXECUTE code: ', { code }); + this._logService.debug('Unknown EXECUTE code: ', { code }); }); this._parser.setOscHandlerFallback((identifier: number, data: string) => { - this._logService.error('Unknown OSC code: ', { identifier, data }); + this._logService.debug('Unknown OSC code: ', { identifier, data }); }); /** @@ -1268,7 +1268,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.cursorBlink = true; break; case 66: - this._logService.info('Serial port requested application keypad.'); + this._logService.debug('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); @@ -1296,7 +1296,7 @@ export class InputHandler extends Disposable implements IInputHandler { if (this._selectionService) { this._selectionService.disable(); } - this._logService.info('Binding to mouse events.'); + this._logService.debug('Binding to mouse events.'); break; case 1004: // send focusin/focusout events // focusin: ^[[I @@ -1471,7 +1471,7 @@ export class InputHandler extends Disposable implements IInputHandler { // this.cursorBlink = false; break; case 66: - this._logService.info('Switching back to normal keypad.'); + this._logService.debug('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); @@ -1762,7 +1762,7 @@ export class InputHandler extends Disposable implements IInputHandler { attr.bg &= ~(Attributes.CM_MASK | Attributes.RGB_MASK); attr.bg |= DEFAULT_ATTR_DATA.bg & (Attributes.PCOLOR_MASK | Attributes.RGB_MASK); } else { - this._logService.error('Unknown SGR attribute: %d.', p); + this._logService.debug('Unknown SGR attribute: %d.', p); } } } @@ -1976,7 +1976,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Enables the numeric keypad to send application sequences to the host. */ public keypadApplicationMode(): void { - this._logService.info('Serial port requested application keypad.'); + this._logService.debug('Serial port requested application keypad.'); this._terminal.applicationKeypad = true; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); @@ -1989,7 +1989,7 @@ export class InputHandler extends Disposable implements IInputHandler { * Enables the keypad to send numeric characters to the host. */ public keypadNumericMode(): void { - this._logService.info('Switching back to normal keypad.'); + this._logService.debug('Switching back to normal keypad.'); this._terminal.applicationKeypad = false; if (this._terminal.viewport) { this._terminal.viewport.syncScrollArea(); diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index b83692e7..2de54396 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -94,6 +94,7 @@ describe('Terminal', () => { expect(e.domEvent).to.be.an.instanceof(Object); done(); }); + (term).textarea = { value: '' }; const evKeyDown = { preventDefault: () => { }, stopPropagation: () => { }, @@ -555,12 +556,16 @@ describe('Terminal', () => { afterEach(() => term.browser.isMac = originalIsMac); it('should interfere with the alt key on keyDown', () => { + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 81; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 192; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); }); }); @@ -573,21 +578,29 @@ describe('Terminal', () => { afterEach(() => term.browser.isMac = originalIsMac); it('should not interfere with the alt key on keyDown', () => { + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 81; - assert.equal(term.keyDown(evKeyDown), true); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, false); + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 192; - assert.equal(term.keyDown(evKeyDown), true); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, false); }); - it('should interefere with the alt + arrow keys', () => { + it('should interfere with the alt + arrow keys', () => { + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 37; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.keyCode = 39; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); }); it('should emit key with alt + key on keyPress', (done) => { @@ -639,24 +652,32 @@ describe('Terminal', () => { afterEach(() => term.browser.isWindows = originalIsWindows); it('should not interfere with the alt + ctrl key on keyDown', () => { + (term)._keyDownHandled = false; evKeyPress.altKey = true; evKeyPress.ctrlKey = true; evKeyPress.keyCode = 81; - assert.equal(term.keyDown(evKeyPress), true); + term.keyDown(evKeyPress); + assert.equal((term)._keyDownHandled, false); + (term)._keyDownHandled = false; evKeyDown.altKey = true; evKeyDown.ctrlKey = true; evKeyDown.keyCode = 81; - assert.equal(term.keyDown(evKeyDown), true); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, false); }); it('should interefere with the alt + ctrl + arrow keys', () => { evKeyDown.altKey = true; evKeyDown.ctrlKey = true; + (term)._keyDownHandled = false; evKeyDown.keyCode = 37; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); + (term)._keyDownHandled = false; evKeyDown.keyCode = 39; - assert.equal(term.keyDown(evKeyDown), false); + term.keyDown(evKeyDown); + assert.equal((term)._keyDownHandled, true); }); it('should emit key with alt + ctrl + key on keyPress', (done) => { diff --git a/src/Terminal.ts b/src/Terminal.ts index 7a068c84..d08ae73c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -38,7 +38,6 @@ import { SoundService } from 'browser/services/SoundService'; import { MouseZoneManager } from 'browser/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm'; -import { removeTerminalFromCache } from './renderer/atlas/CharAtlasCache'; import { DomRenderer } from './renderer/dom/DomRenderer'; import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'common/Types'; import { evaluateKeyboardEvent } from 'common/input/Keyboard'; @@ -179,6 +178,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp // Store if user went browsing history in scrollback private _userScrolling: boolean; + /** + * Records whether the keydown event has already been handled and triggered a data event, if so + * the keypress event should not trigger a data event but should still print to the textarea so + * screen readers will announce it. + */ + private _keyDownHandled: boolean = false; + private _inputHandler: InputHandler; public linkifier: ILinkifier; public viewport: IViewport; @@ -246,13 +252,13 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._instantiationService.setService(IOptionsService, this.optionsService); this._bufferService = this._instantiationService.createInstance(BufferService); this._instantiationService.setService(IBufferService, this._bufferService); + this._logService = this._instantiationService.createInstance(LogService); + this._instantiationService.setService(ILogService, this._logService); this._coreService = this._instantiationService.createInstance(CoreService, () => this.scrollToBottom()); this._instantiationService.setService(ICoreService, this._coreService); this._coreService.onData(e => this._onData.fire(e)); this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService); this._instantiationService.setService(IDirtyRowService, this._dirtyRowService); - this._logService = this._instantiationService.createInstance(LogService); - this._instantiationService.setService(ILogService, this._logService); this._setupOptionsListeners(); this._setup(); @@ -264,8 +270,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this._windowsMode.dispose(); this._windowsMode = undefined; } + this._renderService.dispose(); this._customKeyEventHandler = null; - removeTerminalFromCache(this); this.write = () => {}; if (this.element && this.element.parentNode) { this.element.parentNode.removeChild(this.element); @@ -685,7 +691,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp private _createRenderer(): IRenderer { switch (this.options.rendererType) { - case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService); + case 'canvas': return new Renderer(this._colorManager.colors, this, this._bufferService, this._charSizeService, this.optionsService); case 'dom': return new DomRenderer(this, this._colorManager.colors, this._charSizeService, this.optionsService); default: throw new Error(`Unrecognized rendererType "${this.options.rendererType}"`); } @@ -1515,6 +1521,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp * @param ev The keydown event to be handled. */ protected _keyDown(event: KeyboardEvent): boolean { + this._keyDownHandled = false; + if (this._customKeyEventHandler && this._customKeyEventHandler(event) === false) { return false; } @@ -1530,12 +1538,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp this.updateCursorStyle(event); - // if (result.key === C0.DC3) { // XOFF - // this._writeStopped = true; - // } else if (result.key === C0.DC1) { // XON - // this._writeStopped = false; - // } - if (result.type === KeyboardResultType.PAGE_DOWN || result.type === KeyboardResultType.PAGE_UP) { const scrollCount = this.rows - 1; this.scrollLines(result.type === KeyboardResultType.PAGE_UP ? -scrollCount : scrollCount); @@ -1559,11 +1561,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp return true; } + // If ctrl+c or enter is being sent, clear out the textarea. This is done so that screen readers + // will announce deleted characters. This will not work 100% of the time but it should cover + // most scenarios. + if (result.key === C0.ETX || result.key === C0.CR) { + this.textarea.value = ''; + } + + this._keyDownHandled = true; this._onKey.fire({ key: result.key, domEvent: event }); this.showCursor(); this._coreService.triggerDataEvent(result.key, true); - - return this.cancel(event, true); } private _isThirdLevelShift(browser: IBrowser, ev: IKeyboardEvent): boolean { @@ -1621,6 +1629,10 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp protected _keyPress(ev: KeyboardEvent): boolean { let key; + if (this._keyDownHandled) { + return false; + } + if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) { return false; } diff --git a/src/browser/Types.d.ts b/src/browser/Types.d.ts index c9167316..3f3a7637 100644 --- a/src/browser/Types.d.ts +++ b/src/browser/Types.d.ts @@ -24,6 +24,15 @@ export interface IColorSet { ansi: IColor[]; } +export interface IPartialColorSet { + foreground: IColor; + background: IColor; + cursor?: IColor; + cursorAccent?: IColor; + selection?: IColor; + ansi: IColor[]; +} + export interface IViewport extends IDisposable { scrollBarWidth: number; syncScrollArea(): void; diff --git a/src/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts similarity index 78% rename from src/renderer/BaseRenderLayer.ts rename to src/browser/renderer/BaseRenderLayer.ts index 767ef08c..e9ad5b3a 100644 --- a/src/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -3,22 +3,22 @@ * @license MIT */ -import { IRenderLayer } from './Types'; -import { IRenderDimensions } from 'browser/renderer/Types'; -import { ITerminal } from '../Types'; +import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; import { ICellData } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE } from 'common/buffer/Constants'; -import { IGlyphIdentifier } from './atlas/Types'; +import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { BaseCharAtlas } from './atlas/BaseCharAtlas'; -import { acquireCharAtlas } from './atlas/CharAtlasCache'; +import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; +import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; +import { IBufferService, IOptionsService } from 'common/services/Services'; +import { throwIfFalsy } from 'browser/renderer/RendererUtils'; export abstract class BaseRenderLayer implements IRenderLayer { private _canvas: HTMLCanvasElement; - protected _ctx: CanvasRenderingContext2D; + protected _ctx!: CanvasRenderingContext2D; private _scaledCharWidth: number = 0; private _scaledCharHeight: number = 0; private _scaledCellWidth: number = 0; @@ -26,7 +26,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _scaledCharLeft: number = 0; private _scaledCharTop: number = 0; - protected _charAtlas: BaseCharAtlas; + protected _charAtlas: BaseCharAtlas | undefined; /** * An object that's reused when drawing glyphs in order to reduce GC. @@ -46,7 +46,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { id: string, zIndex: number, private _alpha: boolean, - protected _colors: IColorSet + protected _colors: IColorSet, + private _rendererId: number, + protected readonly _bufferService: IBufferService, + protected readonly _optionsService: IOptionsService ) { this._canvas = document.createElement('canvas'); this._canvas.classList.add(`xterm-${id}-layer`); @@ -63,25 +66,25 @@ export abstract class BaseRenderLayer implements IRenderLayer { } private _initCanvas(): void { - this._ctx = this._canvas.getContext('2d', {alpha: this._alpha}); + this._ctx = throwIfFalsy(this._canvas.getContext('2d', {alpha: this._alpha})); // Draw the background if this is an opaque layer if (!this._alpha) { this._clearAll(); } } - public onOptionsChanged(terminal: ITerminal): void {} - public onBlur(terminal: ITerminal): void {} - public onFocus(terminal: ITerminal): void {} - public onCursorMove(terminal: ITerminal): void {} - public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void {} - public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} + public onOptionsChanged(): void {} + public onBlur(): void {} + public onFocus(): void {} + public onCursorMove(): void {} + public onGridChanged(startRow: number, endRow: number): void {} + public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void {} - public setColors(terminal: ITerminal, colorSet: IColorSet): void { - this._refreshCharAtlas(terminal, colorSet); + public setColors(colorSet: IColorSet): void { + this._refreshCharAtlas(colorSet); } - protected _setTransparency(terminal: ITerminal, alpha: boolean): void { + protected _setTransparency(alpha: boolean): void { // Do nothing when alpha doesn't change if (alpha === this._alpha) { return; @@ -96,24 +99,23 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._container.replaceChild(this._canvas, oldCanvas); // Regenerate char atlas and force a full redraw - this._refreshCharAtlas(terminal, this._colors); - this.onGridChanged(terminal, 0, terminal.rows - 1); + this._refreshCharAtlas(this._colors); + this.onGridChanged(0, this._bufferService.rows - 1); } /** * Refreshes the char atlas, aquiring a new one if necessary. - * @param terminal The terminal. * @param colorSet The color set to use for the char atlas. */ - private _refreshCharAtlas(terminal: ITerminal, colorSet: IColorSet): void { + private _refreshCharAtlas(colorSet: IColorSet): void { if (this._scaledCharWidth <= 0 && this._scaledCharHeight <= 0) { return; } - this._charAtlas = acquireCharAtlas(terminal, colorSet, this._scaledCharWidth, this._scaledCharHeight); + this._charAtlas = acquireCharAtlas(this._optionsService.options, this._rendererId, colorSet, this._scaledCharWidth, this._scaledCharHeight); this._charAtlas.warmUp(); } - public resize(terminal: ITerminal, dim: IRenderDimensions): void { + public resize(dim: IRenderDimensions): void { this._scaledCellWidth = dim.scaledCellWidth; this._scaledCellHeight = dim.scaledCellHeight; this._scaledCharWidth = dim.scaledCharWidth; @@ -130,10 +132,10 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._clearAll(); } - this._refreshCharAtlas(terminal, this._colors); + this._refreshCharAtlas(this._colors); } - public abstract reset(terminal: ITerminal): void; + public abstract reset(): void; /** * Fills 1+ cells completely. This uses the existing fillStyle on the context. @@ -233,16 +235,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { * Draws a truecolor character at the cell. The character will be clipped to * ensure that it fits with the cell, including the cell to the right if it's * a wide character. This uses the existing fillStyle on the context. - * @param terminal The terminal. * @param cell The cell data for the character to draw. * @param x The column to draw at. * @param y The row to draw at. * @param color The color of the character. */ - protected _fillCharTrueColor(terminal: ITerminal, cell: CellData, x: number, y: number): void { - this._ctx.font = this._getFont(terminal, false, false); + protected _fillCharTrueColor(cell: CellData, x: number, y: number): void { + this._ctx.font = this._getFont(false, false); this._ctx.textBaseline = 'middle'; - this._clipRow(terminal, y); + this._clipRow(y); this._ctx.fillText( cell.getChars(), x * this._scaledCellWidth + this._scaledCharLeft, @@ -252,7 +253,6 @@ export abstract class BaseRenderLayer implements IRenderLayer { /** * Draws one or more characters at a cell. If possible this will draw using * the character atlas to reduce draw time. - * @param terminal The terminal. * @param chars The character or characters. * @param code The character code. * @param width The width of the characters. @@ -263,14 +263,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { * This is used to validate whether a cached image can be used. * @param bold Whether the text is bold. */ - protected _drawChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { + protected _drawChars(cell: ICellData, x: number, y: number): void { // skip cache right away if we draw in RGB // Note: to avoid bad runtime JoinedCellData will be skipped // in the cache handler itself (atlasDidDraw == false) and // fall through to uncached later down below if (cell.isFgRGB() || cell.isBgRGB()) { - this._drawUncachedChars(terminal, cell, x, y); + this._drawUncachedChars(cell, x, y); return; } @@ -284,7 +284,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { fg = (cell.isFgDefault()) ? DEFAULT_COLOR : cell.getFgColor(); } - const drawInBrightColor = terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; + const drawInBrightColor = this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8 && fg !== INVERTED_DEFAULT_COLOR; fg += drawInBrightColor ? 8 : 0; this._currentGlyphIdentifier.chars = cell.getChars() || WHITESPACE_CELL_CHAR; @@ -302,7 +302,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { ); if (!atlasDidDraw) { - this._drawUncachedChars(terminal, cell, x, y); + this._drawUncachedChars(cell, x, y); } } @@ -310,16 +310,15 @@ export abstract class BaseRenderLayer implements IRenderLayer { * Draws one or more characters at one or more cells. The character(s) will be * clipped to ensure that they fit with the cell(s), including the cell to the * right if the last character is a wide character. - * @param terminal The terminal. * @param chars The character. * @param width The width of the character. * @param fg The foreground color, in the format stored within the attributes. * @param x The column to draw at. * @param y The row to draw at. */ - private _drawUncachedChars(terminal: ITerminal, cell: ICellData, x: number, y: number): void { + private _drawUncachedChars(cell: ICellData, x: number, y: number): void { this._ctx.save(); - this._ctx.font = this._getFont(terminal, !!cell.isBold(), !!cell.isItalic()); + this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); this._ctx.textBaseline = 'middle'; if (cell.isInverse()) { @@ -337,14 +336,14 @@ export abstract class BaseRenderLayer implements IRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { let fg = cell.getFgColor(); - if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } this._ctx.fillStyle = this._colors.ansi[fg].css; } } - this._clipRow(terminal, y); + this._clipRow(y); // Apply alpha to dim the character if (cell.isDim()) { @@ -360,29 +359,27 @@ export abstract class BaseRenderLayer implements IRenderLayer { /** * Clips a row to ensure no pixels will be drawn outside the cells in the row. - * @param terminal The terminal. * @param y The row to clip. */ - private _clipRow(terminal: ITerminal, y: number): void { + private _clipRow(y: number): void { this._ctx.beginPath(); this._ctx.rect( 0, y * this._scaledCellHeight, - terminal.cols * this._scaledCellWidth, + this._bufferService.cols * this._scaledCellWidth, this._scaledCellHeight); this._ctx.clip(); } /** * Gets the current font. - * @param terminal The terminal. * @param isBold If we should use the bold fontWeight. */ - protected _getFont(terminal: ITerminal, isBold: boolean, isItalic: boolean): string { - const fontWeight = isBold ? terminal.options.fontWeightBold : terminal.options.fontWeight; + protected _getFont(isBold: boolean, isItalic: boolean): string { + const fontWeight = isBold ? this._optionsService.options.fontWeightBold : this._optionsService.options.fontWeight; const fontStyle = isItalic ? 'italic' : ''; - return `${fontStyle} ${fontWeight} ${terminal.options.fontSize * window.devicePixelRatio}px ${terminal.options.fontFamily}`; + return `${fontStyle} ${fontWeight} ${this._optionsService.options.fontSize * window.devicePixelRatio}px ${this._optionsService.options.fontFamily}`; } } diff --git a/src/renderer/LinkRenderLayer.ts b/src/browser/renderer/LinkRenderLayer.ts similarity index 65% rename from src/renderer/LinkRenderLayer.ts rename to src/browser/renderer/LinkRenderLayer.ts index 6e6ba2e4..a7be54ee 100644 --- a/src/renderer/LinkRenderLayer.ts +++ b/src/browser/renderer/LinkRenderLayer.ts @@ -3,29 +3,37 @@ * @license MIT */ -import { ITerminal, ILinkifierAccessor } from '../Types'; import { IRenderDimensions } from 'browser/renderer/Types'; import { BaseRenderLayer } from './BaseRenderLayer'; import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { is256Color } from './atlas/CharAtlasUtils'; -import { IColorSet, ILinkifierEvent } from 'browser/Types'; +import { is256Color } from 'browser/renderer/atlas/CharAtlasUtils'; +import { IColorSet, ILinkifierEvent, ILinkifier } from 'browser/Types'; +import { IBufferService, IOptionsService } from 'common/services/Services'; export class LinkRenderLayer extends BaseRenderLayer { - private _state: ILinkifierEvent = null; + private _state: ILinkifierEvent | undefined; - constructor(container: HTMLElement, zIndex: number, colors: IColorSet, terminal: ILinkifierAccessor) { - super(container, 'link', zIndex, true, colors); - terminal.linkifier.onLinkHover(e => this._onLinkHover(e)); - terminal.linkifier.onLinkLeave(e => this._onLinkLeave(e)); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + rendererId: number, + linkifier: ILinkifier, + readonly bufferService: IBufferService, + readonly optionsService: IOptionsService + ) { + super(container, 'link', zIndex, true, colors, rendererId, bufferService, optionsService); + linkifier.onLinkHover(e => this._onLinkHover(e)); + linkifier.onLinkLeave(e => this._onLinkLeave(e)); } - public resize(terminal: ITerminal, dim: IRenderDimensions): void { - super.resize(terminal, dim); + public resize(dim: IRenderDimensions): void { + super.resize(dim); // Resizing the canvas discards the contents of the canvas so clear state - this._state = null; + this._state = undefined; } - public reset(terminal: ITerminal): void { + public reset(): void { this._clearCurrentLink(); } @@ -37,14 +45,14 @@ export class LinkRenderLayer extends BaseRenderLayer { this._clearCells(0, this._state.y1 + 1, this._state.cols, middleRowCount); } this._clearCells(0, this._state.y2, this._state.x2, 1); - this._state = null; + this._state = undefined; } } private _onLinkHover(e: ILinkifierEvent): void { if (e.fg === INVERTED_DEFAULT_COLOR) { this._ctx.fillStyle = this._colors.background.css; - } else if (is256Color(e.fg)) { + } else if (e.fg && is256Color(e.fg)) { // 256 color support this._ctx.fillStyle = this._colors.ansi[e.fg].css; } else { diff --git a/src/browser/renderer/RendererUtils.ts b/src/browser/renderer/RendererUtils.ts new file mode 100644 index 00000000..48fd26a4 --- /dev/null +++ b/src/browser/renderer/RendererUtils.ts @@ -0,0 +1,11 @@ +/** + * Copyright (c) 2019 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export function throwIfFalsy(value: T | undefined | null): T { + if (!value) { + throw new Error('value must not be falsy'); + } + return value; +} diff --git a/src/renderer/SelectionRenderLayer.ts b/src/browser/renderer/SelectionRenderLayer.ts similarity index 61% rename from src/renderer/SelectionRenderLayer.ts rename to src/browser/renderer/SelectionRenderLayer.ts index 20b86a3f..5cd2fd91 100644 --- a/src/renderer/SelectionRenderLayer.ts +++ b/src/browser/renderer/SelectionRenderLayer.ts @@ -3,51 +3,58 @@ * @license MIT */ -import { ITerminal } from '../Types'; import { IRenderDimensions } from 'browser/renderer/Types'; -import { BaseRenderLayer } from './BaseRenderLayer'; +import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { IColorSet } from 'browser/Types'; +import { IBufferService, IOptionsService } from 'common/services/Services'; interface ISelectionState { - start: [number, number]; - end: [number, number]; - columnSelectMode: boolean; - ydisp: number; + start?: [number, number]; + end?: [number, number]; + columnSelectMode?: boolean; + ydisp?: number; } export class SelectionRenderLayer extends BaseRenderLayer { - private _state: ISelectionState; + private _state!: ISelectionState; - constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'selection', zIndex, true, colors); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + rendererId: number, + readonly bufferService: IBufferService, + readonly optionsService: IOptionsService + ) { + super(container, 'selection', zIndex, true, colors, rendererId, bufferService, optionsService); this._clearState(); } private _clearState(): void { this._state = { - start: null, - end: null, - columnSelectMode: null, - ydisp: null + start: undefined, + end: undefined, + columnSelectMode: undefined, + ydisp: undefined }; } - public resize(terminal: ITerminal, dim: IRenderDimensions): void { - super.resize(terminal, dim); + public resize(dim: IRenderDimensions): void { + super.resize(dim); // Resizing the canvas discards the contents of the canvas so clear state this._clearState(); } - public reset(terminal: ITerminal): void { + public reset(): void { if (this._state.start && this._state.end) { this._clearState(); this._clearAll(); } } - public onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void { + public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void { // Selection has not changed - if (!this._didStateChange(start, end, columnSelectMode, terminal.buffer.ydisp)) { + if (!this._didStateChange(start, end, columnSelectMode, this._bufferService.buffer.ydisp)) { return; } @@ -61,13 +68,13 @@ export class SelectionRenderLayer extends BaseRenderLayer { } // Translate from buffer position to viewport position - const viewportStartRow = start[1] - terminal.buffer.ydisp; - const viewportEndRow = end[1] - terminal.buffer.ydisp; + const viewportStartRow = start[1] - this._bufferService.buffer.ydisp; + const viewportEndRow = end[1] - this._bufferService.buffer.ydisp; const viewportCappedStartRow = Math.max(viewportStartRow, 0); - const viewportCappedEndRow = Math.min(viewportEndRow, terminal.rows - 1); + const viewportCappedEndRow = Math.min(viewportEndRow, this._bufferService.rows - 1); // No need to draw the selection - if (viewportCappedStartRow >= terminal.rows || viewportCappedEndRow < 0) { + if (viewportCappedStartRow >= this._bufferService.rows || viewportCappedEndRow < 0) { return; } @@ -81,17 +88,17 @@ export class SelectionRenderLayer extends BaseRenderLayer { } else { // Draw first row const startCol = viewportStartRow === viewportCappedStartRow ? start[0] : 0; - const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : terminal.cols; + const startRowEndCol = viewportCappedStartRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; this._fillCells(startCol, viewportCappedStartRow, startRowEndCol - startCol, 1); // Draw middle rows const middleRowsCount = Math.max(viewportCappedEndRow - viewportCappedStartRow - 1, 0); - this._fillCells(0, viewportCappedStartRow + 1, terminal.cols, middleRowsCount); + this._fillCells(0, viewportCappedStartRow + 1, this._bufferService.cols, middleRowsCount); // Draw final row if (viewportCappedStartRow !== viewportCappedEndRow) { // Only draw viewportEndRow if it's not the same as viewportStartRow - const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : terminal.cols; + const endCol = viewportEndRow === viewportCappedEndRow ? end[0] : this._bufferService.cols; this._fillCells(0, viewportCappedEndRow, endCol, 1); } } @@ -100,7 +107,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { this._state.start = [start[0], start[1]]; this._state.end = [end[0], end[1]]; this._state.columnSelectMode = columnSelectMode; - this._state.ydisp = terminal.buffer.ydisp; + this._state.ydisp = this._bufferService.buffer.ydisp; } private _didStateChange(start: [number, number], end: [number, number], columnSelectMode: boolean, ydisp: number): boolean { @@ -110,7 +117,7 @@ export class SelectionRenderLayer extends BaseRenderLayer { ydisp !== this._state.ydisp; } - private _areCoordinatesEqual(coord1: [number, number], coord2: [number, number]): boolean { + private _areCoordinatesEqual(coord1: [number, number] | undefined, coord2: [number, number] | undefined): boolean { if (!coord1 || !coord2) { return false; } diff --git a/src/renderer/TextRenderLayer.ts b/src/browser/renderer/TextRenderLayer.ts similarity index 80% rename from src/renderer/TextRenderLayer.ts rename to src/browser/renderer/TextRenderLayer.ts index 03e5c112..330400ca 100644 --- a/src/renderer/TextRenderLayer.ts +++ b/src/browser/renderer/TextRenderLayer.ts @@ -4,15 +4,15 @@ */ import { ICharacterJoinerRegistry, IRenderDimensions } from 'browser/renderer/Types'; -import { ITerminal } from '../Types'; import { CharData, ICellData } from 'common/Types'; import { GridCache } from 'browser/renderer/GridCache'; -import { BaseRenderLayer } from './BaseRenderLayer'; +import { BaseRenderLayer } from 'browser/renderer/BaseRenderLayer'; import { AttributeData } from 'common/buffer/AttributeData'; import { NULL_CELL_CODE, Content } from 'common/buffer/Constants'; import { JoinedCellData } from 'browser/renderer/CharacterJoinerRegistry'; import { IColorSet } from 'browser/Types'; import { CellData } from 'common/buffer/CellData'; +import { IOptionsService, IBufferService } from 'common/services/Services'; /** * This CharData looks like a null character, which will forc a clear and render @@ -23,23 +23,32 @@ import { CellData } from 'common/buffer/CellData'; export class TextRenderLayer extends BaseRenderLayer { private _state: GridCache; - private _characterWidth: number; - private _characterFont: string; + private _characterWidth: number = 0; + private _characterFont: string = ''; private _characterOverlapCache: { [key: string]: boolean } = {}; private _characterJoinerRegistry: ICharacterJoinerRegistry; private _workCell = new CellData(); - constructor(container: HTMLElement, zIndex: number, colors: IColorSet, characterJoinerRegistry: ICharacterJoinerRegistry, alpha: boolean) { - super(container, 'text', zIndex, alpha, colors); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + characterJoinerRegistry: ICharacterJoinerRegistry, + alpha: boolean, + rendererId: number, + readonly bufferService: IBufferService, + readonly optionsService: IOptionsService + ) { + super(container, 'text', zIndex, alpha, colors, rendererId, bufferService, optionsService); this._state = new GridCache(); this._characterJoinerRegistry = characterJoinerRegistry; } - public resize(terminal: ITerminal, dim: IRenderDimensions): void { - super.resize(terminal, dim); + public resize(dim: IRenderDimensions): void { + super.resize(dim); // Clear the character width cache if the font or width has changed - const terminalFont = this._getFont(terminal, false, false); + const terminalFont = this._getFont(false, false); if (this._characterWidth !== dim.scaledCharWidth || this._characterFont !== terminalFont) { this._characterWidth = dim.scaledCharWidth; this._characterFont = terminalFont; @@ -47,16 +56,15 @@ export class TextRenderLayer extends BaseRenderLayer { } // Resizing the canvas discards the contents of the canvas so clear state this._state.clear(); - this._state.resize(terminal.cols, terminal.rows); + this._state.resize(this._bufferService.cols, this._bufferService.rows); } - public reset(terminal: ITerminal): void { + public reset(): void { this._state.clear(); this._clearAll(); } private _forEachCell( - terminal: ITerminal, firstRow: number, lastRow: number, joinerRegistry: ICharacterJoinerRegistry | null, @@ -67,11 +75,11 @@ export class TextRenderLayer extends BaseRenderLayer { ) => void ): void { for (let y = firstRow; y <= lastRow; y++) { - const row = y + terminal.buffer.ydisp; - const line = terminal.buffer.lines.get(row); + const row = y + this._bufferService.buffer.ydisp; + const line = this._bufferService.buffer.lines.get(row); const joinedRanges = joinerRegistry ? joinerRegistry.getJoinedCharacters(row) : []; - for (let x = 0; x < terminal.cols; x++) { - line.loadCell(x, this._workCell); + for (let x = 0; x < this._bufferService.cols; x++) { + line!.loadCell(x, this._workCell); let cell = this._workCell; // If true, indicates that the current character(s) to draw were joined. @@ -89,14 +97,14 @@ export class TextRenderLayer extends BaseRenderLayer { // and attributes of our input. if (joinedRanges.length > 0 && x === joinedRanges[0][0]) { isJoined = true; - const range = joinedRanges.shift(); + const range = joinedRanges.shift()!; // We already know the exact start and end column of the joined range, // so we get the string and width representing it directly cell = new JoinedCellData( this._workCell, - line.translateToString(true, range[0], range[1]), + line!.translateToString(true, range[0], range[1]), range[1] - range[0] ); @@ -116,7 +124,7 @@ export class TextRenderLayer extends BaseRenderLayer { // get removed, and `a` would not re-render because it thinks it's // already in the correct state. // this._state.cache[x][y] = OVERLAP_OWNED_CHAR_DATA; - if (lastCharX < line.length - 1 && line.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { + if (lastCharX < line!.length - 1 && line!.getCodePoint(lastCharX + 1) === NULL_CELL_CODE) { // patch width to 2 cell.content &= ~Content.WIDTH_MASK; cell.content |= 2 << Content.WIDTH_SHIFT; @@ -143,16 +151,16 @@ export class TextRenderLayer extends BaseRenderLayer { * Draws the background for a specified range of columns. Tries to batch adjacent cells of the * same color together to reduce draw calls. */ - private _drawBackground(terminal: ITerminal, firstRow: number, lastRow: number): void { + private _drawBackground(firstRow: number, lastRow: number): void { const ctx = this._ctx; - const cols = terminal.cols; + const cols = this._bufferService.cols; let startX: number = 0; let startY: number = 0; let prevFillStyle: string | null = null; ctx.save(); - this._forEachCell(terminal, firstRow, lastRow, null, (cell, x, y) => { + this._forEachCell(firstRow, lastRow, null, (cell, x, y) => { // libvte and xterm both draw the background (but not foreground) of invisible characters, // so we should too. let nextFillStyle = null; // null represents default background color @@ -176,15 +184,17 @@ export class TextRenderLayer extends BaseRenderLayer { // don't need to draw anything. startX = x; startY = y; - } if (y !== startY) { + } + + if (y !== startY) { // our row changed, draw the previous row - ctx.fillStyle = prevFillStyle; + ctx.fillStyle = prevFillStyle ? prevFillStyle : ''; this._fillCells(startX, startY, cols - startX, 1); startX = x; startY = y; } else if (prevFillStyle !== nextFillStyle) { // our color changed, draw the previous characters in this row - ctx.fillStyle = prevFillStyle; + ctx.fillStyle = prevFillStyle ? prevFillStyle : ''; this._fillCells(startX, startY, x - startX, 1); startX = x; startY = y; @@ -202,12 +212,12 @@ export class TextRenderLayer extends BaseRenderLayer { ctx.restore(); } - private _drawForeground(terminal: ITerminal, firstRow: number, lastRow: number): void { - this._forEachCell(terminal, firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => { + private _drawForeground(firstRow: number, lastRow: number): void { + this._forEachCell(firstRow, lastRow, this._characterJoinerRegistry, (cell, x, y) => { if (cell.isInvisible()) { return; } - this._drawChars(terminal, cell, x, y); + this._drawChars(cell, x, y); if (cell.isUnderline()) { this._ctx.save(); @@ -226,7 +236,7 @@ export class TextRenderLayer extends BaseRenderLayer { this._ctx.fillStyle = `rgb(${AttributeData.toColorRGB(cell.getFgColor()).join(',')})`; } else { let fg = cell.getFgColor(); - if (terminal.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { + if (this._optionsService.options.drawBoldTextInBrightColors && cell.isBold() && fg < 8) { fg += 8; } this._ctx.fillStyle = this._colors.ansi[fg].css; @@ -239,7 +249,7 @@ export class TextRenderLayer extends BaseRenderLayer { }); } - public onGridChanged(terminal: ITerminal, firstRow: number, lastRow: number): void { + public onGridChanged(firstRow: number, lastRow: number): void { // Resize has not been called yet if (this._state.cache.length === 0) { return; @@ -249,13 +259,13 @@ export class TextRenderLayer extends BaseRenderLayer { this._charAtlas.beginFrame(); } - this._clearCells(0, firstRow, terminal.cols, lastRow - firstRow + 1); - this._drawBackground(terminal, firstRow, lastRow); - this._drawForeground(terminal, firstRow, lastRow); + this._clearCells(0, firstRow, this._bufferService.cols, lastRow - firstRow + 1); + this._drawBackground(firstRow, lastRow); + this._drawForeground(firstRow, lastRow); } - public onOptionsChanged(terminal: ITerminal): void { - this._setTransparency(terminal, terminal.options.allowTransparency); + public onOptionsChanged(): void { + this._setTransparency(this._optionsService.options.allowTransparency); } /** diff --git a/src/browser/renderer/Types.d.ts b/src/browser/renderer/Types.d.ts index df8f0031..7f84dfd4 100644 --- a/src/browser/renderer/Types.d.ts +++ b/src/browser/renderer/Types.d.ts @@ -56,3 +56,61 @@ export interface ICharacterJoinerRegistry { deregisterCharacterJoiner(joinerId: number): boolean; getJoinedCharacters(row: number): [number, number][]; } + +export interface IRenderLayer extends IDisposable { + /** + * Called when the terminal loses focus. + */ + onBlur(): void; + + /** + * * Called when the terminal gets focus. + */ + onFocus(): void; + + /** + * Called when the cursor is moved. + */ + onCursorMove(): void; + + /** + * Called when options change. + */ + onOptionsChanged(): void; + + /** + * Called when the theme changes. + */ + setColors(colorSet: IColorSet): void; + + /** + * Called when the data in the grid has changed (or needs to be rendered + * again). + */ + onGridChanged(startRow: number, endRow: number): void; + + /** + * Calls when the selection changes. + */ + onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean): void; + + /** + * Registers a handler to join characters to render as a group + */ + registerCharacterJoiner?(joiner: ICharacterJoiner): void; + + /** + * Deregisters the specified character joiner handler + */ + deregisterCharacterJoiner?(joinerId: number): void; + + /** + * Resize the render layer. + */ + resize(dim: IRenderDimensions): void; + + /** + * Clear the state of the render layer. + */ + reset(): void; +} diff --git a/src/renderer/atlas/BaseCharAtlas.ts b/src/browser/renderer/atlas/BaseCharAtlas.ts similarity index 93% rename from src/renderer/atlas/BaseCharAtlas.ts rename to src/browser/renderer/atlas/BaseCharAtlas.ts index 470736f1..dc114601 100644 --- a/src/renderer/atlas/BaseCharAtlas.ts +++ b/src/browser/renderer/atlas/BaseCharAtlas.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { IGlyphIdentifier } from './Types'; -import { IDisposable } from 'xterm'; +import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; +import { IDisposable } from 'common/Types'; export abstract class BaseCharAtlas implements IDisposable { private _didWarmUp: boolean = false; diff --git a/src/renderer/atlas/CharAtlasCache.ts b/src/browser/renderer/atlas/CharAtlasCache.ts similarity index 67% rename from src/renderer/atlas/CharAtlasCache.ts rename to src/browser/renderer/atlas/CharAtlasCache.ts index 43b5eb83..257835ba 100644 --- a/src/renderer/atlas/CharAtlasCache.ts +++ b/src/browser/renderer/atlas/CharAtlasCache.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { ITerminal } from '../../Types'; -import { generateConfig, configEquals } from './CharAtlasUtils'; -import { BaseCharAtlas } from './BaseCharAtlas'; -import { DynamicCharAtlas } from './DynamicCharAtlas'; -import { ICharAtlasConfig } from './Types'; +import { generateConfig, configEquals } from 'browser/renderer/atlas/CharAtlasUtils'; +import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; +import { DynamicCharAtlas } from 'browser/renderer/atlas/DynamicCharAtlas'; +import { ICharAtlasConfig } from 'browser/renderer/atlas/Types'; import { IColorSet } from 'browser/Types'; +import { ITerminalOptions } from 'common/services/Services'; interface ICharAtlasCacheEntry { atlas: BaseCharAtlas; config: ICharAtlasConfig; // N.B. This implementation potentially holds onto copies of the terminal forever, so // this may cause memory leaks. - ownedBy: ITerminal[]; + ownedBy: number[]; } const charAtlasCache: ICharAtlasCacheEntry[] = []; @@ -23,26 +23,25 @@ const charAtlasCache: ICharAtlasCacheEntry[] = []; /** * Acquires a char atlas, either generating a new one or returning an existing * one that is in use by another terminal. - * @param terminal The terminal. - * @param colors The colors to use. */ export function acquireCharAtlas( - terminal: ITerminal, + options: ITerminalOptions, + rendererId: number, colors: IColorSet, scaledCharWidth: number, scaledCharHeight: number ): BaseCharAtlas { - const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, terminal, colors); + const newConfig = generateConfig(scaledCharWidth, scaledCharHeight, options, colors); - // Check to see if the terminal already owns this config + // Check to see if the renderer already owns this config for (let i = 0; i < charAtlasCache.length; i++) { const entry = charAtlasCache[i]; - const ownedByIndex = entry.ownedBy.indexOf(terminal); + const ownedByIndex = entry.ownedBy.indexOf(rendererId); if (ownedByIndex >= 0) { if (configEquals(entry.config, newConfig)) { return entry.atlas; } - // The configs differ, release the terminal from the entry + // The configs differ, release the renderer from the entry if (entry.ownedBy.length === 1) { entry.atlas.dispose(); charAtlasCache.splice(i, 1); @@ -57,8 +56,8 @@ export function acquireCharAtlas( for (let i = 0; i < charAtlasCache.length; i++) { const entry = charAtlasCache[i]; if (configEquals(entry.config, newConfig)) { - // Add the terminal to the cache entry and return - entry.ownedBy.push(terminal); + // Add the renderer to the cache entry and return + entry.ownedBy.push(rendererId); return entry.atlas; } } @@ -69,7 +68,7 @@ export function acquireCharAtlas( newConfig ), config: newConfig, - ownedBy: [terminal] + ownedBy: [rendererId] }; charAtlasCache.push(newEntry); return newEntry.atlas; @@ -77,14 +76,13 @@ export function acquireCharAtlas( /** * Removes a terminal reference from the cache, allowing its memory to be freed. - * @param terminal The terminal to remove. */ -export function removeTerminalFromCache(terminal: ITerminal): void { +export function removeTerminalFromCache(rendererId: number): void { for (let i = 0; i < charAtlasCache.length; i++) { - const index = charAtlasCache[i].ownedBy.indexOf(terminal); + const index = charAtlasCache[i].ownedBy.indexOf(rendererId); if (index !== -1) { if (charAtlasCache[i].ownedBy.length === 1) { - // Remove the cache entry if it's the only terminal + // Remove the cache entry if it's the only renderer charAtlasCache[i].atlas.dispose(); charAtlasCache.splice(i, 1); } else { diff --git a/src/renderer/atlas/CharAtlasUtils.ts b/src/browser/renderer/atlas/CharAtlasUtils.ts similarity index 70% rename from src/renderer/atlas/CharAtlasUtils.ts rename to src/browser/renderer/atlas/CharAtlasUtils.ts index ee787a7b..346b35f8 100644 --- a/src/renderer/atlas/CharAtlasUtils.ts +++ b/src/browser/renderer/atlas/CharAtlasUtils.ts @@ -3,19 +3,19 @@ * @license MIT */ -import { ITerminal } from '../../Types'; -import { ICharAtlasConfig } from './Types'; +import { ICharAtlasConfig } from 'browser/renderer/atlas/Types'; import { DEFAULT_COLOR } from 'common/buffer/Constants'; -import { IColorSet } from 'browser/Types'; +import { IColorSet, IPartialColorSet } from 'browser/Types'; +import { ITerminalOptions } from 'common/services/Services'; -export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, terminal: ITerminal, colors: IColorSet): ICharAtlasConfig { +export function generateConfig(scaledCharWidth: number, scaledCharHeight: number, options: ITerminalOptions, colors: IColorSet): ICharAtlasConfig { // null out some fields that don't matter - const clonedColors = { + const clonedColors = { foreground: colors.foreground, background: colors.background, - cursor: null, - cursorAccent: null, - selection: null, + cursor: undefined, + cursorAccent: undefined, + selection: undefined, // For the static char atlas, we only use the first 16 colors, but we need all 256 for the // dynamic character atlas. ansi: colors.ansi.slice(0, 16) @@ -24,11 +24,11 @@ export function generateConfig(scaledCharWidth: number, scaledCharHeight: number devicePixelRatio: window.devicePixelRatio, scaledCharWidth, scaledCharHeight, - fontFamily: terminal.options.fontFamily, - fontSize: terminal.options.fontSize, - fontWeight: terminal.options.fontWeight, - fontWeightBold: terminal.options.fontWeightBold, - allowTransparency: terminal.options.allowTransparency, + fontFamily: options.fontFamily, + fontSize: options.fontSize, + fontWeight: options.fontWeight, + fontWeightBold: options.fontWeightBold, + allowTransparency: options.allowTransparency, colors: clonedColors }; } diff --git a/src/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts similarity index 96% rename from src/renderer/atlas/DynamicCharAtlas.ts rename to src/browser/renderer/atlas/DynamicCharAtlas.ts index 6e669d91..40103bc7 100644 --- a/src/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -3,13 +3,14 @@ * @license MIT */ -import { IGlyphIdentifier, ICharAtlasConfig } from './Types'; import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; -import { BaseCharAtlas } from './BaseCharAtlas'; +import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types'; +import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; import { LRUMap } from 'browser/renderer/atlas/LRUMap'; import { isFirefox, isSafari } from 'common/Platform'; import { IColor } from 'browser/Types'; +import { throwIfFalsy } from 'browser/renderer/RendererUtils'; // 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. @@ -89,12 +90,12 @@ export class DynamicCharAtlas extends BaseCharAtlas { // The canvas needs alpha because we use clearColor to convert the background color to alpha. // It might also contain some characters with transparent backgrounds if allowTransparency is // set. - this._cacheCtx = this._cacheCanvas.getContext('2d', {alpha: true}); + this._cacheCtx = throwIfFalsy(this._cacheCanvas.getContext('2d', {alpha: true})); const tmpCanvas = document.createElement('canvas'); tmpCanvas.width = this._config.scaledCharWidth; tmpCanvas.height = this._config.scaledCharHeight; - this._tmpCtx = tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}); + this._tmpCtx = throwIfFalsy(tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency})); this._width = Math.floor(TEXTURE_WIDTH / this._config.scaledCharWidth); this._height = Math.floor(TEXTURE_HEIGHT / this._config.scaledCharHeight); @@ -144,7 +145,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { index = this._cacheMap.size; } else { // we're out of space, so our call to set will delete this item - index = this._cacheMap.peek().index; + index = this._cacheMap.peek()!.index; } const cacheValue = this._drawToCache(glyph, index); this._cacheMap.set(glyphKey, cacheValue); @@ -186,7 +187,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { const cacheX = this._toCoordinateX(cacheValue.index); const cacheY = this._toCoordinateY(cacheValue.index); ctx.drawImage( - cacheValue.inBitmap ? this._bitmap : this._cacheCanvas, + cacheValue.inBitmap ? this._bitmap! : this._cacheCanvas, cacheX, cacheY, this._config.scaledCharWidth, diff --git a/src/renderer/atlas/Types.d.ts b/src/browser/renderer/atlas/Types.d.ts similarity index 78% rename from src/renderer/atlas/Types.d.ts rename to src/browser/renderer/atlas/Types.d.ts index 1de843e0..d8bc54c1 100644 --- a/src/renderer/atlas/Types.d.ts +++ b/src/browser/renderer/atlas/Types.d.ts @@ -3,8 +3,8 @@ * @license MIT */ -import { FontWeight } from 'xterm'; -import { IColorSet } from 'browser/Types'; +import { FontWeight } from 'common/services/Services'; +import { IPartialColorSet } from 'browser/Types'; export interface IGlyphIdentifier { chars: string; @@ -25,5 +25,5 @@ export interface ICharAtlasConfig { scaledCharWidth: number; scaledCharHeight: number; allowTransparency: boolean; - colors: IColorSet; + colors: IPartialColorSet; } diff --git a/src/browser/services/RenderService.ts b/src/browser/services/RenderService.ts index ecf20e28..c0b3c0e5 100644 --- a/src/browser/services/RenderService.ts +++ b/src/browser/services/RenderService.ts @@ -103,6 +103,10 @@ export class RenderService extends Disposable implements IRenderService { this._onDimensionsChange.fire(this._renderer.dimensions); } + public dispose(): void { + this._renderer.dispose(); + } + public setRenderer(renderer: IRenderer): void { // TODO: RenderCoordinator should be the only one to dispose the renderer this._renderer.dispose(); diff --git a/src/browser/services/Services.ts b/src/browser/services/Services.ts index 863f3a9e..2170a04e 100644 --- a/src/browser/services/Services.ts +++ b/src/browser/services/Services.ts @@ -8,6 +8,7 @@ import { IRenderDimensions, IRenderer, CharacterJoinerHandler } from 'browser/re import { IColorSet } from 'browser/Types'; import { ISelectionRedrawRequestEvent } from 'browser/selection/Types'; import { createDecorator } from 'common/services/ServiceRegistry'; +import { IDisposable } from 'common/Types'; export const ICharSizeService = createDecorator('CharSizeService'); export interface ICharSizeService { @@ -31,7 +32,7 @@ export interface IMouseService { } export const IRenderService = createDecorator('RenderService'); -export interface IRenderService { +export interface IRenderService extends IDisposable { serviceBrand: any; onDimensionsChange: IEvent; diff --git a/src/common/services/CoreService.ts b/src/common/services/CoreService.ts index 674cfedb..d17b0c93 100644 --- a/src/common/services/CoreService.ts +++ b/src/common/services/CoreService.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ICoreService, IOptionsService, IBufferService } from 'common/services/Services'; +import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { IDecPrivateModes } from 'common/Types'; import { clone } from 'common/Clone'; @@ -26,6 +26,7 @@ export class CoreService implements ICoreService { // TODO: Move this into a service private readonly _scrollToBottom: () => void, @IBufferService private readonly _bufferService: IBufferService, + @ILogService private readonly _logService: ILogService, @IOptionsService private readonly _optionsService: IOptionsService ) { this.decPrivateModes = clone(DEFAULT_DEC_PRIVATE_MODES); @@ -53,6 +54,7 @@ export class CoreService implements ICoreService { } // Fire onData API + this._logService.debug('sending data', data); this._onData.fire(data); } } diff --git a/src/renderer/CursorRenderLayer.ts b/src/renderer/CursorRenderLayer.ts index b0e43fdc..f847c5f6 100644 --- a/src/renderer/CursorRenderLayer.ts +++ b/src/renderer/CursorRenderLayer.ts @@ -4,11 +4,12 @@ */ import { IRenderDimensions } from 'browser/renderer/Types'; -import { BaseRenderLayer } from './BaseRenderLayer'; +import { BaseRenderLayer } from '../browser/renderer/BaseRenderLayer'; import { ITerminal } from '../Types'; import { ICellData } from 'common/Types'; import { CellData } from 'common/buffer/CellData'; import { IColorSet } from 'browser/Types'; +import { IBufferService, IOptionsService } from 'common/services/Services'; interface ICursorState { x: number; @@ -25,12 +26,20 @@ const BLINK_INTERVAL = 600; export class CursorRenderLayer extends BaseRenderLayer { private _state: ICursorState; - private _cursorRenderers: {[key: string]: (terminal: ITerminal, x: number, y: number, cell: ICellData) => void}; + private _cursorRenderers: {[key: string]: (x: number, y: number, cell: ICellData) => void}; private _cursorBlinkStateManager: CursorBlinkStateManager; private _cell: ICellData = new CellData(); - constructor(container: HTMLElement, zIndex: number, colors: IColorSet) { - super(container, 'cursor', zIndex, true, colors); + constructor( + container: HTMLElement, + zIndex: number, + colors: IColorSet, + private _terminal: ITerminal, + rendererId: number, + readonly bufferService: IBufferService, + readonly optionsService: IOptionsService + ) { + super(container, 'cursor', zIndex, true, colors, rendererId, bufferService, optionsService); this._state = { x: null, y: null, @@ -46,8 +55,8 @@ export class CursorRenderLayer extends BaseRenderLayer { // TODO: Consider initial options? Maybe onOptionsChanged should be called at the end of open? } - public resize(terminal: ITerminal, dim: IRenderDimensions): void { - super.resize(terminal, dim); + public resize(dim: IRenderDimensions): void { + super.resize(dim); // Resizing the canvas discards the contents of the canvas so clear state this._state = { x: null, @@ -58,35 +67,35 @@ export class CursorRenderLayer extends BaseRenderLayer { }; } - public reset(terminal: ITerminal): void { + public reset(): void { this._clearCursor(); if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.dispose(); this._cursorBlinkStateManager = null; - this.onOptionsChanged(terminal); + this.onOptionsChanged(); } } - public onBlur(terminal: ITerminal): void { + public onBlur(): void { if (this._cursorBlinkStateManager) { this._cursorBlinkStateManager.pause(); } - terminal.refresh(terminal.buffer.y, terminal.buffer.y); + this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); } - public onFocus(terminal: ITerminal): void { + public onFocus(): void { if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.resume(terminal); + this._cursorBlinkStateManager.resume(); } else { - terminal.refresh(terminal.buffer.y, terminal.buffer.y); + this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); } } - public onOptionsChanged(terminal: ITerminal): void { - if (terminal.options.cursorBlink) { + public onOptionsChanged(): void { + if (this._optionsService.options.cursorBlink) { if (!this._cursorBlinkStateManager) { - this._cursorBlinkStateManager = new CursorBlinkStateManager(terminal, () => { - this._render(terminal, true); + this._cursorBlinkStateManager = new CursorBlinkStateManager(this._terminal.isFocused, () => { + this._render(true); }); } } else { @@ -96,55 +105,55 @@ export class CursorRenderLayer extends BaseRenderLayer { } // Request a refresh from the terminal as management of rendering is being // moved back to the terminal - terminal.refresh(terminal.buffer.y, terminal.buffer.y); + this._terminal.refresh(this._bufferService.buffer.y, this._bufferService.buffer.y); } } - public onCursorMove(terminal: ITerminal): void { + public onCursorMove(): void { if (this._cursorBlinkStateManager) { - this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + this._cursorBlinkStateManager.restartBlinkAnimation(); } } - public onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void { + public onGridChanged(startRow: number, endRow: number): void { if (!this._cursorBlinkStateManager || this._cursorBlinkStateManager.isPaused) { - this._render(terminal, false); + this._render(false); } else { - this._cursorBlinkStateManager.restartBlinkAnimation(terminal); + this._cursorBlinkStateManager.restartBlinkAnimation(); } } - private _render(terminal: ITerminal, triggeredByAnimationFrame: boolean): void { + private _render(triggeredByAnimationFrame: boolean): void { // Don't draw the cursor if it's hidden - if (!terminal.cursorState || terminal.cursorHidden) { + if (!this._terminal.cursorState || this._terminal.cursorHidden) { this._clearCursor(); return; } - const cursorY = terminal.buffer.ybase + terminal.buffer.y; - const viewportRelativeCursorY = cursorY - terminal.buffer.ydisp; + const cursorY = this._bufferService.buffer.ybase + this._bufferService.buffer.y; + const viewportRelativeCursorY = cursorY - this._bufferService.buffer.ydisp; // Don't draw the cursor if it's off-screen - if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= terminal.rows) { + if (viewportRelativeCursorY < 0 || viewportRelativeCursorY >= this._bufferService.rows) { this._clearCursor(); return; } - terminal.buffer.lines.get(cursorY).loadCell(terminal.buffer.x, this._cell); + this._bufferService.buffer.lines.get(cursorY).loadCell(this._bufferService.buffer.x, this._cell); if (this._cell.content === undefined) { return; } - if (!terminal.isFocused) { + if (!this._terminal.isFocused) { this._clearCursor(); this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; - this._renderBlurCursor(terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); + this._renderBlurCursor(this._bufferService.buffer.x, viewportRelativeCursorY, this._cell); this._ctx.restore(); - this._state.x = terminal.buffer.x; + this._state.x = this._bufferService.buffer.x; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; - this._state.style = terminal.options.cursorStyle; + this._state.style = this._optionsService.options.cursorStyle; this._state.width = this._cell.getWidth(); return; } @@ -157,10 +166,10 @@ export class CursorRenderLayer extends BaseRenderLayer { if (this._state) { // The cursor is already in the correct spot, don't redraw - if (this._state.x === terminal.buffer.x && + if (this._state.x === this._bufferService.buffer.x && this._state.y === viewportRelativeCursorY && - this._state.isFocused === terminal.isFocused && - this._state.style === terminal.options.cursorStyle && + this._state.isFocused === this._terminal.isFocused && + this._state.style === this._optionsService.options.cursorStyle && this._state.width === this._cell.getWidth()) { return; } @@ -168,13 +177,13 @@ export class CursorRenderLayer extends BaseRenderLayer { } this._ctx.save(); - this._cursorRenderers[terminal.options.cursorStyle || 'block'](terminal, terminal.buffer.x, viewportRelativeCursorY, this._cell); + this._cursorRenderers[this._optionsService.options.cursorStyle || 'block'](this._bufferService.buffer.x, viewportRelativeCursorY, this._cell); this._ctx.restore(); - this._state.x = terminal.buffer.x; + this._state.x = this._bufferService.buffer.x; this._state.y = viewportRelativeCursorY; this._state.isFocused = false; - this._state.style = terminal.options.cursorStyle; + this._state.style = this._optionsService.options.cursorStyle; this._state.width = this._cell.getWidth(); } @@ -191,30 +200,30 @@ export class CursorRenderLayer extends BaseRenderLayer { } } - private _renderBarCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { + private _renderBarCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; this._fillLeftLineAtCell(x, y); this._ctx.restore(); } - private _renderBlockCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { + private _renderBlockCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; this._fillCells(x, y, cell.getWidth(), 1); this._ctx.fillStyle = this._colors.cursorAccent.css; - this._fillCharTrueColor(terminal, cell, x, y); + this._fillCharTrueColor(cell, x, y); this._ctx.restore(); } - private _renderUnderlineCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { + private _renderUnderlineCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.fillStyle = this._colors.cursor.css; this._fillBottomLineAtCells(x, y); this._ctx.restore(); } - private _renderBlurCursor(terminal: ITerminal, x: number, y: number, cell: ICellData): void { + private _renderBlurCursor(x: number, y: number, cell: ICellData): void { this._ctx.save(); this._ctx.strokeStyle = this._colors.cursor.css; this._strokeRectAtCell(x, y, cell.getWidth(), 1); @@ -237,11 +246,11 @@ class CursorBlinkStateManager { private _animationTimeRestarted: number; constructor( - terminal: ITerminal, + isFocused: boolean, private _renderCallback: () => void ) { this.isCursorVisible = true; - if (terminal.isFocused) { + if (isFocused) { this._restartInterval(); } } @@ -263,7 +272,7 @@ class CursorBlinkStateManager { } } - public restartBlinkAnimation(terminal: ITerminal): void { + public restartBlinkAnimation(): void { if (this.isPaused) { return; } @@ -346,9 +355,9 @@ class CursorBlinkStateManager { } } - public resume(terminal: ITerminal): void { + public resume(): void { this._animationTimeRestarted = null; this._restartInterval(); - this.restartBlinkAnimation(terminal); + this.restartBlinkAnimation(); } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2ed61b76..d3977af3 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -3,20 +3,24 @@ * @license MIT */ -import { TextRenderLayer } from './TextRenderLayer'; -import { SelectionRenderLayer } from './SelectionRenderLayer'; +import { TextRenderLayer } from '../browser/renderer/TextRenderLayer'; +import { SelectionRenderLayer } from '../browser/renderer/SelectionRenderLayer'; import { CursorRenderLayer } from './CursorRenderLayer'; -import { IRenderLayer } from './Types'; -import { IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types'; +import { IRenderLayer, IRenderer, IRenderDimensions, CharacterJoinerHandler, ICharacterJoinerRegistry } from 'browser/renderer/Types'; import { ITerminal } from '../Types'; -import { LinkRenderLayer } from './LinkRenderLayer'; +import { LinkRenderLayer } from '../browser/renderer/LinkRenderLayer'; import { CharacterJoinerRegistry } from 'browser/renderer/CharacterJoinerRegistry'; import { Disposable } from 'common/Lifecycle'; import { IColorSet } from 'browser/Types'; import { ICharSizeService } from 'browser/services/Services'; -import { IBufferService } from 'common/services/Services'; +import { IBufferService, IOptionsService } from 'common/services/Services'; +import { removeTerminalFromCache } from 'browser/renderer/atlas/CharAtlasCache'; + +let nextRendererId = 1; export class Renderer extends Disposable implements IRenderer { + private _id = nextRendererId++; + private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; private _characterJoinerRegistry: ICharacterJoinerRegistry; @@ -27,17 +31,18 @@ export class Renderer extends Disposable implements IRenderer { private _colors: IColorSet, private readonly _terminal: ITerminal, readonly bufferService: IBufferService, - private readonly _charSizeService: ICharSizeService + private readonly _charSizeService: ICharSizeService, + readonly optionsService: IOptionsService ) { super(); const allowTransparency = this._terminal.options.allowTransparency; this._characterJoinerRegistry = new CharacterJoinerRegistry(bufferService); this._renderLayers = [ - new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency), - new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors), - new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._terminal), - new CursorRenderLayer(this._terminal.screenElement, 3, this._colors) + new TextRenderLayer(this._terminal.screenElement, 0, this._colors, this._characterJoinerRegistry, allowTransparency, this._id, bufferService, optionsService), + new SelectionRenderLayer(this._terminal.screenElement, 1, this._colors, this._id, bufferService, optionsService), + new LinkRenderLayer(this._terminal.screenElement, 2, this._colors, this._id, this._terminal.linkifier, bufferService, optionsService), + new CursorRenderLayer(this._terminal.screenElement, 3, this._colors, this._terminal, this._id, bufferService, optionsService) ]; this.dimensions = { scaledCharWidth: null, @@ -61,6 +66,7 @@ export class Renderer extends Disposable implements IRenderer { public dispose(): void { super.dispose(); this._renderLayers.forEach(l => l.dispose()); + removeTerminalFromCache(this._id); } public onDevicePixelRatioChange(): void { @@ -77,8 +83,8 @@ export class Renderer extends Disposable implements IRenderer { // Clear layers and force a full render this._renderLayers.forEach(l => { - l.setColors(this._terminal, this._colors); - l.reset(this._terminal); + l.setColors(this._colors); + l.reset(); }); } @@ -87,7 +93,7 @@ export class Renderer extends Disposable implements IRenderer { this._updateDimensions(); // Resize all render layers - this._renderLayers.forEach(l => l.resize(this._terminal, this.dimensions)); + this._renderLayers.forEach(l => l.resize(this.dimensions)); // Resize the screen this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; @@ -99,27 +105,27 @@ export class Renderer extends Disposable implements IRenderer { } public onBlur(): void { - this._runOperation(l => l.onBlur(this._terminal)); + this._runOperation(l => l.onBlur()); } public onFocus(): void { - this._runOperation(l => l.onFocus(this._terminal)); + this._runOperation(l => l.onFocus()); } public onSelectionChanged(start: [number, number], end: [number, number], columnSelectMode: boolean = false): void { - this._runOperation(l => l.onSelectionChanged(this._terminal, start, end, columnSelectMode)); + this._runOperation(l => l.onSelectionChanged(start, end, columnSelectMode)); } public onCursorMove(): void { - this._runOperation(l => l.onCursorMove(this._terminal)); + this._runOperation(l => l.onCursorMove()); } public onOptionsChanged(): void { - this._runOperation(l => l.onOptionsChanged(this._terminal)); + this._runOperation(l => l.onOptionsChanged()); } public clear(): void { - this._runOperation(l => l.reset(this._terminal)); + this._runOperation(l => l.reset()); } private _runOperation(operation: (layer: IRenderLayer) => void): void { @@ -131,7 +137,7 @@ export class Renderer extends Disposable implements IRenderer { * necessary before queueing up the next one. */ public renderRows(start: number, end: number): void { - this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); + this._renderLayers.forEach(l => l.onGridChanged(start, end)); } /** diff --git a/src/renderer/Types.d.ts b/src/renderer/Types.d.ts deleted file mode 100644 index 1546248a..00000000 --- a/src/renderer/Types.d.ts +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ITerminal } from '../Types'; -import { IDisposable } from 'xterm'; -import { IColorSet } from 'browser/Types'; -import { IRenderDimensions, CharacterJoinerHandler, ICharacterJoiner } from 'browser/renderer/Types'; - -export interface IRenderLayer extends IDisposable { - /** - * Called when the terminal loses focus. - */ - onBlur(terminal: ITerminal): void; - - /** - * * Called when the terminal gets focus. - */ - onFocus(terminal: ITerminal): void; - - /** - * Called when the cursor is moved. - */ - onCursorMove(terminal: ITerminal): void; - - /** - * Called when options change. - */ - onOptionsChanged(terminal: ITerminal): void; - - /** - * Called when the theme changes. - */ - setColors(terminal: ITerminal, colorSet: IColorSet): void; - - /** - * Called when the data in the grid has changed (or needs to be rendered - * again). - */ - onGridChanged(terminal: ITerminal, startRow: number, endRow: number): void; - - /** - * Calls when the selection changes. - */ - onSelectionChanged(terminal: ITerminal, start: [number, number], end: [number, number], columnSelectMode: boolean): void; - - /** - * Registers a handler to join characters to render as a group - */ - registerCharacterJoiner?(joiner: ICharacterJoiner): void; - - /** - * Deregisters the specified character joiner handler - */ - deregisterCharacterJoiner?(joinerId: number): void; - - /** - * Resize the render layer. - */ - resize(terminal: ITerminal, dim: IRenderDimensions): void; - - /** - * Clear the state of the render layer. - */ - reset(terminal: ITerminal): void; -}