From 72a557cc1a2bbb62805dfb1e91dc5ee298e1620d Mon Sep 17 00:00:00 2001 From: Simon Lamon Date: Wed, 1 Sep 2021 07:59:45 +0000 Subject: [PATCH] Make options available in terminal --- src/browser/Terminal.ts | 2 +- src/browser/public/Terminal.ts | 3 ++ src/common/CoreTerminal.ts | 3 +- src/common/TestUtils.test.ts | 6 +-- src/common/Types.d.ts | 1 + src/common/services/OptionsService.ts | 75 +++++++++++++++------------ src/common/services/Services.ts | 49 ++++------------- typings/xterm.d.ts | 5 ++ 8 files changed, 65 insertions(+), 79 deletions(-) diff --git a/src/browser/Terminal.ts b/src/browser/Terminal.ts index ebe755bc..b487a18f 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 a76b1a22..761c27e6 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..d0f2f8f2 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.options; } constructor( - options: ITerminalOptions + options: Partial ) { super(); diff --git a/src/common/TestUtils.test.ts b/src/common/TestUtils.test.ts index 52fe00d0..1d38df21 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'; @@ -122,10 +122,10 @@ export class MockOptionsService implements IOptionsService { public serviceBrand: any; public options: 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]; } } } 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 5add8283..e1838b5d 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,57 @@ const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows']; export class OptionsService implements IOptionsService { public serviceBrand: any; + private _options: any; public options: 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) { + 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); } } } + + this._options = {}; + for (const propName in this.options) { + const privatePropName = `_${propName}`; + this._options[privatePropName] = this.options[propName]; + Object.defineProperty(this.options, propName, { + get: () => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error(`No option with key "${propName}"`); + } + return this._options[privatePropName]; + }, + set: (value: any) => { + if (!(propName in DEFAULT_OPTIONS)) { + throw new Error('No option with key "' + propName + '"'); + } + if (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[privatePropName] !== value) { + this._options[privatePropName] = value; + this._onOptionChange.fire(propName); + } + } + }); + } } 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); } private _sanitizeAndValidateOption(key: string, value: any): any { @@ -128,7 +138,7 @@ export class OptionsService implements IOptionsService { break; case 'cursorWidth': value = Math.floor(value); - // Fall through for bounds check + // Fall through for bounds check case 'lineHeight': case 'tabStopWidth': if (value < 1) { @@ -155,9 +165,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]; } } diff --git a/src/common/services/Services.ts b/src/common/services/Services.ts index 2190a0f8..c5bc2900 100644 --- a/src/common/services/Services.ts +++ b/src/common/services/Services.ts @@ -198,56 +198,27 @@ export enum LogLevelEnum { OFF = 4 } export type RendererType = 'dom' | 'canvas'; +export type BellStyle = 'none' | 'sound' /* | 'visual' | 'both' */; +export type CursorStyle = 'block' | 'underline' | 'bar'; +export type FastScrollModifier = 'alt' | 'ctrl' | 'shift'; -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; altClickMovesCursor: boolean; bellSound: string; - bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */; + bellStyle: BellStyle; + cancelEvents: boolean; cols: number; + convertEol: boolean; cursorBlink: boolean; - cursorStyle: 'block' | 'underline' | 'bar'; + cursorStyle: CursorStyle; cursorWidth: number; customGlyphs: boolean; disableStdin: boolean; drawBoldTextInBrightColors: boolean; - fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined; + fastScrollModifier: FastScrollModifier | undefined; fastScrollSensitivity: number; fontSize: number; fontFamily: string; @@ -267,15 +238,13 @@ export interface ITerminalOptions { scrollback: number; scrollSensitivity: number; tabStopWidth: number; + termName: string; theme: ITheme; windowsMode: boolean; windowOptions: IWindowOptions; wordSeparator: string; [key: string]: any; - cancelEvents: boolean; - convertEol: boolean; - termName: string; } export interface ITheme { diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ba2be988..2f31a164 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. */