diff --git a/demo/client.ts b/demo/client.ts index a5ac8bb5..cd0657a0 100644 --- a/demo/client.ts +++ b/demo/client.ts @@ -293,11 +293,11 @@ function initOptions(term: TerminalType): void { rendererType: ['dom', 'canvas'], wordSeparator: null }; - const options = Object.keys((term)._core.options); + const options = Object.keys(term.options); const booleanOptions = []; const numberOptions = []; options.filter(o => blacklistedOptions.indexOf(o) === -1).forEach(o => { - switch (typeof term.getOption(o)) { + switch (typeof term.options[o]) { case 'boolean': booleanOptions.push(o); break; @@ -314,18 +314,18 @@ function initOptions(term: TerminalType): void { let html = ''; html += '
'; booleanOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; numberOptions.forEach(o => { - html += `
`; + html += `
`; }); html += '
'; Object.keys(stringOptions).forEach(o => { if (stringOptions[o]) { - html += `
`; + html += `
`; } else { - html += `
`; + html += `
`; } }); html += '
'; @@ -338,7 +338,7 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.checked); - term.setOption(o, input.checked); + term.options[o] = input.checked; }); }); numberOptions.forEach(o => { @@ -347,14 +347,17 @@ function initOptions(term: TerminalType): void { console.log('change', o, input.value); if (o === 'cols' || o === 'rows') { updateTerminalSize(); - } else if (o === 'lineHeight' || o === 'scrollSensitivity') { - term.setOption(o, parseFloat(input.value)); + } else if (o === 'lineHeight') { + term.options.lineHeight = parseFloat(input.value); + updateTerminalSize(); + } else if (o === 'scrollSensitivity') { + term.options.scrollSensitivity = parseFloat(input.value); updateTerminalSize(); } else if(o === 'scrollback') { - term.setOption(o, parseInt(input.value)); + term.options.scrollback = parseInt(input.value); setTimeout(() => updateTerminalSize(), 5); } else { - term.setOption(o, parseInt(input.value)); + term.options[o] = parseInt(input.value); } }); }); @@ -362,7 +365,7 @@ function initOptions(term: TerminalType): void { const input = document.getElementById(`opt-${o}`); addDomListener(input, 'change', () => { console.log('change', o, input.value); - term.setOption(o, input.value); + term.options[o] = input.value; }); }); } diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index 0047ce9f..24dc7af9 100644 --- a/src/browser/Terminal.ts +++ b/src/browser/Terminal.ts @@ -152,7 +152,7 @@ export class Terminal extends CoreTerminal implements ITerminal { * @alias module:xterm/src/xterm */ constructor( - options: ITerminalOptions = {} + options: Partial = {} ) { super(options); diff --git a/src/browser/public/Terminal.ts b/src/browser/public/Terminal.ts index 26cf2728..087fde94 100644 --- a/src/browser/public/Terminal.ts +++ b/src/browser/public/Terminal.ts @@ -89,6 +89,9 @@ export class Terminal implements ITerminalApi { wraparoundMode: m.wraparound }; } + public get options(): ITerminalOptions { + return this._core.options; + } public blur(): void { this._core.blur(); } diff --git a/src/common/CoreTerminal.ts b/src/common/CoreTerminal.ts index 96a4b53d..a5f78f66 100644 --- a/src/common/CoreTerminal.ts +++ b/src/common/CoreTerminal.ts @@ -86,9 +86,10 @@ export abstract class CoreTerminal extends Disposable implements ICoreTerminal { public get cols(): number { return this._bufferService.cols; } public get rows(): number { return this._bufferService.rows; } public get buffers(): IBufferSet { return this._bufferService.buffers; } + public get options(): ITerminalOptions { return this.optionsService.publicOptions; } constructor( - options: ITerminalOptions + options: Partial ) { super(); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 52fe00d0..014142ac 100644 --- a/src/common/TestUtils.test.ts +++ b/src/common/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services'; +import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService, IUnicodeService, IUnicodeVersionProvider, LogLevelEnum } from 'common/services/Services'; import { IEvent, EventEmitter } from 'common/EventEmitter'; import { clone } from 'common/Clone'; import { DEFAULT_OPTIONS } from 'common/services/OptionsService'; @@ -121,11 +121,13 @@ export class MockLogService implements ILogService { export class MockOptionsService implements IOptionsService { public serviceBrand: any; public options: ITerminalOptions = clone(DEFAULT_OPTIONS); + public publicOptions: ITerminalOptions = clone(DEFAULT_OPTIONS); public onOptionChange: IEvent = new EventEmitter().event; - constructor(testOptions?: IPartialTerminalOptions) { + constructor(testOptions?: Partial) { if (testOptions) { for (const key of Object.keys(testOptions)) { - this.options[key] = (testOptions as any)[key]; + this.options[key] = testOptions[key]; + this.publicOptions[key] = testOptions[key]; } } } diff --git a/src/common/Types.d.ts b/src/common/Types.d.ts index 78e2e62d..88497e4a 100644 --- a/src/common/Types.d.ts +++ b/src/common/Types.d.ts @@ -16,6 +16,7 @@ export interface ICoreTerminal { optionsService: IOptionsService; unicodeService: IUnicodeService; buffers: IBufferSet; + options: ITerminalOptions; registerCsiHandler(id: IFunctionIdentifier, callback: (params: IParams) => boolean | Promise): IDisposable; registerDcsHandler(id: IFunctionIdentifier, callback: (data: string, param: IParams) => boolean | Promise): IDisposable; registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise): IDisposable; diff --git a/src/common/services/OptionsService.ts b/src/common/services/OptionsService.ts index e9dcaa6a..a245c153 100644 --- a/src/common/services/OptionsService.ts +++ b/src/common/services/OptionsService.ts @@ -3,10 +3,9 @@ * @license MIT */ -import { IOptionsService, ITerminalOptions, IPartialTerminalOptions, FontWeight } from 'common/services/Services'; +import { IOptionsService, ITerminalOptions, FontWeight } from 'common/services/Services'; import { EventEmitter, IEvent } from 'common/EventEmitter'; import { isMac } from 'common/Platform'; -import { clone } from 'common/Clone'; // Source: https://freesound.org/people/altemark/sounds/45759/ // This sound is released under the Creative Commons Attribution 3.0 Unported @@ -14,15 +13,14 @@ import { clone } from 'common/Clone'; // made, apart from the conversion to base64. export const DEFAULT_BELL_SOUND = 'data:audio/mp3;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjMyLjEwNAAAAAAAAAAAAAAA//tQxAADB8AhSmxhIIEVCSiJrDCQBTcu3UrAIwUdkRgQbFAZC1CQEwTJ9mjRvBA4UOLD8nKVOWfh+UlK3z/177OXrfOdKl7pyn3Xf//WreyTRUoAWgBgkOAGbZHBgG1OF6zM82DWbZaUmMBptgQhGjsyYqc9ae9XFz280948NMBWInljyzsNRFLPWdnZGWrddDsjK1unuSrVN9jJsK8KuQtQCtMBjCEtImISdNKJOopIpBFpNSMbIHCSRpRR5iakjTiyzLhchUUBwCgyKiweBv/7UsQbg8isVNoMPMjAAAA0gAAABEVFGmgqK////9bP/6XCykxBTUUzLjEwMKqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq'; -// TODO: Freeze? -export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ +export const DEFAULT_OPTIONS: Readonly = { cols: 80, rows: 24, cursorBlink: false, cursorStyle: 'block', cursorWidth: 1, customGlyphs: true, - bellSound: DEFAULT_BELL_SOUND, + bellSound: DEFAULT_BELL_SOUND, bellStyle: 'none', drawBoldTextInBrightColors: true, fastScrollModifier: 'alt', @@ -55,7 +53,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({ convertEol: false, termName: 'xterm', cancelEvents: false -}); +}; const FONT_WEIGHT_OPTIONS: Extract[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900']; @@ -67,45 +65,68 @@ const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; export class OptionsService implements IOptionsService { public serviceBrand: any; + private _options: ITerminalOptions; public options: ITerminalOptions; + public publicOptions: ITerminalOptions; private _onOptionChange = new EventEmitter(); public get onOptionChange(): IEvent { return this._onOptionChange.event; } - constructor(options: IPartialTerminalOptions) { - this.options = clone(DEFAULT_OPTIONS); - for (const k of Object.keys(options)) { - if (k in this.options) { + constructor(options: Partial) { + // set the default value of each option + this._options = { ...DEFAULT_OPTIONS }; + for (const key in options) { + if (key in this._options) { try { - const newValue = options[k as keyof IPartialTerminalOptions] as any; - this.options[k] = this._sanitizeAndValidateOption(k, newValue); + const newValue = options[key]; + this._options[key] = this._sanitizeAndValidateOption(key, newValue); } catch (e) { console.error(e); } } } + + // set up getters and setters for each option + this.options = this._setupOptions(this._options, false); + this.publicOptions = this._setupOptions(this._options, true); + } + + private _setupOptions(options: ITerminalOptions, isPublic: boolean): ITerminalOptions { + const copiedOptions = { ... options }; + for (const propName in copiedOptions) { + Object.defineProperty(copiedOptions, propName, { + get: () => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error(`No option with key "${propName}"`); + } + return this._options[propName]; + }, + set: (value: any) => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error(`No option with key "${propName}"`); + } + + // Throw an error if any constructor only option is modified + // from terminal.options + // Modifications from anywhere else are allowed + if (isPublic && CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) { + throw new Error(`Option "${propName}" can only be set in the constructor`); + } + + value = this._sanitizeAndValidateOption(propName, value); + // Don't fire an option change event if they didn't change + if (this._options[propName] !== value) { + this._options[propName] = value; + this._onOptionChange.fire(propName); + } + } + }); + } + return copiedOptions; } public setOption(key: string, value: any): void { - if (!(key in DEFAULT_OPTIONS)) { - throw new Error('No option with key "' + key + '"'); - } - if (CONSTRUCTOR_ONLY_OPTIONS.includes(key)) { - throw new Error(`Option "${key}" can only be set in the constructor`); - } - if (this.options[key] === value) { - return; - } - - value = this._sanitizeAndValidateOption(key, value); - - // Don't fire an option change event if they didn't change - if (this.options[key] === value) { - return; - } - - this.options[key] = value; - this._onOptionChange.fire(key); + this.publicOptions[key] = value; } private _sanitizeAndValidateOption(key: string, value: any): any { @@ -160,9 +181,6 @@ export class OptionsService implements IOptionsService { } public getOption(key: string): any { - if (!(key in DEFAULT_OPTIONS)) { - throw new Error(`No option with key "${key}"`); - } - return this.options[key]; + return this.publicOptions[key]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 2190a0f8..56b10f73 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -181,6 +181,7 @@ export interface IOptionsService { serviceBrand: undefined; readonly options: ITerminalOptions; + readonly publicOptions: ITerminalOptions; readonly onOptionChange: IEvent; @@ -199,41 +200,6 @@ export enum LogLevelEnum { } export type RendererType = 'dom' | 'canvas'; -export interface IPartialTerminalOptions { - altClickMovesCursor?: boolean; - allowTransparency?: boolean; - bellSound?: string; - bellStyle?: 'none' | 'sound' /* | 'visual' | 'both' */; - cols?: number; - cursorBlink?: boolean; - cursorStyle?: 'block' | 'underline' | 'bar'; - cursorWidth?: number; - disableStdin?: boolean; - drawBoldTextInBrightColors?: boolean; - fastScrollModifier?: 'alt' | 'ctrl' | 'shift'; - fastScrollSensitivity?: number; - fontSize?: number; - fontFamily?: string; - fontWeight?: FontWeight; - fontWeightBold?: FontWeight; - letterSpacing?: number; - lineHeight?: number; - logLevel?: LogLevel; - macOptionIsMeta?: boolean; - macOptionClickForcesSelection?: boolean; - rendererType?: RendererType; - rightClickSelectsWord?: boolean; - rows?: number; - screenReaderMode?: boolean; - scrollback?: number; - scrollSensitivity?: number; - tabStopWidth?: number; - theme?: ITheme; - windowsMode?: boolean; - wordSeparator?: string; - windowOptions?: IWindowOptions; -} - export interface ITerminalOptions { allowProposedApi: boolean; allowTransparency: boolean; diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index d7c2effc..f67e3a16 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -635,6 +635,11 @@ declare module 'xterm' { */ readonly modes: IModes; + /** + * Get the terminal options + */ + readonly options: ITerminalOptions; + /** * Natural language strings that can be localized. */ @@ -964,26 +969,31 @@ declare module 'xterm' { /** * Retrieves an option's value from the terminal. * @param key The option key. + * @deprecated Use `options` instead. */ getOption(key: 'bellSound' | 'bellStyle' | 'cursorStyle' | 'fontFamily' | 'logLevel' | 'rendererType' | 'termName' | 'wordSeparator'): string; /** * Retrieves an option's value from the terminal. * @param key The option key. + * @deprecated Use `options` instead. */ getOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'rightClickSelectsWord' | 'popOnBell' | 'visualBell' | 'windowsMode'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. + * @deprecated Use `options` instead. */ getOption(key: 'cols' | 'fontSize' | 'letterSpacing' | 'lineHeight' | 'rows' | 'tabStopWidth' | 'scrollback'): number; /** * Retrieves an option's value from the terminal. * @param key The option key. + * @deprecated Use `options` instead. */ getOption(key: 'fontWeight' | 'fontWeightBold'): FontWeight; /** * Retrieves an option's value from the terminal. * @param key The option key. + * @deprecated Use `options` instead. */ getOption(key: string): any; @@ -991,60 +1001,70 @@ declare module 'xterm' { * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'fontFamily' | 'termName' | 'bellSound' | 'wordSeparator', value: string): void; /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + * @deprecated Use `options` instead. + */ setOption(key: 'fontWeight' | 'fontWeightBold', value: null | 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number): void; /** - * Sets an option on the terminal. - * @param key The option key. - * @param value The option value. - */ + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + * @deprecated Use `options` instead. + */ setOption(key: 'logLevel', value: LogLevel): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'bellStyle', value: null | 'none' | 'visual' | 'sound' | 'both'): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'cursorStyle', value: null | 'block' | 'underline' | 'bar'): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'allowTransparency' | 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'disableStdin' | 'macOptionIsMeta' | 'popOnBell' | 'rightClickSelectsWord' | 'visualBell' | 'windowsMode', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'fontSize' | 'letterSpacing' | 'lineHeight' | 'tabStopWidth' | 'scrollback', value: number): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'theme', value: ITheme): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: 'cols' | 'rows', value: number): void; /** * Sets an option on the terminal. * @param key The option key. * @param value The option value. + * @deprecated Use `options` instead. */ setOption(key: string, value: any): void;