diff --git a/.npmignore b/.npmignore index da489720..91fbd773 100644 --- a/.npmignore +++ b/.npmignore @@ -2,10 +2,6 @@ * !*/ -# Whitelist - entries to be included must be negated with "!" -!*.js -!*.json - # Whitelist - css/ !css/**/*.css @@ -32,25 +28,15 @@ !src/**/*.css +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + # Whitelist - typings/ !typings/*.d.ts # Blacklist - (normal behavior) these will override any whitelist -*.d.ts.map -*.test.ts -*.test.d.ts -*.test.js -*.test.js.map -lib/test/ -webpack.config.js - -docs/ -/.idea/ -.vscode/ -bin/ -build/ -fixtures/ -demo/ -.devcontainer/ -out/ -addons/ +headless/ +typings/xterm-headless.d.ts diff --git a/addons/xterm-addon-attach/.npmignore b/addons/xterm-addon-attach/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-attach/.npmignore +++ b/addons/xterm-addon-attach/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-fit/.npmignore b/addons/xterm-addon-fit/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-fit/.npmignore +++ b/addons/xterm-addon-fit/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-ligatures/.npmignore b/addons/xterm-addon-ligatures/.npmignore index 9f4fec9a..b203232a 100644 --- a/addons/xterm-addon-ligatures/.npmignore +++ b/addons/xterm-addon-ligatures/.npmignore @@ -2,21 +2,13 @@ * !*/ -# Whitelist - entries to be included must be negated with "!" -!*.js -!*.json - # Whitelist - lib/ !lib/**/*.d.ts !lib/**/*.js !lib/**/*.js.map -# Whitelist - out/ -!out/**/*.d.ts - -!out/**/*.js -!out/**/*.js.map +!lib/**/*.css # Whitelist - src/ !src/**/*.ts @@ -25,24 +17,13 @@ !src/**/*.js !src/**/*.js.map +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + # Whitelist - typings/ -!typings/**/*.d.ts - -# Blacklist - (normal behavior) these will override any whitelist -*.test.ts -*.test.d.ts -*.test.js -*.test.js.map - -docs/ -/.idea/ -.vscode/ -coverage/ -.nyc_output/ -fonts/ - -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +!typings/*.d.ts diff --git a/addons/xterm-addon-search/.npmignore b/addons/xterm-addon-search/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-search/.npmignore +++ b/addons/xterm-addon-search/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-serialize/.npmignore b/addons/xterm-addon-serialize/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-serialize/.npmignore +++ b/addons/xterm-addon-serialize/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-unicode11/.npmignore b/addons/xterm-addon-unicode11/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-unicode11/.npmignore +++ b/addons/xterm-addon-unicode11/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-web-links/.npmignore b/addons/xterm-addon-web-links/.npmignore index 1c794445..b203232a 100644 --- a/addons/xterm-addon-web-links/.npmignore +++ b/addons/xterm-addon-web-links/.npmignore @@ -1,5 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-webgl/.npmignore b/addons/xterm-addon-webgl/.npmignore index 5e5c6068..b203232a 100644 --- a/addons/xterm-addon-webgl/.npmignore +++ b/addons/xterm-addon-webgl/.npmignore @@ -1,7 +1,29 @@ -**/*.api.js -**/*.api.ts -tsconfig.json -.yarnrc -webpack.config.js -out-test/ -test/ +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ + +# Whitelist - lib/ +!lib/**/*.d.ts + +!lib/**/*.js +!lib/**/*.js.map + +!lib/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts diff --git a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts index 6128c836..609df6eb 100644 --- a/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts +++ b/addons/xterm-addon-webgl/src/atlas/WebglCharAtlas.ts @@ -4,7 +4,7 @@ */ import { ICharAtlasConfig } from './Types'; -import { DIM_OPACITY } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { IRasterizedGlyph, IBoundingBox, IRasterizedGlyphSet } from '../Types'; import { DEFAULT_COLOR, Attributes } from 'common/buffer/Constants'; import { throwIfFalsy } from '../WebglUtils'; @@ -368,7 +368,7 @@ export class WebglCharAtlas implements IDisposable { const fontStyle = italic ? 'italic' : ''; this._tmpCtx.font = `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; - this._tmpCtx.textBaseline = 'ideographic'; + this._tmpCtx.textBaseline = TEXT_BASELINE; this._tmpCtx.fillStyle = this._getForegroundCss(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, bold); diff --git a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts index 532836ee..4c17aad4 100644 --- a/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts +++ b/addons/xterm-addon-webgl/src/renderLayer/BaseRenderLayer.ts @@ -7,6 +7,7 @@ import { IRenderLayer } from './Types'; import { acquireCharAtlas } from '../atlas/CharAtlasCache'; import { Terminal } from 'xterm'; import { IColorSet } from 'browser/Types'; +import { TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { IRenderDimensions } from 'browser/renderer/Types'; import { CellData } from 'common/buffer/CellData'; import { WebglCharAtlas } from 'atlas/WebglCharAtlas'; @@ -224,7 +225,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected _fillCharTrueColor(terminal: Terminal, cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(terminal, false, false); - this._ctx.textBaseline = 'ideographic'; + this._ctx.textBaseline = TEXT_BASELINE; this._clipRow(terminal, y); this._ctx.fillText( cell.getChars(), 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/headless/.npmignore b/headless/.npmignore index 83e9f201..535e5ec3 100644 --- a/headless/.npmignore +++ b/headless/.npmignore @@ -1,6 +1,29 @@ -# Include -!typings/*.d.ts -!lib-headless/ +# Blacklist - exclude everything except npm defaults such as LICENSE, etc +* +!*/ -# Exclude -test/ +# Whitelist - lib-headless/ +!lib-headless/**/*.d.ts + +!lib-headless/**/*.js +!lib-headless/**/*.js.map + +!lib-headless/**/*.css + +# Whitelist - src/ +!src/**/*.ts +!src/**/*.d.ts + +!src/**/*.js +!src/**/*.js.map + +!src/**/*.css + +# Blacklist - src/ test files +src/**/*.test.ts +src/**/*.test.d.ts +src/**/*.test.js +src/**/*.test.js.map + +# Whitelist - typings/ +!typings/*.d.ts 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/browser/renderer/BaseRenderLayer.ts b/src/browser/renderer/BaseRenderLayer.ts index 68f83a75..40b6e3f9 100644 --- a/src/browser/renderer/BaseRenderLayer.ts +++ b/src/browser/renderer/BaseRenderLayer.ts @@ -7,7 +7,7 @@ import { IRenderDimensions, IRenderLayer } from 'browser/renderer/Types'; import { ICellData } from 'common/Types'; import { DEFAULT_COLOR, WHITESPACE_CELL_CHAR, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants'; import { IGlyphIdentifier } from 'browser/renderer/atlas/Types'; -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { acquireCharAtlas } from 'browser/renderer/atlas/CharAtlasCache'; import { AttributeData } from 'common/buffer/AttributeData'; @@ -262,7 +262,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { */ protected _fillCharTrueColor(cell: CellData, x: number, y: number): void { this._ctx.font = this._getFont(false, false); - this._ctx.textBaseline = 'ideographic'; + this._ctx.textBaseline = TEXT_BASELINE; this._clipRow(y); // Draw custom characters if applicable @@ -350,7 +350,7 @@ export abstract class BaseRenderLayer implements IRenderLayer { private _drawUncachedChars(cell: ICellData, x: number, y: number, fgOverride?: IColor): void { this._ctx.save(); this._ctx.font = this._getFont(!!cell.isBold(), !!cell.isItalic()); - this._ctx.textBaseline = 'ideographic'; + this._ctx.textBaseline = TEXT_BASELINE; if (cell.isInverse()) { if (fgOverride) { diff --git a/src/browser/renderer/atlas/Constants.ts b/src/browser/renderer/atlas/Constants.ts index 150aad88..e698bcd8 100644 --- a/src/browser/renderer/atlas/Constants.ts +++ b/src/browser/renderer/atlas/Constants.ts @@ -3,7 +3,13 @@ * @license MIT */ +import { isFirefox } from 'common/Platform'; + export const INVERTED_DEFAULT_COLOR = 257; export const DIM_OPACITY = 0.5; +// The text baseline is set conditionally by browser. Using 'ideographic' for Firefox would +// result in truncated text (Issue 3353). Using 'bottom' for Chrome would result in slightly +// unaligned Powerline fonts (PR 3356#issuecomment-850928179). +export const TEXT_BASELINE: CanvasTextBaseline = isFirefox ? 'bottom' : 'ideographic'; export const CHAR_ATLAS_CELL_SPACING = 1; diff --git a/src/browser/renderer/atlas/DynamicCharAtlas.ts b/src/browser/renderer/atlas/DynamicCharAtlas.ts index 666324ad..118dbcd2 100644 --- a/src/browser/renderer/atlas/DynamicCharAtlas.ts +++ b/src/browser/renderer/atlas/DynamicCharAtlas.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { DIM_OPACITY, INVERTED_DEFAULT_COLOR } from 'browser/renderer/atlas/Constants'; +import { DIM_OPACITY, INVERTED_DEFAULT_COLOR, TEXT_BASELINE } from 'browser/renderer/atlas/Constants'; import { IGlyphIdentifier, ICharAtlasConfig } from 'browser/renderer/atlas/Types'; import { BaseCharAtlas } from 'browser/renderer/atlas/BaseCharAtlas'; import { DEFAULT_ANSI_COLORS } from 'browser/ColorManager'; @@ -266,7 +266,7 @@ export class DynamicCharAtlas extends BaseCharAtlas { const fontStyle = glyph.italic ? 'italic' : ''; this._tmpCtx.font = `${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`; - this._tmpCtx.textBaseline = 'ideographic'; + this._tmpCtx.textBaseline = TEXT_BASELINE; this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css; 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;