mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into options3
This commit is contained in:
@@ -179,7 +179,7 @@ Xterm.js is used in several world-class applications to provide great terminal e
|
||||
- [**WizardWebssh**](https://gitlab.com/mikeramsey/wizardwebssh): A terminal with Pyqt5 Widget for embedding, which can be used as an ssh client to connect to your ssh servers. It is written in Python, based on tornado, paramiko, and xterm.js.
|
||||
- [**Wizard Assistant**](https://wizardassistant.com/): Wizard Assistant comes with advanced automation tools, preloaded common and special time-saving commands, and a built-in SSH terminal. Now you can remotely administer, troubleshoot, and analyze any system with ease.
|
||||
- [**ucli**](https://github.com/tsadarsh/ucli): Command Line for everyone :family_man_woman_girl_boy: at [www.ucli.tech](https://www.ucli.tech).
|
||||
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone.
|
||||
- [**Tess**](https://github.com/SquitchYT/Tess/): Simple Terminal Fully Customizable for Everyone. Discover more at [tessapp.dev](https://tessapp.dev)
|
||||
- [**HashiCorp Nomad**](https://www.nomadproject.io/): A container orchestrator with the ability to connect to remote tasks via a web interface using websockets and xterm.js.
|
||||
- [**TermPair**](https://github.com/cs01/termpair): View and control terminals from your browser with end-to-end encryption
|
||||
- [**gdbgui**](https://github.com/cs01/gdbgui): Browser-based frontend to gdb (gnu debugger)
|
||||
|
||||
+2
-1
@@ -133,7 +133,8 @@
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.xterm.xterm-cursor-pointer {
|
||||
.xterm.xterm-cursor-pointer,
|
||||
.xterm .xterm-cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import { IBufferService, IUnicodeService } from 'common/services/Services';
|
||||
import { Linkifier } from 'browser/Linkifier';
|
||||
import { MockLogService, MockUnicodeService } from 'common/TestUtils.test';
|
||||
import { IRegisteredLinkMatcher, IMouseZoneManager, IMouseZone } from 'browser/Types';
|
||||
import { IMarker } from 'common/Types';
|
||||
import { IMarker, ITerminalOptions } from 'common/Types';
|
||||
|
||||
const INIT_COLS = 80;
|
||||
const INIT_ROWS = 24;
|
||||
@@ -1501,6 +1501,22 @@ describe('Terminal', () => {
|
||||
assert.deepEqual(markers.map(el => el.line), [-1, -1, 0, 1, 2]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
beforeEach(async () => {
|
||||
term = new TestTerminal({});
|
||||
});
|
||||
it('get options', () => {
|
||||
assert.equal(term.options.cols, 80);
|
||||
assert.equal(term.options.rows, 24);
|
||||
});
|
||||
it('set options', async () => {
|
||||
term.options.cols = 40;
|
||||
assert.equal(term.options.cols, 40);
|
||||
term.options.rows = 20;
|
||||
assert.equal(term.options.rows, 20);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
class TestLinkifier extends Linkifier {
|
||||
|
||||
@@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
@@ -52,9 +52,9 @@ import { MouseService } from 'browser/services/MouseService';
|
||||
import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
|
||||
import { rgba } from 'browser/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { ITerminalOptions } from 'common/services/Services';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -74,9 +74,6 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
|
||||
public browser: IBrowser = Browser as any;
|
||||
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
public get options(): IInitializedTerminalOptions { return this.optionsService.options; }
|
||||
|
||||
private _customKeyEventHandler: CustomKeyEventHandler | undefined;
|
||||
|
||||
// browser services
|
||||
@@ -537,7 +534,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._mouseZoneManager);
|
||||
this.register(this.onScroll(() => this._mouseZoneManager!.clearAll()));
|
||||
this.linkifier.attachToDom(this.element, this._mouseZoneManager);
|
||||
this.linkifier2.attachToDom(this.element, this._mouseService, this._renderService);
|
||||
this.linkifier2.attachToDom(this.screenElement, this._mouseService, this._renderService);
|
||||
|
||||
// This event listener must be registered aftre MouseZoneManager is created
|
||||
this.register(addDisposableDomListener(this.element, 'mousedown', (e: MouseEvent) => this._selectionService!.onMouseDown(e)));
|
||||
|
||||
Vendored
-1
@@ -16,7 +16,6 @@ export interface ITerminal extends IPublicTerminal, ICoreTerminal {
|
||||
browser: IBrowser;
|
||||
buffer: IBuffer;
|
||||
viewport: IViewport | undefined;
|
||||
// TODO: We should remove options once components adopt optionsService
|
||||
options: ITerminalOptions;
|
||||
linkifier: ILinkifier;
|
||||
linkifier2: ILinkifier2;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { Terminal as ITerminalApi, ITerminalOptions, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm';
|
||||
import { Terminal as ITerminalApi, IMarker, IDisposable, ILinkMatcherOptions, ITheme, ILocalizableStrings, ITerminalAddon, ISelectionPosition, IBufferNamespace as IBufferNamespaceApi, IParser, ILinkProvider, IUnicodeHandling, FontWeight, IModes } from 'xterm';
|
||||
import { ITerminal } from 'browser/Types';
|
||||
import { Terminal as TerminalCore } from 'browser/Terminal';
|
||||
import * as Strings from 'browser/LocalizableStrings';
|
||||
@@ -12,16 +12,42 @@ import { ParserApi } from 'common/public/ParserApi';
|
||||
import { UnicodeApi } from 'common/public/UnicodeApi';
|
||||
import { AddonManager } from 'common/public/AddonManager';
|
||||
import { BufferNamespaceApi } from 'common/public/BufferNamespaceApi';
|
||||
import { ITerminalOptions } from 'common/Types';
|
||||
|
||||
/**
|
||||
* The set of options that only have an effect when set in the Terminal constructor.
|
||||
*/
|
||||
const CONSTRUCTOR_ONLY_OPTIONS = ['cols', 'rows'];
|
||||
|
||||
export class Terminal implements ITerminalApi {
|
||||
private _core: ITerminal;
|
||||
private _addonManager: AddonManager;
|
||||
private _parser: IParser | undefined;
|
||||
private _buffer: BufferNamespaceApi | undefined;
|
||||
private _publicOptions: ITerminalOptions;
|
||||
|
||||
constructor(options?: ITerminalOptions) {
|
||||
this._core = new TerminalCore(options);
|
||||
this._addonManager = new AddonManager();
|
||||
|
||||
this._publicOptions = new Proxy(this._core.options, {
|
||||
get: (target, propName: string): any => {
|
||||
return target[propName];
|
||||
},
|
||||
set: (target, propName: string, value): any => {
|
||||
this._checkReadonlyOptions(propName);
|
||||
target[propName] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private _checkReadonlyOptions(propName: string): void {
|
||||
// Throw an error if any constructor only option is modified
|
||||
// from terminal.options
|
||||
// Modifications from anywhere else are allowed
|
||||
if (CONSTRUCTOR_ONLY_OPTIONS.includes(propName)) {
|
||||
throw new Error(`Option "${propName}" can only be set in the constructor`);
|
||||
}
|
||||
}
|
||||
|
||||
private _checkProposedApi(): void {
|
||||
@@ -90,7 +116,12 @@ export class Terminal implements ITerminalApi {
|
||||
};
|
||||
}
|
||||
public get options(): ITerminalOptions {
|
||||
return this._core.options;
|
||||
return this._publicOptions;
|
||||
}
|
||||
public set options(options: ITerminalOptions) {
|
||||
for (const propName in options) {
|
||||
this._publicOptions[propName] = options[propName];
|
||||
}
|
||||
}
|
||||
public blur(): void {
|
||||
this._core.blur();
|
||||
@@ -216,6 +247,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._checkReadonlyOptions(key);
|
||||
this._core.optionsService.setOption(key, value);
|
||||
}
|
||||
public refresh(start: number, end: number): void {
|
||||
|
||||
@@ -122,5 +122,13 @@ describe('SelectionModel', () => {
|
||||
model.selectionEnd = [5, 2];
|
||||
assert.deepEqual(model.finalSelectionEnd, [5, 2]);
|
||||
});
|
||||
it('should not include a trailing EOL when the selection ends at the end of a line', () => {
|
||||
model.selectionStart = [0, 0];
|
||||
model.selectionStartLength = 80;
|
||||
assert.deepEqual(model.finalSelectionEnd, [80, 0]);
|
||||
model.selectionStart = [0, 0];
|
||||
model.selectionStartLength = 160;
|
||||
assert.deepEqual(model.finalSelectionEnd, [80, 1]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -79,6 +79,10 @@ export class SelectionModel {
|
||||
if (!this.selectionEnd || this.areSelectionValuesReversed()) {
|
||||
const startPlusLength = this.selectionStart[0] + this.selectionStartLength;
|
||||
if (startPlusLength > this._bufferService.cols) {
|
||||
// Ensure the trailing EOL isn't included when the selection ends on the right edge
|
||||
if (startPlusLength % this._bufferService.cols === 0) {
|
||||
return [this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols) - 1];
|
||||
}
|
||||
return [startPlusLength % this._bufferService.cols, this.selectionStart[1] + Math.floor(startPlusLength / this._bufferService.cols)];
|
||||
}
|
||||
return [startPlusLength, this.selectionStart[1]];
|
||||
|
||||
@@ -22,12 +22,12 @@
|
||||
*/
|
||||
|
||||
import { Disposable } from 'common/Lifecycle';
|
||||
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum } from 'common/services/Services';
|
||||
import { IInstantiationService, IOptionsService, IBufferService, ILogService, ICharsetService, ICoreService, ICoreMouseService, IUnicodeService, IDirtyRowService, LogLevelEnum, ITerminalOptions } from 'common/services/Services';
|
||||
import { InstantiationService } from 'common/services/InstantiationService';
|
||||
import { LogService } from 'common/services/LogService';
|
||||
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
|
||||
import { OptionsService } from 'common/services/OptionsService';
|
||||
import { ITerminalOptions, IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource } from 'common/Types';
|
||||
import { IDisposable, IBufferLine, IAttributeData, ICoreTerminal, IKeyboardEvent, IScrollEvent, ScrollSource, ITerminalOptions as IPublicTerminalOptions } from 'common/Types';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { CoreMouseService } from 'common/services/CoreMouseService';
|
||||
@@ -86,7 +86,12 @@ 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; }
|
||||
public get options(): ITerminalOptions { return this.optionsService.options; }
|
||||
public set options(options: ITerminalOptions) {
|
||||
for (const key in options) {
|
||||
this.optionsService.options[key] = options[key];
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
options: Partial<ITerminalOptions>
|
||||
|
||||
@@ -386,6 +386,56 @@ describe('InputHandler', () => {
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.translateToString(false), Array(bufferService.cols + 1).join(' '));
|
||||
|
||||
});
|
||||
it('eraseInLine reflow', async () => {
|
||||
const bufferService = new MockBufferService(80, 30);
|
||||
const inputHandler = new TestInputHandler(
|
||||
bufferService,
|
||||
new MockCharsetService(),
|
||||
new MockCoreService(),
|
||||
new MockDirtyRowService(),
|
||||
new MockLogService(),
|
||||
new MockOptionsService(),
|
||||
new MockCoreMouseService(),
|
||||
new MockUnicodeService()
|
||||
);
|
||||
|
||||
const resetToBaseState = async (): Promise<void> => {
|
||||
// reset and add a wrapped line
|
||||
bufferService.buffer.y = 0;
|
||||
bufferService.buffer.x = 0;
|
||||
await inputHandler.parseP(Array(bufferService.cols + 1).join('a')); // line 0
|
||||
await inputHandler.parseP(Array(bufferService.cols + 10).join('a')); // line 1 and 2
|
||||
for (let i = 3; i < bufferService.rows; ++i) await inputHandler.parseP(Array(bufferService.cols + 1).join('a'));
|
||||
|
||||
// confirm precondition that line 2 is wrapped
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
|
||||
};
|
||||
|
||||
// params[0] - erase from the cursor through the end of the row.
|
||||
await resetToBaseState();
|
||||
bufferService.buffer.y = 2;
|
||||
bufferService.buffer.x = 40;
|
||||
inputHandler.eraseInLine(Params.fromArray([0]));
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
|
||||
bufferService.buffer.y = 2;
|
||||
bufferService.buffer.x = 0;
|
||||
inputHandler.eraseInLine(Params.fromArray([0]));
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
|
||||
|
||||
// params[1] - erase from the beginning of the line through the cursor
|
||||
await resetToBaseState();
|
||||
bufferService.buffer.y = 2;
|
||||
bufferService.buffer.x = 40;
|
||||
inputHandler.eraseInLine(Params.fromArray([1]));
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, true);
|
||||
|
||||
// params[2] - erase complete line
|
||||
await resetToBaseState();
|
||||
bufferService.buffer.y = 2;
|
||||
bufferService.buffer.x = 40;
|
||||
inputHandler.eraseInLine(Params.fromArray([2]));
|
||||
assert.equal(bufferService.buffer.lines.get(2)!.isWrapped, false);
|
||||
});
|
||||
it('eraseInDisplay', async () => {
|
||||
const bufferService = new MockBufferService(80, 7);
|
||||
const inputHandler = new TestInputHandler(
|
||||
|
||||
+12
-12
@@ -25,7 +25,7 @@ import { IBuffer } from 'common/buffer/Types';
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
*/
|
||||
const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
|
||||
const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
|
||||
|
||||
/**
|
||||
* VT commands done by the parser - FIXME: move this to the parser?
|
||||
@@ -167,7 +167,7 @@ class DECRQSS implements IDcsHandler {
|
||||
break;
|
||||
case 'r': // DECSTBM
|
||||
const pt = '' + (this._bufferService.buffer.scrollTop + 1) +
|
||||
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
|
||||
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
|
||||
break;
|
||||
case 'm': // SGR
|
||||
@@ -175,7 +175,7 @@ class DECRQSS implements IDcsHandler {
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`);
|
||||
break;
|
||||
case ' q': // DECSCUSR
|
||||
const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 };
|
||||
const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 };
|
||||
let style = STYLES[this._optionsService.options.cursorStyle];
|
||||
style -= this._optionsService.options.cursorBlink ? 1 : 0;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
|
||||
@@ -855,10 +855,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* - any cursor movement sequence keeps working as expected
|
||||
*/
|
||||
if (this._activeBuffer.x === 0
|
||||
&& this._activeBuffer.y > this._activeBuffer.scrollTop
|
||||
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
|
||||
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped)
|
||||
{
|
||||
&& this._activeBuffer.y > this._activeBuffer.scrollTop
|
||||
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
|
||||
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {
|
||||
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
|
||||
this._activeBuffer.y--;
|
||||
this._activeBuffer.x = this._bufferService.cols - 1;
|
||||
@@ -1195,6 +1194,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* @param y row index
|
||||
* @param start first cell index to be erased
|
||||
* @param end end - 1 is last erased cell
|
||||
* @param cleanWrap clear the isWrapped flag
|
||||
*/
|
||||
private _eraseInBufferLine(y: number, start: number, end: number, clearWrap: boolean = false): void {
|
||||
const line = this._activeBuffer.lines.get(this._activeBuffer.ybase + y)!;
|
||||
@@ -1320,13 +1320,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._restrictCursor(this._bufferService.cols);
|
||||
switch (params.params[0]) {
|
||||
case 0:
|
||||
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols);
|
||||
this._eraseInBufferLine(this._activeBuffer.y, this._activeBuffer.x, this._bufferService.cols, this._activeBuffer.x === 0);
|
||||
break;
|
||||
case 1:
|
||||
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1);
|
||||
this._eraseInBufferLine(this._activeBuffer.y, 0, this._activeBuffer.x + 1, false);
|
||||
break;
|
||||
case 2:
|
||||
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols);
|
||||
this._eraseInBufferLine(this._activeBuffer.y, 0, this._bufferService.cols, true);
|
||||
break;
|
||||
}
|
||||
this._dirtyRowService.markDirty(this._activeBuffer.y);
|
||||
@@ -2264,7 +2264,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
// exit early if can decide color mode with semicolons
|
||||
if ((accu[1] === 5 && advance + cSpace >= 2)
|
||||
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|
||||
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|
||||
break;
|
||||
}
|
||||
// offset colorSpace slot for semicolon mode
|
||||
@@ -2683,7 +2683,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
const top = params.params[0] || 1;
|
||||
let bottom: number;
|
||||
|
||||
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
|
||||
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
|
||||
bottom = this._bufferService.rows;
|
||||
}
|
||||
|
||||
|
||||
@@ -121,16 +121,19 @@ 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<string> = new EventEmitter<string>().event;
|
||||
constructor(testOptions?: Partial<ITerminalOptions>) {
|
||||
if (testOptions) {
|
||||
for (const key of Object.keys(testOptions)) {
|
||||
this.options[key] = testOptions[key];
|
||||
this.publicOptions[key] = testOptions[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
public setOptions(options: ITerminalOptions): void {
|
||||
for (const key of Object.keys(options)) {
|
||||
this.options[key] = options[key];
|
||||
}
|
||||
}
|
||||
public setOption<T>(key: string, value: T): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Vendored
+1
@@ -46,6 +46,7 @@ export interface IKeyboardEvent {
|
||||
ctrlKey: boolean;
|
||||
shiftKey: boolean;
|
||||
metaKey: boolean;
|
||||
/** @deprecated See KeyboardEvent.keyCode */
|
||||
keyCode: number;
|
||||
key: string;
|
||||
type: string;
|
||||
|
||||
@@ -57,16 +57,10 @@ export const DEFAULT_OPTIONS: Readonly<ITerminalOptions> = {
|
||||
|
||||
const FONT_WEIGHT_OPTIONS: Extract<FontWeight, string>[] = ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'];
|
||||
|
||||
/**
|
||||
* 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 serviceBrand: any;
|
||||
|
||||
public options: ITerminalOptions;
|
||||
public publicOptions: ITerminalOptions;
|
||||
|
||||
private _onOptionChange = new EventEmitter<string>();
|
||||
public get onOptionChange(): IEvent<string> { return this._onOptionChange.event; }
|
||||
@@ -86,11 +80,10 @@ export class OptionsService implements IOptionsService {
|
||||
}
|
||||
|
||||
// set up getters and setters for each option
|
||||
this.options = this._setupOptions(defaultOptions, false);
|
||||
this.publicOptions = this._setupOptions(defaultOptions, true);
|
||||
this.options = this._setupOptions(defaultOptions);
|
||||
}
|
||||
|
||||
private _setupOptions(options: ITerminalOptions, isPublic: boolean): ITerminalOptions {
|
||||
private _setupOptions(options: ITerminalOptions): ITerminalOptions {
|
||||
return new Proxy(options, {
|
||||
get: (target, propName: string): any => {
|
||||
if (!(propName in DEFAULT_OPTIONS)) {
|
||||
@@ -103,13 +96,6 @@ export class OptionsService implements IOptionsService {
|
||||
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 (target[propName] !== value) {
|
||||
@@ -122,7 +108,7 @@ export class OptionsService implements IOptionsService {
|
||||
}
|
||||
|
||||
public setOption(key: string, value: any): void {
|
||||
this.publicOptions[key] = value;
|
||||
this.options[key] = value;
|
||||
}
|
||||
|
||||
private _sanitizeAndValidateOption(key: string, value: any): any {
|
||||
@@ -145,7 +131,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) {
|
||||
@@ -177,6 +163,6 @@ export class OptionsService implements IOptionsService {
|
||||
}
|
||||
|
||||
public getOption(key: string): any {
|
||||
return this.publicOptions[key];
|
||||
return this.options[key];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,14 @@ export interface IInstantiationService {
|
||||
createInstance<Ctor extends new (...args: any[]) => any, R extends InstanceType<Ctor>>(t: Ctor, ...args: GetLeadingNonServiceArgs<ConstructorParameters<Ctor>>): R;
|
||||
}
|
||||
|
||||
export enum LogLevelEnum {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3,
|
||||
OFF = 4
|
||||
}
|
||||
|
||||
export const ILogService = createDecorator<ILogService>('LogService');
|
||||
export interface ILogService {
|
||||
serviceBrand: undefined;
|
||||
@@ -181,7 +189,6 @@ export interface IOptionsService {
|
||||
serviceBrand: undefined;
|
||||
|
||||
readonly options: ITerminalOptions;
|
||||
readonly publicOptions: ITerminalOptions;
|
||||
|
||||
readonly onOptionChange: IEvent<string>;
|
||||
|
||||
@@ -191,13 +198,7 @@ export interface IOptionsService {
|
||||
|
||||
export type FontWeight = 'normal' | 'bold' | '100' | '200' | '300' | '400' | '500' | '600' | '700' | '800' | '900' | number;
|
||||
export type LogLevel = 'debug' | 'info' | 'warn' | 'error' | 'off';
|
||||
export enum LogLevelEnum {
|
||||
DEBUG = 0,
|
||||
INFO = 1,
|
||||
WARN = 2,
|
||||
ERROR = 3,
|
||||
OFF = 4
|
||||
}
|
||||
|
||||
export type RendererType = 'dom' | 'canvas';
|
||||
|
||||
export interface ITerminalOptions {
|
||||
@@ -207,6 +208,7 @@ export interface ITerminalOptions {
|
||||
bellSound: string;
|
||||
bellStyle: 'none' | 'sound' /* | 'visual' | 'both' */;
|
||||
cols: number;
|
||||
convertEol: boolean;
|
||||
cursorBlink: boolean;
|
||||
cursorStyle: 'block' | 'underline' | 'bar';
|
||||
cursorWidth: number;
|
||||
@@ -240,7 +242,6 @@ export interface ITerminalOptions {
|
||||
|
||||
[key: string]: any;
|
||||
cancelEvents: boolean;
|
||||
convertEol: boolean;
|
||||
termName: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { assert } from 'chai';
|
||||
import { pollFor, timeout, writeSync, openTerminal, launchBrowser } from './TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { fail } from 'assert';
|
||||
|
||||
const APP = 'http://127.0.0.1:3001/test';
|
||||
|
||||
@@ -160,6 +161,36 @@ describe('API Integration Tests', function(): void {
|
||||
assert.equal(await page.evaluate(`window.term.getOption('rendererType')`), 'dom');
|
||||
});
|
||||
|
||||
describe('options', () => {
|
||||
it('getter', async () => {
|
||||
await openTerminal(page);
|
||||
assert.equal(await page.evaluate(`window.term.options.rendererType`), 'canvas');
|
||||
assert.equal(await page.evaluate(`window.term.options.cols`), 80);
|
||||
assert.equal(await page.evaluate(`window.term.options.rows`), 24);
|
||||
});
|
||||
it('setter', async () => {
|
||||
await openTerminal(page);
|
||||
try {
|
||||
await page.evaluate('window.term.options.cols = 40');
|
||||
fail();
|
||||
} catch {}
|
||||
try {
|
||||
await page.evaluate('window.term.options.rows = 20');
|
||||
fail();
|
||||
} catch {}
|
||||
await page.evaluate('window.term.options.scrollback = 1');
|
||||
assert.equal(await page.evaluate(`window.term.options.scrollback`), 1);
|
||||
await page.evaluate(`
|
||||
window.term.options = {
|
||||
fontSize: 30,
|
||||
fontFamily: 'Arial'
|
||||
};
|
||||
`);
|
||||
assert.equal(await page.evaluate(`window.term.options.fontSize`), 30);
|
||||
assert.equal(await page.evaluate(`window.term.options.fontFamily`), 'Arial');
|
||||
});
|
||||
});
|
||||
|
||||
describe('renderer', () => {
|
||||
it('foreground', async () => {
|
||||
await openTerminal(page, { rendererType: 'dom' });
|
||||
|
||||
Vendored
+22
-4
@@ -636,9 +636,27 @@ declare module 'xterm' {
|
||||
readonly modes: IModes;
|
||||
|
||||
/**
|
||||
* Get the terminal options
|
||||
* Gets or sets the terminal options. This supports setting multiple options.
|
||||
*
|
||||
* @example Get a single option
|
||||
* ```typescript
|
||||
* console.log(terminal.options.fontSize);
|
||||
* ```
|
||||
*
|
||||
* @example Set a single option
|
||||
* ```typescript
|
||||
* terminal.options.fontSize = 12;
|
||||
* ```
|
||||
*
|
||||
* @example Set multiple options
|
||||
* ```typescript
|
||||
* terminal.options = {
|
||||
* fontSize: 12,
|
||||
* fontFamily: 'Arial',
|
||||
* };
|
||||
* ```
|
||||
*/
|
||||
readonly options: ITerminalOptions;
|
||||
options: ITerminalOptions;
|
||||
|
||||
/**
|
||||
* Natural language strings that can be localized.
|
||||
@@ -1265,12 +1283,12 @@ declare module 'xterm' {
|
||||
*/
|
||||
interface IBufferCellPosition {
|
||||
/**
|
||||
* The x position within the buffer.
|
||||
* The x position within the buffer (1-based).
|
||||
*/
|
||||
x: number;
|
||||
|
||||
/**
|
||||
* The y position within the buffer.
|
||||
* The y position within the buffer (1-based).
|
||||
*/
|
||||
y: number;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user