Merge pull request #4195 from Tyriar/4190

Create onSpecificOptionChange and onMultipleOptionChange helpers
This commit is contained in:
Daniel Imms
2022-10-09 11:04:59 -07:00
committed by GitHub
29 changed files with 243 additions and 166 deletions
@@ -79,7 +79,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
}
}
public handleOptionsChanged(): void {}
public handleBlur(): void {}
public handleFocus(): void {}
public handleCursorMove(): void {}
+14 -12
View File
@@ -4,7 +4,7 @@
*/
import { ICharacterJoinerService, ICharSizeService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { IColorSet, ITerminal } from 'browser/Types';
import { CanvasRenderer } from './CanvasRenderer';
import { IBufferService, ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ITerminalAddon, Terminal } from 'xterm';
@@ -23,25 +23,27 @@ export class CanvasAddon extends Disposable implements ITerminalAddon {
}
public activate(terminal: Terminal): void {
const core = (terminal as any)._core;
const core = (terminal as any)._core as ITerminal;
if (!terminal.element) {
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
this._terminal = terminal;
const bufferService: IBufferService = core._bufferService;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
const charSizeService: ICharSizeService = core._charSizeService;
const coreService: ICoreService = core.coreService;
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const decorationService: IDecorationService = core._decorationService;
const optionsService: IOptionsService = core.optionsService;
const themeService: IThemeService = core._themeService;
const screenElement: HTMLElement = core.screenElement;
const coreService = core.coreService;
const optionsService = core.optionsService;
const screenElement = core.screenElement!;
const linkifier = core.linkifier2;
const unsafeCore = core as any;
const bufferService: IBufferService = unsafeCore._bufferService;
const renderService: IRenderService = unsafeCore._renderService;
const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService;
const charSizeService: ICharSizeService = unsafeCore._charSizeService;
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
const decorationService: IDecorationService = unsafeCore._decorationService;
const themeService: IThemeService = unsafeCore._themeService;
this._renderer = new CanvasRenderer(terminal, screenElement, linkifier, bufferService, charSizeService, optionsService, characterJoinerService, coreService, coreBrowserService, decorationService, themeService);
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
@@ -68,9 +68,6 @@ export class CanvasRenderer extends Disposable implements IRenderer {
this._updateDimensions();
this.register(observeDevicePixelDimensions(this._renderLayers[0].canvas, this._coreBrowserService.window, (w, h) => this._setCanvasDevicePixelDimensions(w, h)));
this.handleOptionsChanged();
this.register(toDisposable(() => {
for (const l of this._renderLayers) {
l.dispose();
@@ -130,10 +127,6 @@ export class CanvasRenderer extends Disposable implements IRenderer {
this._runOperation(l => l.handleCursorMove());
}
public handleOptionsChanged(): void {
this._runOperation(l => l.handleOptionsChanged());
}
public clear(): void {
this._runOperation(l => l.reset());
}
@@ -58,6 +58,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
@@ -79,7 +80,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(): void {
this._clearCursor();
this._cursorBlinkStateManager?.restartBlinkAnimation();
this.handleOptionsChanged();
this._handleOptionsChanged();
}
public handleBlur(): void {
@@ -92,7 +93,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._onRequestRedraw.fire({ start: this._bufferService.buffer.y, end: this._bufferService.buffer.y });
}
public handleOptionsChanged(): void {
private _handleOptionsChanged(): void {
if (this._optionsService.rawOptions.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(this._coreBrowserService.isFocused, () => {
@@ -45,6 +45,7 @@ export class TextRenderLayer extends BaseRenderLayer {
) {
super(terminal, container, 'text', zIndex, alpha, themeService, bufferService, optionsService, decorationService, coreBrowserService);
this._state = new GridCache<CharData>();
this.register(optionsService.onSpecificOptionChange('allowTransparency', value => this._setTransparency(value)));
}
public resize(dim: IRenderDimensions): void {
@@ -251,10 +252,6 @@ export class TextRenderLayer extends BaseRenderLayer {
this._drawForeground(firstRow, lastRow);
}
public handleOptionsChanged(): void {
this._setTransparency(this._optionsService.rawOptions.allowTransparency);
}
/**
* Whether a character is overlapping to the next cell.
*/
-5
View File
@@ -73,11 +73,6 @@ export interface IRenderLayer extends IDisposable {
*/
handleCursorMove(): void;
/**
* Called when options change.
*/
handleOptionsChanged(): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
* again).
+19 -12
View File
@@ -3,14 +3,15 @@
* @license MIT
*/
import { Terminal, ITerminalAddon, IEvent } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
import { ICharacterJoinerService, ICoreBrowserService, IRenderService, IThemeService } from 'browser/services/Services';
import { IColorSet } from 'browser/Types';
import { ITerminal } from 'browser/Types';
import { EventEmitter, forwardEvent } from 'common/EventEmitter';
import { isSafari } from 'common/Platform';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { isSafari } from 'common/Platform';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { ICoreTerminal } from 'common/Types';
import { ITerminalAddon, Terminal } from 'xterm';
import { WebglRenderer } from './WebglRenderer';
export class WebglAddon extends Disposable implements ITerminalAddon {
private _terminal?: Terminal;
@@ -31,19 +32,25 @@ export class WebglAddon extends Disposable implements ITerminalAddon {
if (isSafari) {
throw new Error('Webgl is not currently supported on Safari');
}
const core = (terminal as any)._core;
const core = (terminal as any)._core as ITerminal;
if (!terminal.element) {
this.register(core.onWillOpen(() => this.activate(terminal)));
return;
}
this._terminal = terminal;
const renderService: IRenderService = core._renderService;
const characterJoinerService: ICharacterJoinerService = core._characterJoinerService;
const coreBrowserService: ICoreBrowserService = core._coreBrowserService;
const coreService: ICoreService = core.coreService;
const decorationService: IDecorationService = core._decorationService;
const themeService: IThemeService = core._themeService;
this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, coreService, decorationService, this._preserveDrawingBuffer));
const optionsService: IOptionsService = core.optionsService;
const unsafeCore = core as any;
const renderService: IRenderService = unsafeCore._renderService;
const characterJoinerService: ICharacterJoinerService = unsafeCore._characterJoinerService;
const coreBrowserService: ICoreBrowserService = unsafeCore._coreBrowserService;
const decorationService: IDecorationService = unsafeCore._decorationService;
const themeService: IThemeService = unsafeCore._themeService;
this._renderer = this.register(new WebglRenderer(terminal, themeService, characterJoinerService, coreBrowserService, optionsService, coreService, decorationService, this._preserveDrawingBuffer));
this.register(forwardEvent(this._renderer.onContextLoss, this._onContextLoss));
this.register(forwardEvent(this._renderer.onChangeTextureAtlas, this._onChangeTextureAtlas));
renderService.setRenderer(this._renderer);
@@ -15,7 +15,7 @@ import { CellData } from 'common/buffer/CellData';
import { Content, NULL_CELL_CHAR, NULL_CELL_CODE } from 'common/buffer/Constants';
import { EventEmitter } from 'common/EventEmitter';
import { Disposable, toDisposable } from 'common/Lifecycle';
import { ICoreService, IDecorationService } from 'common/services/Services';
import { ICoreService, IDecorationService, IOptionsService } from 'common/services/Services';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { Terminal } from 'xterm';
import { GlyphRenderer } from './GlyphRenderer';
@@ -58,6 +58,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
private readonly _themeService: IThemeService,
private readonly _characterJoinerService: ICharacterJoinerService,
private readonly _coreBrowserService: ICoreBrowserService,
optionsService: IOptionsService,
coreService: ICoreService,
private readonly _decorationService: IDecorationService,
preserveDrawingBuffer?: boolean
@@ -72,7 +73,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
this._renderLayers = [
new LinkRenderLayer(this._core.screenElement!, 2, this._terminal, this._core.linkifier2, this._coreBrowserService, this._themeService),
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._onRequestRedraw, this._coreBrowserService, coreService, this._themeService)
new CursorRenderLayer(_terminal, this._core.screenElement!, 3, this._onRequestRedraw, this._coreBrowserService, coreService, this._themeService, optionsService)
];
this.dimensions = {
scaledCharWidth: 0,
@@ -90,6 +91,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
};
this._devicePixelRatio = this._coreBrowserService.dpr;
this._updateDimensions();
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged()));
this._canvas = document.createElement('canvas');
@@ -230,10 +232,7 @@ export class WebglRenderer extends Disposable implements IRenderer {
}
}
public handleOptionsChanged(): void {
for (const l of this._renderLayers) {
l.handleOptionsChanged(this._terminal);
}
private _handleOptionsChanged(): void {
this._updateDimensions();
this._refreshCharAtlas();
}
@@ -59,7 +59,6 @@ export abstract class BaseRenderLayer extends Disposable implements IRenderLayer
}
}
public handleOptionsChanged(terminal: Terminal): void {}
public handleBlur(terminal: Terminal): void {}
public handleFocus(terminal: Terminal): void {}
public handleCursorMove(terminal: Terminal): void {}
@@ -11,7 +11,7 @@ import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions, IRequestRedrawEvent } from 'browser/renderer/shared/Types';
import { IEventEmitter } from 'common/EventEmitter';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ICoreService } from 'common/services/Services';
import { ICoreService, IOptionsService } from 'common/services/Services';
import { toDisposable } from 'common/Lifecycle';
interface ICursorState {
@@ -40,7 +40,8 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _onRequestRefreshRowsEvent: IEventEmitter<IRequestRedrawEvent>,
coreBrowserService: ICoreBrowserService,
private readonly _coreService: ICoreService,
themeService: IThemeService
themeService: IThemeService,
optionsService: IOptionsService
) {
super(terminal, container, 'cursor', zIndex, true, coreBrowserService, themeService);
this._state = {
@@ -55,7 +56,8 @@ export class CursorRenderLayer extends BaseRenderLayer {
'block': this._renderBlockCursor.bind(this),
'underline': this._renderUnderlineCursor.bind(this)
};
this.handleOptionsChanged(terminal);
this._handleOptionsChanged(terminal);
this.register(optionsService.onOptionChange(() => this._handleOptionsChanged(terminal)));
this.register(toDisposable(() => {
this._cursorBlinkStateManager?.dispose();
this._cursorBlinkStateManager = undefined;
@@ -77,7 +79,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
public reset(terminal: Terminal): void {
this._clearCursor();
this._cursorBlinkStateManager?.restartBlinkAnimation(terminal);
this.handleOptionsChanged(terminal);
this._handleOptionsChanged(terminal);
}
public handleBlur(terminal: Terminal): void {
@@ -90,7 +92,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
this._onRequestRefreshRowsEvent.fire({ start: terminal.buffer.active.cursorY, end: terminal.buffer.active.cursorY });
}
public handleOptionsChanged(terminal: Terminal): void {
private _handleOptionsChanged(terminal: Terminal): void {
if (terminal.options.cursorBlink) {
if (!this._cursorBlinkStateManager) {
this._cursorBlinkStateManager = new CursorBlinkStateManager(() => {
@@ -7,7 +7,7 @@ import { is256Color } from 'browser/renderer/shared/CharAtlasUtils';
import { INVERTED_DEFAULT_COLOR } from 'browser/renderer/shared/Constants';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
import { ICoreBrowserService, IThemeService } from 'browser/services/Services';
import { ILinkifier2, ILinkifierEvent, ITerminal } from 'browser/Types';
import { ILinkifier2, ILinkifierEvent } from 'browser/Types';
import { Terminal } from 'xterm';
import { BaseRenderLayer } from './BaseRenderLayer';
@@ -4,7 +4,6 @@
*/
import { IDisposable, Terminal } from 'xterm';
import { IColorSet, ReadonlyColorSet } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/shared/Types';
export interface IRenderLayer extends IDisposable {
@@ -23,11 +22,6 @@ export interface IRenderLayer extends IDisposable {
*/
handleCursorMove(terminal: Terminal): void;
/**
* Called when options change.
*/
handleOptionsChanged(terminal: Terminal): void;
/**
* Called when the data in the grid has changed (or needs to be rendered
* again).
+11 -46
View File
@@ -264,50 +264,14 @@ export class Terminal extends CoreTerminal implements ITerminal {
}
}
protected _updateOptions(key: string): void {
super._updateOptions(key);
// TODO: These listeners should be owned by individual components
switch (key) {
case 'fontFamily':
case 'fontSize':
// When the font changes the size of the cells may change which requires a renderer clear
this._renderService?.clear();
this._charSizeService?.measure();
break;
case 'cursorBlink':
case 'cursorStyle':
// The DOM renderer needs a row refresh to update the cursor styles
this.refresh(this.buffer.y, this.buffer.y);
break;
case 'customGlyphs':
case 'drawBoldTextInBrightColors':
case 'letterSpacing':
case 'lineHeight':
case 'fontWeight':
case 'fontWeightBold':
case 'minimumContrastRatio':
// When the font changes the size of the cells may change which requires a renderer clear
if (this._renderService) {
this._renderService.clear();
this._renderService.handleResize(this.cols, this.rows);
this.refresh(0, this.rows - 1);
}
break;
case 'scrollback':
this.viewport?.syncScrollArea();
break;
case 'screenReaderMode':
if (this.optionsService.rawOptions.screenReaderMode) {
if (!this._accessibilityManager && this._renderService) {
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
} else {
this._accessibilityManager?.dispose();
this._accessibilityManager = undefined;
}
break;
case 'tabStopWidth': this.buffers.setupTabStops(); break;
private _handleScreenReaderModeOptionChange(value: boolean): void {
if (value) {
if (!this._accessibilityManager && this._renderService) {
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
} else {
this._accessibilityManager?.dispose();
this._accessibilityManager = undefined;
}
}
@@ -583,12 +547,13 @@ export class Terminal extends CoreTerminal implements ITerminal {
// ensure the correct order of the dprchange event
this._accessibilityManager = new AccessibilityManager(this, this._renderService);
}
this.register(this.optionsService.onSpecificOptionChange('screenReaderMode', e => this._handleScreenReaderModeOptionChange(e)));
if (this.options.overviewRulerWidth) {
this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));
}
this.optionsService.onOptionChange(() => {
if (!this._overviewRulerRenderer && this.options.overviewRulerWidth && this._viewportElement && this.screenElement) {
this.optionsService.onSpecificOptionChange('overviewRulerWidth', value => {
if (!this._overviewRulerRenderer && value && this._viewportElement && this.screenElement) {
this._overviewRulerRenderer = this.register(this._instantiationService.createInstance(OverviewRulerRenderer, this._viewportElement, this.screenElement));
}
});
+1
View File
@@ -75,6 +75,7 @@ export class Viewport extends Disposable implements IViewport {
this._handleThemeChange(themeService.colors);
this.register(themeService.onChangeColors(e => this._handleThemeChange(e)));
this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.syncScrollArea()));
// Perform this async to ensure the ICharSizeService is ready.
setTimeout(() => this.syncScrollArea(), 0);
@@ -110,15 +110,9 @@ export class OverviewRulerRenderer extends Disposable {
}
}));
// overview ruler width changed
this.register(this._optionsService.onOptionChange(o => {
if (o === 'overviewRulerWidth') {
this._queueRefresh(true);
}
}));
this.register(this._optionsService.onSpecificOptionChange('overviewRulerWidth', () => this._queueRefresh(true)));
// device pixel ratio changed
this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => {
this._queueRefresh(true);
}));
this.register(addDisposableDomListener(this._coreBrowseService.window, 'resize', () => this._queueRefresh(true)));
// set the canvas dimensions
this._queueRefresh(true);
}
+2 -1
View File
@@ -79,6 +79,7 @@ export class DomRenderer extends Disposable implements IRenderer {
actualCellHeight: 0
};
this._updateDimensions();
this.register(this._optionsService.onOptionChange(() => this._handleOptionsChanged()));
this.register(themeService.onChangeColors(e => this._injectCss(e)));
this._injectCss(themeService.colors);
@@ -342,7 +343,7 @@ export class DomRenderer extends Disposable implements IRenderer {
// No-op, the cursor is drawn when rows are drawn
}
public handleOptionsChanged(): void {
private _handleOptionsChanged(): void {
// Force a refresh
this._updateDimensions();
}
-1
View File
@@ -68,7 +68,6 @@ export interface IRenderer extends IDisposable {
handleFocus(): void;
handleSelectionChanged(start: [number, number] | undefined, end: [number, number] | undefined, columnSelectMode: boolean): void;
handleCursorMove(): void;
handleOptionsChanged(): void;
clear(): void;
renderRows(start: number, end: number): void;
clearTextureAtlas?(): void;
+2
View File
@@ -7,6 +7,7 @@ import { IOptionsService } from 'common/services/Services';
import { EventEmitter } from 'common/EventEmitter';
import { ICharSizeService } from 'browser/services/Services';
import { Disposable } from 'common/Lifecycle';
import { ITerminalOptions } from 'common/Types';
export class CharSizeService extends Disposable implements ICharSizeService {
public serviceBrand: undefined;
@@ -27,6 +28,7 @@ export class CharSizeService extends Disposable implements ICharSizeService {
) {
super();
this._measureStrategy = new DomMeasureStrategy(document, parentElement, this._optionsService);
this.register(this._optionsService.onMultipleOptionChange(['fontFamily', 'fontSize'], () => this.measure()));
}
public measure(): void {
+22 -3
View File
@@ -84,8 +84,28 @@ export class RenderService extends Disposable implements IRenderService {
this.register(decorationService.onDecorationRegistered(() => this._fullRefresh()));
this.register(decorationService.onDecorationRemoved(() => this._fullRefresh()));
// No need to register this as renderer is explicitly disposed in RenderService.dispose
// this._renderer.onRequestRedraw(e => this.refreshRows(e.start, e.end, true));
// Clear the renderer when the a change that could affect glyphs occurs
this.register(optionsService.onMultipleOptionChange([
'customGlyphs',
'drawBoldTextInBrightColors',
'letterSpacing',
'lineHeight',
'fontFamily',
'fontSize',
'fontWeight',
'fontWeightBold',
'minimumContrastRatio'
], () => {
this.clear();
this.handleResize(bufferService.cols, bufferService.rows);
this._fullRefresh();
}));
// Refresh the cursor line when the cursor changes
this.register(optionsService.onMultipleOptionChange([
'cursorBlink',
'cursorStyle'
], () => this.refreshRows(bufferService.buffer.y, bufferService.buffer.y, true)));
// dprchange should handle this case, we need this as well for browsers that don't support the
// matchMedia query.
@@ -157,7 +177,6 @@ export class RenderService extends Disposable implements IRenderService {
if (!this._renderer) {
return;
}
this._renderer.handleOptionsChanged();
this.refreshRows(0, this._rowCount - 1);
this._fireOnCanvasResize();
}
+2 -10
View File
@@ -111,16 +111,8 @@ export class ThemeService extends Disposable implements IThemeService {
this._updateRestoreColors();
this._setTheme(this._optionsService.rawOptions.theme);
this.register(this._optionsService.onOptionChange(key => {
switch (key) {
case 'minimumContrastRatio':
this._contrastCache.clear();
break;
case 'theme':
this._setTheme(this._optionsService.rawOptions.theme);
break;
}
}));
this.register(this._optionsService.onSpecificOptionChange('minimumContrastRatio', () => this._contrastCache.clear()));
this.register(this._optionsService.onSpecificOptionChange('theme', () => this._setTheme(this._optionsService.rawOptions.theme)));
}
/**
+7 -15
View File
@@ -130,7 +130,7 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this.register(forwardEvent(this.coreService.onData, this._onData));
this.register(forwardEvent(this.coreService.onBinary, this._onBinary));
this.register(this.coreService.onUserInput(() => this._writeBuffer.handleUserInput()));
this.register(this.optionsService.onOptionChange(key => this._updateOptions(key)));
this.register(this.optionsService.onSpecificOptionChange('windowsMode', e => this._handleWindowsModeOptionChange(e)));
this.register(this._bufferService.onScroll(event => {
this._onScroll.fire({ position: this._bufferService.buffer.ydisp, source: ScrollSource.TERMINAL });
this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop, this._bufferService.buffer.scrollBottom);
@@ -261,20 +261,12 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal {
this.coreMouseService.reset();
}
protected _updateOptions(key: string): void {
// TODO: These listeners should be owned by individual components
switch (key) {
case 'scrollback':
this.buffers.resize(this.cols, this.rows);
break;
case 'windowsMode':
if (this.optionsService.rawOptions.windowsMode) {
this._enableWindowsMode();
} else {
this._windowsMode?.dispose();
this._windowsMode = undefined;
}
break;
private _handleWindowsModeOptionChange(value: boolean): void {
if (value) {
this._enableWindowsMode();
} else {
this._windowsMode?.dispose();
this._windowsMode = undefined;
}
}
+18 -2
View File
@@ -9,7 +9,7 @@ import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset, IModes, IAttributeData, IOscLinkData, IDisposable } from 'common/Types';
import { UnicodeV6 } from 'common/input/UnicodeV6';
import { IDecorationOptions, IDecoration } from 'xterm';
@@ -113,7 +113,7 @@ export class MockOptionsService implements IOptionsService {
public serviceBrand: any;
public readonly rawOptions: Required<ITerminalOptions> = clone(DEFAULT_OPTIONS);
public options: Required<ITerminalOptions> = this.rawOptions;
public onOptionChange: IEvent<string> = new EventEmitter<string>().event;
public onOptionChange: IEvent<keyof ITerminalOptions> = new EventEmitter<keyof ITerminalOptions>().event;
constructor(testOptions?: Partial<ITerminalOptions>) {
if (testOptions) {
for (const key of Object.keys(testOptions)) {
@@ -121,6 +121,22 @@ export class MockOptionsService implements IOptionsService {
}
}
}
// eslint-disable-next-line @typescript-eslint/naming-convention
public onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (arg1: ITerminalOptions[T]) => any): IDisposable {
return this.onOptionChange(eventKey => {
if (eventKey === key) {
listener(this.rawOptions[key]);
}
});
}
// eslint-disable-next-line @typescript-eslint/naming-convention
public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable {
return this.onOptionChange(eventKey => {
if (keys.indexOf(eventKey) !== -1) {
listener();
}
});
}
public setOptions(options: ITerminalOptions): void {
for (const key of Object.keys(options)) {
this.options[key] = options[key];
+3
View File
@@ -32,6 +32,8 @@ export class BufferSet extends Disposable implements IBufferSet {
) {
super();
this.reset();
this.register(this._optionsService.onSpecificOptionChange('scrollback', () => this.resize(this._bufferService.cols, this._bufferService.rows)));
this.register(this._optionsService.onSpecificOptionChange('tabStopWidth', () => this.setupTabStops()));
}
public reset(): void {
@@ -119,6 +121,7 @@ export class BufferSet extends Disposable implements IBufferSet {
public resize(newCols: number, newRows: number): void {
this._normal.resize(newCols, newRows);
this._alt.resize(newCols, newRows);
this.setupTabStops(newCols);
}
/**
+1 -1
View File
@@ -43,7 +43,7 @@ export class BufferService extends Disposable implements IBufferService {
this.cols = cols;
this.rows = rows;
this.buffers.resize(cols, rows);
this.buffers.setupTabStops(this.cols);
// TODO: This doesn't fire when scrollback changes - add a resize event to BufferSet and forward event
this._onResize.fire({ cols, rows });
}
+1 -5
View File
@@ -40,11 +40,7 @@ export class LogService extends Disposable implements ILogService {
) {
super();
this._updateLogLevel();
this.register(this._optionsService.onOptionChange(key => {
if (key === 'logLevel') {
this._updateLogLevel();
}
}));
this.register(this._optionsService.onSpecificOptionChange('logLevel', () => this._updateLogLevel()));
}
private _updateLogLevel(): void {
@@ -5,6 +5,7 @@
import { assert } from 'chai';
import { OptionsService, DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { IDisposable } from 'common/Types';
describe('OptionsService', () => {
describe('constructor', () => {
@@ -71,4 +72,79 @@ describe('OptionsService', () => {
assert.equal(service.options.fontWeight, DEFAULT_OPTIONS.fontWeight, 'Wrong string literals should be reset to default');
});
});
describe('onOptionChange', () => {
let service: OptionsService;
beforeEach(() => {
service = new OptionsService({});
});
it('should fire on any option change', async () => {
let disposable: IDisposable;
await new Promise<void>(r => {
disposable = service.onOptionChange(e => {
assert.strictEqual(e, 'cursorWidth');
r();
});
service.options.cursorWidth = 10;
});
disposable!.dispose();
await new Promise<void>(r => {
service.onOptionChange(e => {
assert.strictEqual(e, 'scrollback');
r();
});
service.options.scrollback = 20;
});
});
});
describe('onSpecificOptionChange', () => {
let service: OptionsService;
beforeEach(() => {
service = new OptionsService({});
});
it('should fire only on a specific option change', async () => {
await new Promise<void>(r => {
service.onSpecificOptionChange('scrollback', e => {
assert.strictEqual(e, 20);
r();
});
service.options.cursorWidth = 10;
service.options.scrollback = 20;
});
});
});
describe('onSpecificOptionChange', () => {
let service: OptionsService;
beforeEach(() => {
service = new OptionsService({});
});
it('should fire only on a specific option change', async () => {
await new Promise<void>(r => {
service.onSpecificOptionChange('scrollback', e => {
assert.strictEqual(e, 20);
r();
});
service.options.cursorWidth = 10;
service.options.scrollback = 20;
});
});
});
describe('onMultipleOptionChange', () => {
let service: OptionsService;
beforeEach(() => {
service = new OptionsService({});
});
it('should fire only for specific options', async () => {
await new Promise<void>(r => {
let called = false;
service.onMultipleOptionChange(['scrollback'], () => {
called = true;
});
service.options.cursorWidth = 10;
assert.notOk(called);
service.options.scrollback = 20;
assert.ok(called);
r();
});
});
});
});
+20 -2
View File
@@ -6,7 +6,7 @@
import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { isMac } from 'common/Platform';
import { CursorStyle } from 'common/Types';
import { CursorStyle, IDisposable } from 'common/Types';
import { Disposable } from 'common/Lifecycle';
export const DEFAULT_OPTIONS: Readonly<Required<ITerminalOptions>> = {
@@ -58,7 +58,7 @@ export class OptionsService extends Disposable implements IOptionsService {
public readonly rawOptions: Required<ITerminalOptions>;
public options: Required<ITerminalOptions>;
private readonly _onOptionChange = this.register(new EventEmitter<string>());
private readonly _onOptionChange = this.register(new EventEmitter<keyof ITerminalOptions>());
public readonly onOptionChange = this._onOptionChange.event;
constructor(options: Partial<ITerminalOptions>) {
@@ -82,6 +82,24 @@ export class OptionsService extends Disposable implements IOptionsService {
this._setupOptions();
}
// eslint-disable-next-line @typescript-eslint/naming-convention
public onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (value: ITerminalOptions[T]) => any): IDisposable {
return this.onOptionChange(eventKey => {
if (eventKey === key) {
listener(this.rawOptions[key]);
}
});
}
// eslint-disable-next-line @typescript-eslint/naming-convention
public onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable {
return this.onOptionChange(eventKey => {
if (keys.indexOf(eventKey) !== -1) {
listener();
}
});
}
private _setupOptions(): void {
const getter = (propName: string): any => {
if (!(propName in DEFAULT_OPTIONS)) {
+25 -1
View File
@@ -182,9 +182,33 @@ export interface IOptionsService {
* internally.
*/
readonly rawOptions: Required<ITerminalOptions>;
/**
* Options as exposed through the public API, this property uses getters and setters with
* validation which makes it safer but slower. {@link rawOptions} should be used for pretty much
* all internal usage for performance reasons.
*/
readonly options: Required<ITerminalOptions>;
readonly onOptionChange: IEvent<string>;
/**
* Adds an event listener for when any option changes.
*/
readonly onOptionChange: IEvent<keyof ITerminalOptions>;
/**
* Adds an event listener for when a specific option changes, this is a convenience method that is
* preferred over {@link onOptionChange} when only a single option is being listened to.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
onSpecificOptionChange<T extends keyof ITerminalOptions>(key: T, listener: (arg1: Required<ITerminalOptions>[T]) => any): IDisposable;
/**
* Adds an event listener for when a set of specific options change, this is a convenience method
* that is preferred over {@link onOptionChange} when multiple options are being listened to and
* handled the same way.
*/
// eslint-disable-next-line @typescript-eslint/naming-convention
onMultipleOptionChange(keys: (keyof ITerminalOptions)[], listener: () => any): IDisposable;
}
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
-9
View File
@@ -78,15 +78,6 @@ export class Terminal extends CoreTerminal {
return this.buffers.active;
}
protected _updateOptions(key: string): void {
super._updateOptions(key);
// TODO: These listeners should be owned by individual components
switch (key) {
case 'tabStopWidth': this.buffers.setupTabStops(); break;
}
}
// TODO: Support paste here?
public get markers(): IMarker[] {