mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #2171 from Tyriar/options_service
Introduce options service, move options handling code into common
This commit is contained in:
+1
-1
@@ -1366,7 +1366,7 @@ describe('Buffer', () => {
|
||||
const input = '\thttps://google.de';
|
||||
terminal.writeSync(input);
|
||||
const s = terminal.buffer.iterator(true).next().content;
|
||||
assert.equal(s, Array(terminal.getOption('tabStopWidth') + 1).join(' ') + 'https://google.de');
|
||||
assert.equal(s, Array(terminal.options.tabStopWidth + 1).join(' ') + 'https://google.de');
|
||||
});
|
||||
});
|
||||
describe('BufferStringIterator', function(): void {
|
||||
|
||||
+4
-4
@@ -1842,19 +1842,19 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
switch (param) {
|
||||
case 1:
|
||||
case 2:
|
||||
this._terminal.setOption('cursorStyle', 'block');
|
||||
this._terminal.options.cursorStyle = 'block';
|
||||
break;
|
||||
case 3:
|
||||
case 4:
|
||||
this._terminal.setOption('cursorStyle', 'underline');
|
||||
this._terminal.options.cursorStyle = 'underline';
|
||||
break;
|
||||
case 5:
|
||||
case 6:
|
||||
this._terminal.setOption('cursorStyle', 'bar');
|
||||
this._terminal.options.cursorStyle = 'bar';
|
||||
break;
|
||||
}
|
||||
const isBlinking = param % 2 === 1;
|
||||
this._terminal.setOption('cursorBlink', isBlinking);
|
||||
this._terminal.options.cursorBlink = isBlinking;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,12 +5,6 @@
|
||||
|
||||
import { ITerminal, ISoundManager } from './Types';
|
||||
|
||||
// Source: https://freesound.org/people/altemark/sounds/45759/
|
||||
// This sound is released under the Creative Commons Attribution 3.0 Unported
|
||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
||||
// made, apart from the conversion to base64.
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
|
||||
export class SoundManager implements ISoundManager {
|
||||
private static _audioContext: AudioContext;
|
||||
|
||||
|
||||
+3
-31
@@ -43,7 +43,7 @@ describe('Terminal', () => {
|
||||
});
|
||||
|
||||
it('should not mutate the options parameter', () => {
|
||||
term.setOption('cols', 1000);
|
||||
term.options.cols = 1000;
|
||||
|
||||
assert.deepEqual(termOptions, {
|
||||
cols: INIT_COLS,
|
||||
@@ -51,22 +51,6 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('getOption', () => {
|
||||
it('should retrieve the option correctly', () => {
|
||||
// In the `options` namespace.
|
||||
term.options.cursorBlink = true;
|
||||
assert.equal(term.getOption('cursorBlink'), true);
|
||||
|
||||
// On the Terminal instance
|
||||
delete term.options.cursorBlink;
|
||||
term.options.cursorBlink = false;
|
||||
assert.equal(term.getOption('cursorBlink'), false);
|
||||
});
|
||||
it('should throw when retrieving a non-existant option', () => {
|
||||
assert.throws(term.getOption.bind(term, 'fake', true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('events', () => {
|
||||
it('should fire the onData evnet', (done) => {
|
||||
term.onData(() => done());
|
||||
@@ -335,18 +319,6 @@ describe('Terminal', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('setOption', () => {
|
||||
it('should set option correctly', () => {
|
||||
term.setOption('cursorBlink', true);
|
||||
assert.equal(term.options.cursorBlink, true);
|
||||
term.setOption('cursorBlink', false);
|
||||
assert.equal(term.options.cursorBlink, false);
|
||||
});
|
||||
it('should throw when setting a non-existant option', () => {
|
||||
assert.throws(term.setOption.bind(term, 'fake', true));
|
||||
});
|
||||
});
|
||||
|
||||
describe('reset', () => {
|
||||
it('should not affect cursorState', () => {
|
||||
term.cursorState = 1;
|
||||
@@ -625,7 +597,7 @@ describe('Terminal', () => {
|
||||
|
||||
describe('when scrollback === 0', () => {
|
||||
beforeEach(() => {
|
||||
term.setOption('scrollback', 0);
|
||||
term.optionsService.setOption('scrollback', 0);
|
||||
assert.equal(term.buffer.lines.maxLength, INIT_ROWS);
|
||||
});
|
||||
|
||||
@@ -730,7 +702,7 @@ describe('Terminal', () => {
|
||||
describe('with macOptionIsMeta', () => {
|
||||
beforeEach(() => {
|
||||
term.browser.isMac = true;
|
||||
term.setOption('macOptionIsMeta', true);
|
||||
term.options.macOptionIsMeta = true;
|
||||
});
|
||||
|
||||
it('should interfere with the alt key on keyDown', () => {
|
||||
|
||||
+80
-199
@@ -24,7 +24,7 @@
|
||||
import { IInputHandlingTerminal, IViewport, ICompositionHelper, ITerminalOptions, ITerminal, IBrowser, ILinkifier, ILinkMatcherOptions, CustomKeyEventHandler, LinkMatcherHandler, CharacterJoinerHandler, IMouseZoneManager } from './Types';
|
||||
import { IRenderer } from './renderer/Types';
|
||||
import { BufferSet } from './BufferSet';
|
||||
import { Buffer, MAX_BUFFER_SIZE } from './Buffer';
|
||||
import { Buffer } from './Buffer';
|
||||
import { CompositionHelper } from './CompositionHelper';
|
||||
import { EventEmitter } from 'common/EventEmitter';
|
||||
import { Viewport } from './Viewport';
|
||||
@@ -39,7 +39,7 @@ import * as Browser from 'common/Platform';
|
||||
import { addDisposableDomListener } from 'ui/Lifecycle';
|
||||
import * as Strings from './Strings';
|
||||
import { MouseHelper } from './MouseHelper';
|
||||
import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager';
|
||||
import { SoundManager } from './SoundManager';
|
||||
import { MouseZoneManager } from './MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
|
||||
@@ -48,12 +48,13 @@ import { DomRenderer } from './renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'core/input/Keyboard';
|
||||
import { KeyboardResultType, ICharset, IBufferLine, IAttributeData } from 'core/Types';
|
||||
import { clone } from 'common/Clone';
|
||||
import { EventEmitter2, IEvent } from 'common/EventEmitter2';
|
||||
import { Attributes, DEFAULT_ATTR_DATA } from 'core/buffer/BufferLine';
|
||||
import { applyWindowsMode } from './WindowsMode';
|
||||
import { ColorManager } from 'ui/ColorManager';
|
||||
import { RenderCoordinator } from './renderer/RenderCoordinator';
|
||||
import { IOptionsService } from 'common/options/Types';
|
||||
import { OptionsService } from 'common/options/OptionsService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document = (typeof window !== 'undefined') ? window.document : null;
|
||||
@@ -77,44 +78,6 @@ const WRITE_BUFFER_LENGTH_THRESHOLD = 50;
|
||||
const MINIMUM_COLS = 2; // Less than 2 can mess with wide chars
|
||||
const MINIMUM_ROWS = 1;
|
||||
|
||||
/**
|
||||
* The set of options that only have an effect when set in the Terminal constructor.
|
||||
*/
|
||||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
|
||||
|
||||
const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
bellSound: DEFAULT_BELL_SOUND,
|
||||
bellStyle: 'none',
|
||||
drawBoldTextInBrightColors: true,
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
fontWeight: 'normal',
|
||||
fontWeightBold: 'bold',
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
scrollback: 1000,
|
||||
screenKeys: false,
|
||||
screenReaderMode: false,
|
||||
debug: false,
|
||||
macOptionIsMeta: false,
|
||||
macOptionClickForcesSelection: false,
|
||||
cancelEvents: false,
|
||||
disableStdin: false,
|
||||
useFlowControl: false,
|
||||
allowTransparency: false,
|
||||
tabStopWidth: 8,
|
||||
theme: undefined,
|
||||
rightClickSelectsWord: Browser.isMac,
|
||||
rendererType: 'canvas',
|
||||
windowsMode: false
|
||||
};
|
||||
|
||||
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||
public textarea: HTMLTextAreaElement;
|
||||
public element: HTMLElement;
|
||||
@@ -135,7 +98,8 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
|
||||
public browser: IBrowser = <any>Browser;
|
||||
|
||||
public options: ITerminalOptions;
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
public get options(): ITerminalOptions { return this.optionsService.options; }
|
||||
|
||||
// TODO: This can be changed to an enum or boolean, 0 and 1 seem to be the only options
|
||||
public cursorState: number;
|
||||
@@ -143,6 +107,9 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
|
||||
private _customKeyEventHandler: CustomKeyEventHandler;
|
||||
|
||||
// services
|
||||
public optionsService: IOptionsService;
|
||||
|
||||
// modes
|
||||
public applicationKeypad: boolean;
|
||||
public applicationCursor: boolean;
|
||||
@@ -257,7 +224,10 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
options: ITerminalOptions = {}
|
||||
) {
|
||||
super();
|
||||
this.options = clone(options);
|
||||
this.optionsService = new OptionsService(options);
|
||||
this._setupOptionsListeners();
|
||||
|
||||
// this.options = clone(options);
|
||||
this._setup();
|
||||
|
||||
// TODO: Remove these in v4
|
||||
@@ -289,24 +259,11 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
|
||||
private _setup(): void {
|
||||
Object.keys(DEFAULT_OPTIONS).forEach((key) => {
|
||||
if (this.options[key] === null || this.options[key] === undefined) {
|
||||
this.options[key] = DEFAULT_OPTIONS[key];
|
||||
}
|
||||
});
|
||||
|
||||
// this.context = options.context || window;
|
||||
// this.document = options.document || document;
|
||||
// TODO: WHy not document.body?
|
||||
this._parent = document ? document.body : null;
|
||||
|
||||
this.cols = Math.max(this.options.cols, MINIMUM_COLS);
|
||||
this.rows = Math.max(this.options.rows, MINIMUM_ROWS);
|
||||
|
||||
if (this.options.handler) {
|
||||
this.onData(this.options.handler);
|
||||
}
|
||||
|
||||
this.cursorState = 0;
|
||||
this.cursorHidden = false;
|
||||
this._customKeyEventHandler = null;
|
||||
@@ -394,82 +351,59 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
return document.activeElement === this.textarea && document.hasFocus();
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves an option's value from the terminal.
|
||||
* @param key The option key.
|
||||
*/
|
||||
public getOption(key: string): any {
|
||||
if (!(key in DEFAULT_OPTIONS)) {
|
||||
throw new Error('No option with key "' + key + '"');
|
||||
}
|
||||
|
||||
return this.options[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets an option on the terminal.
|
||||
* @param key The option key.
|
||||
* @param value The option value.
|
||||
*/
|
||||
public setOption(key: string, value: any): void {
|
||||
if (!(key in DEFAULT_OPTIONS)) {
|
||||
throw new Error('No option with key "' + key + '"');
|
||||
}
|
||||
if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {
|
||||
console.error(`Option "${key}" can only be set in the constructor`);
|
||||
}
|
||||
if (this.options[key] === value) {
|
||||
return;
|
||||
}
|
||||
switch (key) {
|
||||
case 'bellStyle':
|
||||
if (!value) {
|
||||
value = 'none';
|
||||
}
|
||||
break;
|
||||
case 'cursorStyle':
|
||||
if (!value) {
|
||||
value = 'block';
|
||||
}
|
||||
break;
|
||||
case 'fontWeight':
|
||||
if (!value) {
|
||||
value = 'normal';
|
||||
}
|
||||
break;
|
||||
case 'fontWeightBold':
|
||||
if (!value) {
|
||||
value = 'bold';
|
||||
}
|
||||
break;
|
||||
case 'lineHeight':
|
||||
if (value < 1) {
|
||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||
return;
|
||||
}
|
||||
case 'rendererType':
|
||||
if (!value) {
|
||||
value = 'canvas';
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth':
|
||||
if (value < 1) {
|
||||
console.warn(`${key} cannot be less than 1, value: ${value}`);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
case 'theme':
|
||||
this._setTheme(<ITheme>value);
|
||||
break;
|
||||
case 'scrollback':
|
||||
value = Math.min(value, MAX_BUFFER_SIZE);
|
||||
|
||||
if (value < 0) {
|
||||
console.warn(`${key} cannot be less than 0, value: ${value}`);
|
||||
return;
|
||||
}
|
||||
if (this.options[key] !== value) {
|
||||
const newBufferLength = this.rows + value;
|
||||
private _setupOptionsListeners(): void {
|
||||
// TODO: These listeners should be owned by individual components
|
||||
this.optionsService.onOptionChange(key => {
|
||||
switch (key) {
|
||||
case 'fontFamily':
|
||||
case 'fontSize':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.clear();
|
||||
this.charMeasure.measure(this.options);
|
||||
}
|
||||
break;
|
||||
case 'drawBoldTextInBrightColors':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.clear();
|
||||
this._renderCoordinator.onResize(this.cols, this.rows);
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
break;
|
||||
case 'rendererType':
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.setRenderer(this._createRenderer());
|
||||
}
|
||||
break;
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
break;
|
||||
case 'screenReaderMode':
|
||||
if (this.optionsService.options.screenReaderMode) {
|
||||
if (!this._accessibilityManager && this._renderCoordinator) {
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions);
|
||||
}
|
||||
} else {
|
||||
if (this._accessibilityManager) {
|
||||
this._accessibilityManager.dispose();
|
||||
this._accessibilityManager = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||
case 'theme':
|
||||
this._setTheme(this.optionsService.options.theme);
|
||||
break;
|
||||
case 'scrollback':
|
||||
const newBufferLength = this.rows + this.optionsService.options.scrollback;
|
||||
if (this.buffer.lines.length > newBufferLength) {
|
||||
const amountToTrim = this.buffer.lines.length - newBufferLength;
|
||||
const needsRefresh = (this.buffer.ydisp - amountToTrim < 0);
|
||||
@@ -480,72 +414,20 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
this.options[key] = value;
|
||||
switch (key) {
|
||||
case 'fontFamily':
|
||||
case 'fontSize':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.clear();
|
||||
this.charMeasure.measure(this.options);
|
||||
}
|
||||
break;
|
||||
case 'drawBoldTextInBrightColors':
|
||||
case 'letterSpacing':
|
||||
case 'lineHeight':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
// When the font changes the size of the cells may change which requires a renderer clear
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.clear();
|
||||
this._renderCoordinator.onResize(this.cols, this.rows);
|
||||
this.refresh(0, this.rows - 1);
|
||||
}
|
||||
break;
|
||||
case 'rendererType':
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.setRenderer(this._createRenderer());
|
||||
}
|
||||
break;
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
if (this.viewport) {
|
||||
this.viewport.syncScrollArea();
|
||||
}
|
||||
break;
|
||||
case 'screenReaderMode':
|
||||
if (value) {
|
||||
if (!this._accessibilityManager && this._renderCoordinator) {
|
||||
this._accessibilityManager = new AccessibilityManager(this, this._renderCoordinator.dimensions);
|
||||
case 'windowsMode':
|
||||
if (this.optionsService.options.windowsMode) {
|
||||
if (!this._windowsMode) {
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
}
|
||||
} else {
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this._accessibilityManager) {
|
||||
this._accessibilityManager.dispose();
|
||||
this._accessibilityManager = null;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||
case 'windowsMode':
|
||||
if (value) {
|
||||
if (!this._windowsMode) {
|
||||
this._windowsMode = applyWindowsMode(this);
|
||||
}
|
||||
} else {
|
||||
if (this._windowsMode) {
|
||||
this._windowsMode.dispose();
|
||||
this._windowsMode = undefined;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Inform renderer of changes
|
||||
if (this._renderCoordinator) {
|
||||
this._renderCoordinator.onOptionsChanged();
|
||||
}
|
||||
break;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -717,7 +599,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
|
||||
this.textarea = document.createElement('textarea');
|
||||
this.textarea.classList.add('xterm-helper-textarea');
|
||||
// TODO: New API to set title? This could say "Terminal bash input", etc.
|
||||
this.textarea.setAttribute('aria-label', Strings.promptLabel);
|
||||
this.textarea.setAttribute('aria-multiline', 'false');
|
||||
this.textarea.setAttribute('autocorrect', 'off');
|
||||
@@ -744,7 +625,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
this._colorManager.setTheme(this._theme);
|
||||
|
||||
const renderer = this._createRenderer();
|
||||
this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement);
|
||||
this._renderCoordinator = new RenderCoordinator(renderer, this.rows, this.screenElement, this.optionsService);
|
||||
this._renderCoordinator.onRender(e => this._onRender.fire(e));
|
||||
this.onResize(e => this._renderCoordinator.resize(e.cols, e.rows));
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
|
||||
import { Terminal } from './Terminal';
|
||||
import { AttributeData } from 'core/buffer/BufferLine';
|
||||
import { IColorManager, IColorSet } from 'ui/Types';
|
||||
import { IOptionsService } from 'common/options/Types';
|
||||
|
||||
export class TestTerminal extends Terminal {
|
||||
writeSync(data: string): void {
|
||||
@@ -29,9 +30,11 @@ export class MockTerminal implements ITerminal {
|
||||
onTitleChange: IEvent<string>;
|
||||
onScroll: IEvent<number>;
|
||||
onKey: IEvent<{ key: string; domEvent: KeyboardEvent; }>;
|
||||
onRender: IEvent<{ start: number; end: number; }>;
|
||||
onRender: IEvent<{ start: number
|
||||
; end: number; }>;
|
||||
onResize: IEvent<{ cols: number; rows: number; }>;
|
||||
markers: IMarker[];
|
||||
optionsService: IOptionsService;
|
||||
addMarker(cursorYOffset: number): IMarker {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
@@ -42,9 +45,6 @@ export class MockTerminal implements ITerminal {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
static string: any;
|
||||
getOption(key: any): any {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
setOption(key: any, value: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
+4
-4
@@ -8,6 +8,7 @@ import { ICharset, IAttributeData, ICellData, IBufferLine, CharData } from 'core
|
||||
import { ICircularList } from 'common/Types';
|
||||
import { IEvent } from 'common/EventEmitter2';
|
||||
import { IColorSet } from 'ui/Types';
|
||||
import { IOptionsService } from 'common/options/Types';
|
||||
|
||||
export type CustomKeyEventHandler = (event: KeyboardEvent) => boolean;
|
||||
|
||||
@@ -72,7 +73,6 @@ export interface IInputHandlingTerminal extends IEventEmitter {
|
||||
showCursor(): void;
|
||||
refresh(start: number, end: number): void;
|
||||
error(text: string, data?: any): void;
|
||||
setOption(key: string, value: any): void;
|
||||
tabSet(): void;
|
||||
handler(data: string): void;
|
||||
handleTitle(title: string): void;
|
||||
@@ -204,7 +204,6 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
|
||||
writeBuffer: string[];
|
||||
cursorHidden: boolean;
|
||||
cursorState: number;
|
||||
options: ITerminalOptions;
|
||||
buffer: IBuffer;
|
||||
buffers: IBufferSet;
|
||||
isFocused: boolean;
|
||||
@@ -212,6 +211,9 @@ export interface ITerminal extends IPublicTerminal, IElementAccessor, IBufferAcc
|
||||
viewport: IViewport;
|
||||
bracketedPasteMode: boolean;
|
||||
applicationCursor: boolean;
|
||||
optionsService: IOptionsService;
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
options: ITerminalOptions;
|
||||
|
||||
handler(data: string): void;
|
||||
scrollLines(disp: number, suppressScrollEvent?: boolean): void;
|
||||
@@ -265,8 +267,6 @@ export interface IPublicTerminal extends IDisposable, IEventEmitter {
|
||||
clear(): void;
|
||||
write(data: string): void;
|
||||
writeUtf8(data: Uint8Array): void;
|
||||
getOption(key: string): any;
|
||||
setOption(key: string, value: any): void;
|
||||
refresh(start: number, end: number): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
@@ -38,14 +38,6 @@ describe('clone', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('should clone null values', () => {
|
||||
const test: any = {
|
||||
a: null
|
||||
};
|
||||
|
||||
assert.deepEqual(clone(test), { a: null });
|
||||
});
|
||||
|
||||
it('should clone array values', () => {
|
||||
const test = {
|
||||
a: [1, 2, 3],
|
||||
|
||||
+2
-7
@@ -6,22 +6,17 @@
|
||||
/*
|
||||
* A simple utility for cloning values
|
||||
*/
|
||||
export function clone<T>(val: T, depth: number = 5): T | null {
|
||||
export function clone<T>(val: T, depth: number = 5): T {
|
||||
if (typeof val !== 'object') {
|
||||
return val;
|
||||
}
|
||||
|
||||
// cloning null always returns null
|
||||
if (val === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If we're cloning an array, use an array as the base, otherwise use an object
|
||||
const clonedObject: any = Array.isArray(val) ? [] : {};
|
||||
|
||||
for (const key in val) {
|
||||
// Recursively clone eack item unless we're at the maximum depth
|
||||
clonedObject[key] = depth <= 1 ? val[key] : clone(val[key], depth - 1);
|
||||
clonedObject[key] = depth <= 1 ? val[key] : (val[key] ? clone(val[key], depth - 1) : val[key]);
|
||||
}
|
||||
|
||||
return clonedObject as T;
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IOptionsService, ITerminalOptions, IPartialTerminalOptions } from 'common/options/Types';
|
||||
import { EventEmitter2, IEvent } from 'common/EventEmitter2';
|
||||
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
|
||||
// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been
|
||||
// made, apart from the conversion to base64.
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
|
||||
// TODO: Freeze?
|
||||
const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
cols: 80,
|
||||
rows: 24,
|
||||
cursorBlink: false,
|
||||
cursorStyle: 'block',
|
||||
bellSound: DEFAULT_BELL_SOUND,
|
||||
bellStyle: 'none',
|
||||
drawBoldTextInBrightColors: true,
|
||||
fontFamily: 'courier-new, courier, monospace',
|
||||
fontSize: 15,
|
||||
fontWeight: 'normal',
|
||||
fontWeightBold: 'bold',
|
||||
lineHeight: 1.0,
|
||||
letterSpacing: 0,
|
||||
scrollback: 1000,
|
||||
screenReaderMode: false,
|
||||
macOptionIsMeta: false,
|
||||
macOptionClickForcesSelection: false,
|
||||
disableStdin: false,
|
||||
allowTransparency: false,
|
||||
tabStopWidth: 8,
|
||||
theme: {},
|
||||
rightClickSelectsWord: isMac,
|
||||
rendererType: 'canvas',
|
||||
windowsMode: false,
|
||||
|
||||
convertEol: false,
|
||||
termName: 'xterm',
|
||||
screenKeys: false,
|
||||
debug: false,
|
||||
cancelEvents: false,
|
||||
useFlowControl: false
|
||||
};
|
||||
|
||||
/**
|
||||
* The set of options that only have an effect when set in the Terminal constructor.
|
||||
*/
|
||||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
|
||||
|
||||
export class OptionsService implements IOptionsService {
|
||||
public options: ITerminalOptions;
|
||||
|
||||
private _onOptionChange = new EventEmitter2<string>();
|
||||
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
|
||||
|
||||
constructor(options: IPartialTerminalOptions) {
|
||||
this.options = clone(DEFAULT_OPTIONS);
|
||||
Object.keys(options).forEach(k => {
|
||||
if (k in this.options) {
|
||||
const newValue = options[k as keyof IPartialTerminalOptions] as any;
|
||||
this.options[k] = newValue;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public setOption(key: string, value: any): void {
|
||||
if (!(key in DEFAULT_OPTIONS)) {
|
||||
throw new Error('No option with key "' + key + '"');
|
||||
}
|
||||
if (CONSTRUCTOR_ONLY_OPTIONS.indexOf(key) !== -1) {
|
||||
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 {
|
||||
switch (key) {
|
||||
case 'bellStyle':
|
||||
case 'cursorStyle':
|
||||
case 'fontWeight':
|
||||
case 'fontWeightBold':
|
||||
case 'rendererType':
|
||||
if (!value) {
|
||||
value = DEFAULT_OPTIONS[key];
|
||||
}
|
||||
break;
|
||||
case 'lineHeight':
|
||||
case 'tabStopWidth':
|
||||
if (value < 1) {
|
||||
throw new Error(`${key} cannot be less than 1, value: ${value}`);
|
||||
}
|
||||
break;
|
||||
case 'scrollback':
|
||||
value = Math.min(value, 4294967295);
|
||||
if (value < 0) {
|
||||
throw new Error(`${key} cannot be less than 0, value: ${value}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
public getOption(key: string): any {
|
||||
if (!(key in DEFAULT_OPTIONS)) {
|
||||
throw new Error(`No option with key "${key}"`);
|
||||
}
|
||||
return this.options[key];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
/**
|
||||
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IEvent } from 'common/EventEmitter2';
|
||||
|
||||
export interface IOptionsService {
|
||||
readonly onOptionChange: IEvent<string>;
|
||||
// TODO: as const?
|
||||
readonly options: ITerminalOptions;
|
||||
|
||||
setOption<T>(key: string, value: T): void;
|
||||
getOption<T>(key: string): T | undefined;
|
||||
}
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900';
|
||||
|
||||
export type RendererType = 'dom' | 'canvas';
|
||||
|
||||
export interface IPartialTerminalOptions {
|
||||
allowTransparency?: boolean;
|
||||
bellSound?: string;
|
||||
bellStyle?: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
|
||||
cols?: number;
|
||||
cursorBlink?: boolean;
|
||||
cursorStyle?: 'block' | 'underline' | 'bar';
|
||||
disableStdin?: boolean;
|
||||
drawBoldTextInBrightColors?: boolean;
|
||||
fontSize?: number;
|
||||
fontFamily?: string;
|
||||
fontWeight?: FontWeight;
|
||||
fontWeightBold?: FontWeight;
|
||||
letterSpacing?: number;
|
||||
lineHeight?: number;
|
||||
macOptionIsMeta?: boolean;
|
||||
macOptionClickForcesSelection?: boolean;
|
||||
rendererType?: RendererType;
|
||||
rightClickSelectsWord?: boolean;
|
||||
rows?: number;
|
||||
screenReaderMode?: boolean;
|
||||
scrollback?: number;
|
||||
tabStopWidth?: number;
|
||||
theme?: ITheme;
|
||||
windowsMode?: boolean;
|
||||
}
|
||||
|
||||
export interface ITerminalOptions {
|
||||
allowTransparency: boolean;
|
||||
bellSound: string;
|
||||
bellStyle: 'none' /*| 'visual'*/ | 'sound' /*| 'both'*/;
|
||||
cols: number;
|
||||
cursorBlink: boolean;
|
||||
cursorStyle: 'block' | 'underline' | 'bar';
|
||||
disableStdin: boolean;
|
||||
drawBoldTextInBrightColors: boolean;
|
||||
fontSize: number;
|
||||
fontFamily: string;
|
||||
fontWeight: FontWeight;
|
||||
fontWeightBold: FontWeight;
|
||||
letterSpacing: number;
|
||||
lineHeight: number;
|
||||
macOptionIsMeta: boolean;
|
||||
macOptionClickForcesSelection: boolean;
|
||||
rendererType: RendererType;
|
||||
rightClickSelectsWord: boolean;
|
||||
rows: number;
|
||||
screenReaderMode: boolean;
|
||||
scrollback: number;
|
||||
tabStopWidth: number;
|
||||
theme: ITheme;
|
||||
windowsMode: boolean;
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
convertEol: boolean;
|
||||
debug: boolean;
|
||||
screenKeys: boolean;
|
||||
termName: string;
|
||||
useFlowControl: boolean;
|
||||
}
|
||||
|
||||
export interface ITheme {
|
||||
foreground?: string;
|
||||
background?: string;
|
||||
cursor?: string;
|
||||
cursorAccent?: string;
|
||||
selection?: string;
|
||||
black?: string;
|
||||
red?: string;
|
||||
green?: string;
|
||||
yellow?: string;
|
||||
blue?: string;
|
||||
magenta?: string;
|
||||
cyan?: string;
|
||||
white?: string;
|
||||
brightBlack?: string;
|
||||
brightRed?: string;
|
||||
brightGreen?: string;
|
||||
brightYellow?: string;
|
||||
brightBlue?: string;
|
||||
brightMagenta?: string;
|
||||
brightCyan?: string;
|
||||
brightWhite?: string;
|
||||
}
|
||||
@@ -152,7 +152,7 @@ export class Terminal implements ITerminalApi {
|
||||
public getOption(key: 'handler'): (data: string) => void;
|
||||
public getOption(key: string): any;
|
||||
public getOption(key: any): any {
|
||||
return this._core.getOption(key);
|
||||
return this._core.optionsService.getOption(key);
|
||||
}
|
||||
public setOption(key: 'bellSound' | 'fontFamily' | 'termName', value: string): void;
|
||||
public setOption(key: 'fontWeight' | 'fontWeightBold', value: 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900'): void;
|
||||
@@ -166,7 +166,7 @@ export class Terminal implements ITerminalApi {
|
||||
public setOption(key: 'cols' | 'rows', value: number): void;
|
||||
public setOption(key: string, value: any): void;
|
||||
public setOption(key: any, value: any): void {
|
||||
this._core.setOption(key, value);
|
||||
this._core.optionsService.setOption(key, value);
|
||||
}
|
||||
public refresh(start: number, end: number): void {
|
||||
this._core.refresh(start, end);
|
||||
|
||||
@@ -11,6 +11,7 @@ import { ScreenDprMonitor } from 'ui/ScreenDprMonitor';
|
||||
import { addDisposableDomListener } from 'ui/Lifecycle';
|
||||
import { IColorSet } from 'ui/Types';
|
||||
import { CharacterJoinerHandler } from '../Types';
|
||||
import { IOptionsService } from 'common/options/Types';
|
||||
|
||||
export class RenderCoordinator extends Disposable {
|
||||
private _renderDebouncer: RenderDebouncer;
|
||||
@@ -33,7 +34,8 @@ export class RenderCoordinator extends Disposable {
|
||||
constructor(
|
||||
private _renderer: IRenderer,
|
||||
private _rowCount: number,
|
||||
screenElement: HTMLElement
|
||||
screenElement: HTMLElement,
|
||||
optionsService: IOptionsService
|
||||
) {
|
||||
super();
|
||||
this._renderDebouncer = new RenderDebouncer((start, end) => this._renderRows(start, end));
|
||||
@@ -43,6 +45,8 @@ export class RenderCoordinator extends Disposable {
|
||||
this._screenDprMonitor.setListener(() => this._renderer.onDevicePixelRatioChange());
|
||||
this.register(this._screenDprMonitor);
|
||||
|
||||
this.register(optionsService.onOptionChange(() => this._renderer.onOptionsChanged()));
|
||||
|
||||
// dprchange should handle this case, we need this as well for browsers that don't support the
|
||||
// matchMedia query.
|
||||
this.register(addDisposableDomListener(window, 'resize', () => this._renderer.onDevicePixelRatioChange()));
|
||||
@@ -144,10 +148,6 @@ export class RenderCoordinator extends Disposable {
|
||||
this._renderer.onCursorMove();
|
||||
}
|
||||
|
||||
public onOptionsChanged(): void {
|
||||
this._renderer.onOptionsChanged();
|
||||
}
|
||||
|
||||
public clear(): void {
|
||||
this._renderer.clear();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user