mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge pull request #3524 from jerch/osc_colors
OSC 4/10/11/12 color handling
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColorRGB } from 'common/Types';
|
||||
|
||||
/**
|
||||
* Helper functions where the source type is "channels" (individual color channels as numbers).
|
||||
@@ -17,6 +18,8 @@ export namespace channels {
|
||||
}
|
||||
|
||||
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
|
||||
// Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
|
||||
// on LE systems, before it can be used for direct 32-bit buffer writes.
|
||||
// >>> 0 forces an unsigned int
|
||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||
}
|
||||
@@ -81,6 +84,10 @@ export namespace color {
|
||||
rgba: channels.toRgba(r, g, b, a)
|
||||
};
|
||||
}
|
||||
|
||||
export function toColorRGB(color: IColor): IColorRGB {
|
||||
return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,6 +204,7 @@ export namespace rgba {
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
|
||||
// FIXME: Move this to channels NS?
|
||||
export function toChannels(value: number): [number, number, number, number] {
|
||||
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
|
||||
}
|
||||
|
||||
@@ -7,6 +7,16 @@ import { IColorManager, IColor, IColorSet, IColorContrastCache } from 'browser/T
|
||||
import { ITheme } from 'common/services/Services';
|
||||
import { channels, color, css } from 'browser/Color';
|
||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
||||
import { ColorIndex } from 'common/Types';
|
||||
|
||||
|
||||
interface IRestoreColorSet {
|
||||
foreground: IColor;
|
||||
background: IColor;
|
||||
cursor: IColor;
|
||||
ansi: IColor[];
|
||||
}
|
||||
|
||||
|
||||
const DEFAULT_FOREGROUND = css.toColor('#ffffff');
|
||||
const DEFAULT_BACKGROUND = css.toColor('#000000');
|
||||
@@ -73,6 +83,7 @@ export class ColorManager implements IColorManager {
|
||||
private _ctx: CanvasRenderingContext2D;
|
||||
private _litmusColor: CanvasGradient;
|
||||
private _contrastCache: IColorContrastCache;
|
||||
private _restoreColors!: IRestoreColorSet;
|
||||
|
||||
constructor(document: Document, public allowTransparency: boolean) {
|
||||
const canvas = document.createElement('canvas');
|
||||
@@ -96,6 +107,7 @@ export class ColorManager implements IColorManager {
|
||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||
contrastCache: this._contrastCache
|
||||
};
|
||||
this._updateRestoreColors();
|
||||
}
|
||||
|
||||
public onOptionsChange(key: string): void {
|
||||
@@ -142,6 +154,39 @@ export class ColorManager implements IColorManager {
|
||||
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
|
||||
// Clear our the cache
|
||||
this._contrastCache.clear();
|
||||
this._updateRestoreColors();
|
||||
}
|
||||
|
||||
public restoreColor(slot?: ColorIndex): void {
|
||||
// unset slot restores all ansi colors
|
||||
if (slot === undefined) {
|
||||
for (let i = 0; i < this._restoreColors.ansi.length; ++i) {
|
||||
this.colors.ansi[i] = this._restoreColors.ansi[i];
|
||||
}
|
||||
return;
|
||||
}
|
||||
switch (slot) {
|
||||
case ColorIndex.FOREGROUND:
|
||||
this.colors.foreground = this._restoreColors.foreground;
|
||||
break;
|
||||
case ColorIndex.BACKGROUND:
|
||||
this.colors.background = this._restoreColors.background;
|
||||
break;
|
||||
case ColorIndex.CURSOR:
|
||||
this.colors.cursor = this._restoreColors.cursor;
|
||||
break;
|
||||
default:
|
||||
this.colors.ansi[slot] = this._restoreColors.ansi[slot];
|
||||
}
|
||||
}
|
||||
|
||||
private _updateRestoreColors(): void {
|
||||
this._restoreColors = {
|
||||
foreground: this.colors.foreground,
|
||||
background: this.colors.background,
|
||||
cursor: this.colors.cursor,
|
||||
ansi: [...this.colors.ansi]
|
||||
};
|
||||
}
|
||||
|
||||
private _parseColor(
|
||||
|
||||
+52
-14
@@ -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, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, ColorRequestType } 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 { rgba } from 'browser/Color';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
import { ITerminalOptions } from 'common/services/Services';
|
||||
import { toRgbString } from 'common/input/XParseColor';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
||||
@@ -164,7 +164,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
|
||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
||||
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
|
||||
this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
|
||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
|
||||
@@ -174,17 +174,55 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
|
||||
}
|
||||
|
||||
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
|
||||
if (!this._colorManager) { return; }
|
||||
|
||||
for (const ansiColor of event.colors) {
|
||||
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
|
||||
|
||||
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
|
||||
/**
|
||||
* Handle color event from inputhandler for OSC 4|104 | 10|110 | 11|111 | 12|112.
|
||||
* An event from OSC 4|104 may contain multiple set or report requests, and multiple
|
||||
* or none restore requests (resetting all),
|
||||
* while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.
|
||||
*/
|
||||
private _handleColorEvent(event: IColorEvent): void {
|
||||
if (!this._colorManager) return;
|
||||
for (const req of event) {
|
||||
let acc: 'foreground' | 'background' | 'cursor' | 'ansi' | undefined = undefined;
|
||||
let ident = '';
|
||||
switch (req.index) {
|
||||
case ColorIndex.FOREGROUND: // OSC 10 | 110
|
||||
acc = 'foreground';
|
||||
ident = '10';
|
||||
break;
|
||||
case ColorIndex.BACKGROUND: // OSC 11 | 111
|
||||
acc = 'background';
|
||||
ident = '11';
|
||||
break;
|
||||
case ColorIndex.CURSOR: // OSC 12 | 112
|
||||
acc = 'cursor';
|
||||
ident = '12';
|
||||
break;
|
||||
default: // OSC 4 | 104
|
||||
// we can skip the [0..255] range check here (already done in inputhandler)
|
||||
acc = 'ansi';
|
||||
ident = '4;' + req.index;
|
||||
}
|
||||
if (acc) {
|
||||
switch (req.type) {
|
||||
case ColorRequestType.REPORT:
|
||||
const channels = color.toColorRGB(acc === 'ansi'
|
||||
? this._colorManager.colors.ansi[req.index]
|
||||
: this._colorManager.colors[acc]);
|
||||
this.coreService.triggerDataEvent(`${C0.ESC}]${ident};${toRgbString(channels)}${C0.BEL}`);
|
||||
break;
|
||||
case ColorRequestType.SET:
|
||||
if (acc === 'ansi') this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
else this._colorManager.colors[acc] = rgba.toColor(...req.color);
|
||||
break;
|
||||
case ColorRequestType.RESTORE:
|
||||
this._colorManager.restoreColor(req.index);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._renderService?.setColors(this._colorManager!.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager!.colors);
|
||||
this._renderService?.setColors(this._colorManager.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager.colors);
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
+134
-49
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { InputHandler } from 'common/InputHandler';
|
||||
import { IBufferLine, IAttributeData, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { IBufferLine, IAttributeData, IColorEvent, ColorIndex, ColorRequestType } from 'common/Types';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
|
||||
@@ -17,7 +17,7 @@ import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
||||
import { clone } from 'common/Clone';
|
||||
import { BufferService } from 'common/services/BufferService';
|
||||
import { CoreService } from 'common/services/CoreService';
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
|
||||
|
||||
function getCursor(bufferService: IBufferService): number[] {
|
||||
return [
|
||||
@@ -41,7 +41,6 @@ class TestInputHandler extends InputHandler {
|
||||
public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
|
||||
public get windowTitleStack(): string[] { return this._windowTitleStack; }
|
||||
public get iconNameStack(): string[] { return this._iconNameStack; }
|
||||
public parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null { return this._parseAnsiColorChange(data); }
|
||||
|
||||
/**
|
||||
* Promise based parse call to await the full resolve of given input data.
|
||||
@@ -1915,58 +1914,144 @@ describe('InputHandler', () => {
|
||||
});
|
||||
});
|
||||
describe('OSC', () => {
|
||||
it('4: should parse correct Ansi color change data', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3');
|
||||
|
||||
assert.isNotNull(event);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
it('4: query color events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single color query
|
||||
await inputHandler.parseP('\x1b]4;0;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]4;123;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 123 }]]);
|
||||
stack.length = 0;
|
||||
// multiple queries
|
||||
await inputHandler.parseP('\x1b]4;0;?;123;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: 0 }, { type: ColorRequestType.REPORT, index: 123 }]]);
|
||||
stack.length = 0;
|
||||
});
|
||||
it('4: set color events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single color query
|
||||
await inputHandler.parseP('\x1b]4;0;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]4;123;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 123, color: [170, 187, 204] }]]);
|
||||
stack.length = 0;
|
||||
// multiple queries
|
||||
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;123;#001122\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
|
||||
stack.length = 0;
|
||||
});
|
||||
it('4: should ignore invalid values', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
await inputHandler.parseP('\x1b]4;0;rgb:aa/bb/cc;45;rgb:1/22/333;123;#001122\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: 0, color: [170, 187, 204] }, { type: ColorRequestType.SET, index: 123, color: [0, 17, 34] }]]);
|
||||
stack.length = 0;
|
||||
});
|
||||
it('104: restore events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
await inputHandler.parseP('\x1b]104;0\x07\x1b]104;43\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }], [{ type: ColorRequestType.RESTORE, index: 43 }]]);
|
||||
stack.length = 0;
|
||||
// multiple in one command
|
||||
await inputHandler.parseP('\x1b]104;0;43\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: 0 }, { type: ColorRequestType.RESTORE, index: 43 }]]);
|
||||
stack.length = 0;
|
||||
// full ANSI table restore
|
||||
await inputHandler.parseP('\x1b]104\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE}]]);
|
||||
});
|
||||
|
||||
it('4: should ignore incorrect Ansi color change data', () => {
|
||||
// this is testing a private method
|
||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c'));
|
||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc'));
|
||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc'));
|
||||
assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc'));
|
||||
it('10: FG set & query events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single foreground query --> color undefined
|
||||
await inputHandler.parseP('\x1b]10;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }]]);
|
||||
stack.length = 0;
|
||||
// OSC with multiple values maps to OSC 10 & OSC 11 & OSC 12
|
||||
await inputHandler.parseP('\x1b]10;?;?;?;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.FOREGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
|
||||
stack.length = 0;
|
||||
// set foreground color events
|
||||
await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]10;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]);
|
||||
stack.length = 0;
|
||||
// set FG, BG and cursor color at once
|
||||
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122;rgb:12/34/56\x07');
|
||||
assert.deepEqual(stack, [
|
||||
[{ type: ColorRequestType.SET, index: ColorIndex.FOREGROUND, color: [170, 187, 204] }],
|
||||
[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
|
||||
[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }]
|
||||
]);
|
||||
});
|
||||
|
||||
it('4: should parse a list of Ansi color changes', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d');
|
||||
|
||||
assert.isNotNull(event);
|
||||
assert.equal(event!.colors.length, 3);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 });
|
||||
assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
|
||||
it('110: restore FG color', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
await inputHandler.parseP('\x1b]110\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]]);
|
||||
});
|
||||
it('4: should ignore incorrect colors in a list of Ansi color changes', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d');
|
||||
|
||||
assert.equal(event!.colors.length, 2);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
|
||||
it('11: BG set & query events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single background query --> color undefined
|
||||
await inputHandler.parseP('\x1b]11;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }]]);
|
||||
stack.length = 0;
|
||||
// OSC 11 with multiple values creates only BG and cursor event
|
||||
await inputHandler.parseP('\x1b]11;?;?;?;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }], [{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
|
||||
stack.length = 0;
|
||||
// set background color events
|
||||
await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]11;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]);
|
||||
stack.length = 0;
|
||||
// set BG and cursor color at once
|
||||
await inputHandler.parseP('\x1b]11;#001122;rgb:12/34/56\x07');
|
||||
assert.deepEqual(stack, [
|
||||
[{ type: ColorRequestType.SET, index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
|
||||
[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [18, 52, 86] }]
|
||||
]);
|
||||
});
|
||||
it('4: should be case insensitive when parsing Ansi color changes', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3');
|
||||
|
||||
assert.equal(event!.colors.length, 1);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
it('111: restore BG color', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
await inputHandler.parseP('\x1b]111\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]]);
|
||||
});
|
||||
it('4: should fire event on Ansi color change', async () => {
|
||||
return new Promise(async r => {
|
||||
inputHandler.onAnsiColorChange(e => {
|
||||
assert.isNotNull(e);
|
||||
assert.isNotNull(e!.colors);
|
||||
assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c });
|
||||
assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 });
|
||||
r();
|
||||
});
|
||||
await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
|
||||
});
|
||||
it('12: cursor color set & query events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single cursor query --> color undefined
|
||||
await inputHandler.parseP('\x1b]12;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
|
||||
stack.length = 0;
|
||||
// OSC 12 with multiple values creates only cursor event
|
||||
await inputHandler.parseP('\x1b]12;?;?;?;?\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
|
||||
stack.length = 0;
|
||||
// set cursor color events
|
||||
await inputHandler.parseP('\x1b]12;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]12;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.SET, index: ColorIndex.CURSOR, color: [170, 187, 204] }]]);
|
||||
});
|
||||
it('112: restore cursor color', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
await inputHandler.parseP('\x1b]112\x07');
|
||||
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
+165
-36
@@ -4,7 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent, IParseStack } from 'common/Types';
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex, ColorRequestType } from 'common/Types';
|
||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
|
||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||
@@ -21,6 +21,7 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe
|
||||
import { OscHandler } from 'common/parser/OscParser';
|
||||
import { DcsHandler } from 'common/parser/DcsParser';
|
||||
import { IBuffer } from 'common/buffer/Types';
|
||||
import { parseColor } from 'common/input/XParseColor';
|
||||
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
@@ -262,8 +263,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
public get onScroll(): IEvent<number> { return this._onScroll.event; }
|
||||
private _onTitleChange = new EventEmitter<string>();
|
||||
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
|
||||
private _onAnsiColorChange = new EventEmitter<IAnsiColorChangeEvent>();
|
||||
public get onAnsiColorChange(): IEvent<IAnsiColorChangeEvent> { return this._onAnsiColorChange.event; }
|
||||
private _onColor = new EventEmitter<IColorEvent>();
|
||||
public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
|
||||
|
||||
private _parseStack: IParseStack = {
|
||||
paused: false,
|
||||
@@ -398,13 +399,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
|
||||
// 3 - set property X in the form "prop=value"
|
||||
// 4 - Change Color Number
|
||||
this._parser.registerOscHandler(4, new OscHandler(data => this.setAnsiColor(data)));
|
||||
this._parser.registerOscHandler(4, new OscHandler(data => this.setOrReportIndexedColor(data)));
|
||||
// 5 - Change Special Color Number
|
||||
// 6 - Enable/disable Special Color Number c
|
||||
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
|
||||
// 10 - Change VT100 text foreground color to Pt.
|
||||
this._parser.registerOscHandler(10, new OscHandler(data => this.setOrReportFgColor(data)));
|
||||
// 11 - Change VT100 text background color to Pt.
|
||||
this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
|
||||
// 12 - Change text cursor color to Pt.
|
||||
this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
|
||||
// 13 - Change mouse foreground color to Pt.
|
||||
// 14 - Change mouse background color to Pt.
|
||||
// 15 - Change Tektronix foreground color to Pt.
|
||||
@@ -417,11 +421,15 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// 51 - reserved for Emacs shell.
|
||||
// 52 - Manipulate Selection Data.
|
||||
// 104 ; c - Reset Color Number c.
|
||||
this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));
|
||||
// 105 ; c - Reset Special Color Number c.
|
||||
// 106 ; c; f - Enable/disable Special Color Number c.
|
||||
// 110 - Reset VT100 text foreground color.
|
||||
this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));
|
||||
// 111 - Reset VT100 text background color.
|
||||
this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));
|
||||
// 112 - Reset text cursor color.
|
||||
this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));
|
||||
// 113 - Reset mouse foreground color.
|
||||
// 114 - Reset mouse background color.
|
||||
// 115 - Reset Tektronix foreground color.
|
||||
@@ -1977,7 +1985,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
this.saveCursor();
|
||||
// FALL-THROUGH
|
||||
// FALL-THROUGH
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
|
||||
@@ -2197,7 +2205,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this.restoreCursor();
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
// FALL-THROUGH
|
||||
// FALL-THROUGH
|
||||
case 47: // normal screen buffer
|
||||
case 1047: // normal screen buffer - clearing it first
|
||||
// Ensure the selection manager has the correct buffer
|
||||
@@ -2840,46 +2848,167 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null {
|
||||
const result: IAnsiColorChangeEvent = { colors: [] };
|
||||
// example data: 5;rgb:aa/bb/cc
|
||||
const regex = /(\d+);rgb:([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})/gi;
|
||||
let match;
|
||||
|
||||
while ((match = regex.exec(data)) !== null) {
|
||||
result.colors.push({
|
||||
colorIndex: parseInt(match[1]),
|
||||
red: parseInt(match[2], 16),
|
||||
green: parseInt(match[3], 16),
|
||||
blue: parseInt(match[4], 16)
|
||||
});
|
||||
}
|
||||
|
||||
if (result.colors.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
|
||||
*
|
||||
* @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
|
||||
* `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits.
|
||||
* There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0.
|
||||
* `c` is the color index between 0 and 255. The color format of `spec` is derived from `XParseColor` (see OSC 10 for supported formats).
|
||||
* There may be multipe `c ; spec` pairs present in the same instruction.
|
||||
* If `spec` contains `?` the terminal returns a sequence with the currently set color.
|
||||
*/
|
||||
public setAnsiColor(data: string): boolean {
|
||||
const event = this._parseAnsiColorChange(data);
|
||||
if (event) {
|
||||
this._onAnsiColorChange.fire(event);
|
||||
public setOrReportIndexedColor(data: string): boolean {
|
||||
const event: IColorEvent = [];
|
||||
const slots = data.split(';');
|
||||
while (slots.length > 1) {
|
||||
const idx = slots.shift() as string;
|
||||
const spec = slots.shift() as string;
|
||||
if (/^\d+$/.exec(idx)) {
|
||||
const index = parseInt(idx);
|
||||
if (0 <= index && index < 256) {
|
||||
if (spec === '?') {
|
||||
event.push({ type: ColorRequestType.REPORT, index });
|
||||
} else {
|
||||
const color = parseColor(spec);
|
||||
if (color) {
|
||||
event.push({ type: ColorRequestType.SET, index, color });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
this._logService.warn(`Expected format <num>;rgb:<rr>/<gg>/<bb> but got data: ${data}`);
|
||||
if (event.length) {
|
||||
this._onColor.fire(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// special colors - OSC 10 | 11 | 12
|
||||
private _specialColors = [ColorIndex.FOREGROUND, ColorIndex.BACKGROUND, ColorIndex.CURSOR];
|
||||
|
||||
/**
|
||||
* Apply colors requests for special colors in OSC 10 | 11 | 12.
|
||||
* Since these commands are stacking from multiple parameters,
|
||||
* we handle them in a loop with an entry offset to `_specialColors`.
|
||||
*/
|
||||
private _setOrReportSpecialColor(data: string, offset: number): boolean {
|
||||
const slots = data.split(';');
|
||||
for (let i = 0; i < slots.length; ++i, ++offset) {
|
||||
if (offset >= this._specialColors.length) break;
|
||||
if (slots[i] === '?') {
|
||||
this._onColor.fire([{ type: ColorRequestType.REPORT, index: this._specialColors[offset] }]);
|
||||
} else {
|
||||
const color = parseColor(slots[i]);
|
||||
if (color) {
|
||||
this._onColor.fire([{ type: ColorRequestType.SET, index: this._specialColors[offset], color }]);
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 10 ; <xcolor name>|<?> ST - set or query default foreground color
|
||||
*
|
||||
* @vt: #Y OSC 10 "Set or query default foreground color" "OSC 10 ; Pt BEL" "Set or query default foreground color."
|
||||
* To set the color, the following color specification formats are supported:
|
||||
* - `rgb:<red>/<green>/<blue>` for `<red>, <green>, <blue>` in `h | hh | hhh | hhhh`, where
|
||||
* `h` is a single hexadecimal digit (case insignificant). The different widths scale
|
||||
* from 4 bit (`h`) to 16 bit (`hhhh`) and get converted to 8 bit (`hh`).
|
||||
* - `#RGB` - 4 bits per channel, expanded to `#R0G0B0`
|
||||
* - `#RRGGBB` - 8 bits per channel
|
||||
* - `#RRRGGGBBB` - 12 bits per channel, truncated to `#RRGGBB`
|
||||
* - `#RRRRGGGGBBBB` - 16 bits per channel, truncated to `#RRGGBB`
|
||||
*
|
||||
* **Note:** X11 named colors are currently unsupported.
|
||||
*
|
||||
* If `Pt` contains `?` instead of a color specification, the terminal
|
||||
* returns a sequence with the current default foreground color
|
||||
* (use that sequence to restore the color after changes).
|
||||
*
|
||||
* **Note:** Other than xterm, xterm.js does not support OSC 12 - 19.
|
||||
* Therefore stacking multiple `Pt` separated by `;` only works for the first two entries.
|
||||
*/
|
||||
public setOrReportFgColor(data: string): boolean {
|
||||
return this._setOrReportSpecialColor(data, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 11 ; <xcolor name>|<?> ST - set or query default background color
|
||||
*
|
||||
* @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background."
|
||||
*/
|
||||
public setOrReportBgColor(data: string): boolean {
|
||||
return this._setOrReportSpecialColor(data, 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 12 ; <xcolor name>|<?> ST - set or query default cursor color
|
||||
*
|
||||
* @vt: #Y OSC 12 "Set or query default cursor color" "OSC 12 ; Pt BEL" "Same as OSC 10, but for default cursor color."
|
||||
*/
|
||||
public setOrReportCursorColor(data: string): boolean {
|
||||
return this._setOrReportSpecialColor(data, 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 104 ; <num> ST - restore ANSI color <num>
|
||||
*
|
||||
* @vt: #Y OSC 104 "Reset ANSI color" "OSC 104 ; c BEL" "Reset color number `c` to themed color."
|
||||
* `c` is the color index between 0 and 255. This function restores the default color for `c` as
|
||||
* specified by the loaded theme. Any number of `c` parameters may be given.
|
||||
* If no parameters are given, the entire indexed color table will be reset.
|
||||
*/
|
||||
public restoreIndexedColor(data: string): boolean {
|
||||
if (!data) {
|
||||
this._onColor.fire([{ type: ColorRequestType.RESTORE }]);
|
||||
return true;
|
||||
}
|
||||
const event: IColorEvent = [];
|
||||
const slots = data.split(';');
|
||||
for (let i = 0; i < slots.length; ++i) {
|
||||
if (/^\d+$/.exec(slots[i])) {
|
||||
const index = parseInt(slots[i]);
|
||||
if (0 <= index && index < 256) {
|
||||
event.push({ type: ColorRequestType.RESTORE, index });
|
||||
}
|
||||
}
|
||||
}
|
||||
if (event.length) {
|
||||
this._onColor.fire(event);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 110 ST - restore default foreground color
|
||||
*
|
||||
* @vt: #Y OSC 110 "Restore default foreground color" "OSC 110 BEL" "Restore default foreground to themed color."
|
||||
*/
|
||||
public restoreFgColor(data: string): boolean {
|
||||
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 111 ST - restore default background color
|
||||
*
|
||||
* @vt: #Y OSC 111 "Restore default background color" "OSC 111 BEL" "Restore default background to themed color."
|
||||
*/
|
||||
public restoreBgColor(data: string): boolean {
|
||||
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 112 ST - restore default cursor color
|
||||
*
|
||||
* @vt: #Y OSC 112 "Restore default cursor color" "OSC 112 BEL" "Restore default cursor to themed color."
|
||||
*/
|
||||
public restoreCursorColor(data: string): boolean {
|
||||
this._onColor.fire([{ type: ColorRequestType.RESTORE, index: ColorIndex.CURSOR }]);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC E
|
||||
* C1.NEL
|
||||
|
||||
Vendored
+94
-72
@@ -349,19 +349,32 @@ export interface IWindowOptions {
|
||||
setWinLines?: boolean;
|
||||
}
|
||||
|
||||
export interface IAnsiColorChangeEventColor {
|
||||
colorIndex: number;
|
||||
red: number;
|
||||
green: number;
|
||||
blue: number;
|
||||
// color events from common, used for OSC 4/10/11/12 and 104/110/111/112
|
||||
export const enum ColorRequestType {
|
||||
REPORT = 0,
|
||||
SET = 1,
|
||||
RESTORE = 2
|
||||
}
|
||||
export const enum ColorIndex {
|
||||
FOREGROUND = 256,
|
||||
BACKGROUND = 257,
|
||||
CURSOR = 258
|
||||
}
|
||||
export interface IColorReportRequest {
|
||||
type: ColorRequestType.REPORT;
|
||||
index: ColorIndex;
|
||||
}
|
||||
export interface IColorSetRequest {
|
||||
type: ColorRequestType.SET;
|
||||
index: ColorIndex;
|
||||
color: IColorRGB;
|
||||
}
|
||||
export interface IColorRestoreRequest {
|
||||
type: ColorRequestType.RESTORE;
|
||||
index?: ColorIndex;
|
||||
}
|
||||
export type IColorEvent = (IColorReportRequest | IColorSetRequest | IColorRestoreRequest)[];
|
||||
|
||||
/**
|
||||
* Event fired for OSC 4 command - to change ANSI color based on its index.
|
||||
*/
|
||||
export interface IAnsiColorChangeEvent {
|
||||
colors: IAnsiColorChangeEventColor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the parser and handles actions generated by the parser.
|
||||
@@ -376,80 +389,89 @@ export interface IInputHandler {
|
||||
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
|
||||
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
||||
|
||||
/** C0 BEL */ bell(): void;
|
||||
/** C0 LF */ lineFeed(): void;
|
||||
/** C0 CR */ carriageReturn(): void;
|
||||
/** C0 BS */ backspace(): void;
|
||||
/** C0 HT */ tab(): void;
|
||||
/** C0 SO */ shiftOut(): void;
|
||||
/** C0 SI */ shiftIn(): void;
|
||||
/** C0 BEL */ bell(): boolean;
|
||||
/** C0 LF */ lineFeed(): boolean;
|
||||
/** C0 CR */ carriageReturn(): boolean;
|
||||
/** C0 BS */ backspace(): boolean;
|
||||
/** C0 HT */ tab(): boolean;
|
||||
/** C0 SO */ shiftOut(): boolean;
|
||||
/** C0 SI */ shiftIn(): boolean;
|
||||
|
||||
/** CSI @ */ insertChars(params: IParams): boolean;
|
||||
/** CSI SP @ */ scrollLeft(params: IParams): boolean;
|
||||
/** CSI A */ cursorUp(params: IParams): boolean;
|
||||
/** CSI SP A */ scrollRight(params: IParams): boolean;
|
||||
/** CSI B */ cursorDown(params: IParams): boolean;
|
||||
/** CSI C */ cursorForward(params: IParams): boolean;
|
||||
/** CSI D */ cursorBackward(params: IParams): boolean;
|
||||
/** CSI E */ cursorNextLine(params: IParams): boolean;
|
||||
/** CSI F */ cursorPrecedingLine(params: IParams): boolean;
|
||||
/** CSI G */ cursorCharAbsolute(params: IParams): boolean;
|
||||
/** CSI H */ cursorPosition(params: IParams): boolean;
|
||||
/** CSI I */ cursorForwardTab(params: IParams): boolean;
|
||||
/** CSI J */ eraseInDisplay(params: IParams): boolean;
|
||||
/** CSI K */ eraseInLine(params: IParams): boolean;
|
||||
/** CSI L */ insertLines(params: IParams): boolean;
|
||||
/** CSI M */ deleteLines(params: IParams): boolean;
|
||||
/** CSI P */ deleteChars(params: IParams): boolean;
|
||||
/** CSI S */ scrollUp(params: IParams): boolean;
|
||||
/** CSI T */ scrollDown(params: IParams, collect?: string): boolean;
|
||||
/** CSI X */ eraseChars(params: IParams): boolean;
|
||||
/** CSI Z */ cursorBackwardTab(params: IParams): boolean;
|
||||
/** CSI ` */ charPosAbsolute(params: IParams): boolean;
|
||||
/** CSI a */ hPositionRelative(params: IParams): boolean;
|
||||
/** CSI b */ repeatPrecedingCharacter(params: IParams): boolean;
|
||||
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): boolean;
|
||||
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): boolean;
|
||||
/** CSI d */ linePosAbsolute(params: IParams): boolean;
|
||||
/** CSI e */ vPositionRelative(params: IParams): boolean;
|
||||
/** CSI f */ hVPosition(params: IParams): boolean;
|
||||
/** CSI g */ tabClear(params: IParams): boolean;
|
||||
/** CSI h */ setMode(params: IParams, collect?: string): boolean;
|
||||
/** CSI l */ resetMode(params: IParams, collect?: string): boolean;
|
||||
/** CSI m */ charAttributes(params: IParams): boolean;
|
||||
/** CSI n */ deviceStatus(params: IParams, collect?: string): boolean;
|
||||
/** CSI p */ softReset(params: IParams, collect?: string): boolean;
|
||||
/** CSI q */ setCursorStyle(params: IParams, collect?: string): boolean;
|
||||
/** CSI r */ setScrollRegion(params: IParams, collect?: string): boolean;
|
||||
/** CSI s */ saveCursor(params: IParams): boolean;
|
||||
/** CSI u */ restoreCursor(params: IParams): boolean;
|
||||
/** CSI ' } */ insertColumns(params: IParams): boolean;
|
||||
/** CSI ' ~ */ deleteColumns(params: IParams): boolean;
|
||||
|
||||
/** CSI @ */ insertChars(params: IParams): void;
|
||||
/** CSI SP @ */ scrollLeft(params: IParams): void;
|
||||
/** CSI A */ cursorUp(params: IParams): void;
|
||||
/** CSI SP A */ scrollRight(params: IParams): void;
|
||||
/** CSI B */ cursorDown(params: IParams): void;
|
||||
/** CSI C */ cursorForward(params: IParams): void;
|
||||
/** CSI D */ cursorBackward(params: IParams): void;
|
||||
/** CSI E */ cursorNextLine(params: IParams): void;
|
||||
/** CSI F */ cursorPrecedingLine(params: IParams): void;
|
||||
/** CSI G */ cursorCharAbsolute(params: IParams): void;
|
||||
/** CSI H */ cursorPosition(params: IParams): void;
|
||||
/** CSI I */ cursorForwardTab(params: IParams): void;
|
||||
/** CSI J */ eraseInDisplay(params: IParams): void;
|
||||
/** CSI K */ eraseInLine(params: IParams): void;
|
||||
/** CSI L */ insertLines(params: IParams): void;
|
||||
/** CSI M */ deleteLines(params: IParams): void;
|
||||
/** CSI P */ deleteChars(params: IParams): void;
|
||||
/** CSI S */ scrollUp(params: IParams): void;
|
||||
/** CSI T */ scrollDown(params: IParams, collect?: string): void;
|
||||
/** CSI X */ eraseChars(params: IParams): void;
|
||||
/** CSI Z */ cursorBackwardTab(params: IParams): void;
|
||||
/** CSI ` */ charPosAbsolute(params: IParams): void;
|
||||
/** CSI a */ hPositionRelative(params: IParams): void;
|
||||
/** CSI b */ repeatPrecedingCharacter(params: IParams): void;
|
||||
/** CSI c */ sendDeviceAttributesPrimary(params: IParams): void;
|
||||
/** CSI > c */ sendDeviceAttributesSecondary(params: IParams): void;
|
||||
/** CSI d */ linePosAbsolute(params: IParams): void;
|
||||
/** CSI e */ vPositionRelative(params: IParams): void;
|
||||
/** CSI f */ hVPosition(params: IParams): void;
|
||||
/** CSI g */ tabClear(params: IParams): void;
|
||||
/** CSI h */ setMode(params: IParams, collect?: string): void;
|
||||
/** CSI l */ resetMode(params: IParams, collect?: string): void;
|
||||
/** CSI m */ charAttributes(params: IParams): void;
|
||||
/** CSI n */ deviceStatus(params: IParams, collect?: string): void;
|
||||
/** CSI p */ softReset(params: IParams, collect?: string): void;
|
||||
/** CSI q */ setCursorStyle(params: IParams, collect?: string): void;
|
||||
/** CSI r */ setScrollRegion(params: IParams, collect?: string): void;
|
||||
/** CSI s */ saveCursor(params: IParams): void;
|
||||
/** CSI u */ restoreCursor(params: IParams): void;
|
||||
/** CSI ' } */ insertColumns(params: IParams): void;
|
||||
/** CSI ' ~ */ deleteColumns(params: IParams): void;
|
||||
/** OSC 0
|
||||
OSC 2 */ setTitle(data: string): void;
|
||||
/** OSC 4 */ setAnsiColor(data: string): void;
|
||||
/** ESC E */ nextLine(): void;
|
||||
/** ESC = */ keypadApplicationMode(): void;
|
||||
/** ESC > */ keypadNumericMode(): void;
|
||||
OSC 2 */ setTitle(data: string): boolean;
|
||||
/** OSC 4 */ setOrReportIndexedColor(data: string): boolean;
|
||||
/** OSC 10 */ setOrReportFgColor(data: string): boolean;
|
||||
/** OSC 11 */ setOrReportBgColor(data: string): boolean;
|
||||
/** OSC 12 */ setOrReportCursorColor(data: string): boolean;
|
||||
/** OSC 104 */ restoreIndexedColor(data: string): boolean;
|
||||
/** OSC 110 */ restoreFgColor(data: string): boolean;
|
||||
/** OSC 111 */ restoreBgColor(data: string): boolean;
|
||||
/** OSC 112 */ restoreCursorColor(data: string): boolean;
|
||||
|
||||
/** ESC E */ nextLine(): boolean;
|
||||
/** ESC = */ keypadApplicationMode(): boolean;
|
||||
/** ESC > */ keypadNumericMode(): boolean;
|
||||
/** ESC % G
|
||||
ESC % @ */ selectDefaultCharset(): void;
|
||||
ESC % @ */ selectDefaultCharset(): boolean;
|
||||
/** ESC ( C
|
||||
ESC ) C
|
||||
ESC * C
|
||||
ESC + C
|
||||
ESC - C
|
||||
ESC . C
|
||||
ESC / C */ selectCharset(collectAndFlag: string): void;
|
||||
/** ESC D */ index(): void;
|
||||
/** ESC H */ tabSet(): void;
|
||||
/** ESC M */ reverseIndex(): void;
|
||||
/** ESC c */ fullReset(): void;
|
||||
ESC / C */ selectCharset(collectAndFlag: string): boolean;
|
||||
/** ESC D */ index(): boolean;
|
||||
/** ESC H */ tabSet(): boolean;
|
||||
/** ESC M */ reverseIndex(): boolean;
|
||||
/** ESC c */ fullReset(): boolean;
|
||||
/** ESC n
|
||||
ESC o
|
||||
ESC |
|
||||
ESC }
|
||||
ESC ~ */ setgLevel(level: number): void;
|
||||
/** ESC # 8 */ screenAlignmentPattern(): void;
|
||||
ESC ~ */ setgLevel(level: number): boolean;
|
||||
/** ESC # 8 */ screenAlignmentPattern(): boolean;
|
||||
}
|
||||
|
||||
interface IParseStack {
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { parseColor, toRgbString } from 'common/input/XParseColor';
|
||||
|
||||
describe('XParseColor', () => {
|
||||
describe('parseColor', () => {
|
||||
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(parseColor('rgb:0/0/0'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('rgb:f/f/f'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('rgb:1/2/3'), [17, 34, 51]);
|
||||
// 8 bit
|
||||
assert.deepEqual(parseColor('rgb:00/00/00'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('rgb:ff/ff/ff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('rgb:11/22/33'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(parseColor('rgb:000/000/000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('rgb:fff/fff/fff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('rgb:111/222/333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(parseColor('rgb:0000/0000/0000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('rgb:ffff/ffff/ffff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('rgb:1111/2222/3333'), [17, 34, 51]);
|
||||
});
|
||||
it('#RGB scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(parseColor('#000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('#fff'), [240, 240, 240]);
|
||||
assert.deepEqual(parseColor('#123'), [16, 32, 48]);
|
||||
// 8 bit
|
||||
assert.deepEqual(parseColor('#000000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('#ffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('#112233'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(parseColor('#000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('#fffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('#111222333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(parseColor('#000000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(parseColor('#ffffffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(parseColor('#111122223333'), [17, 34, 51]);
|
||||
});
|
||||
it('supports upper case', () => {
|
||||
assert.deepEqual(parseColor('RGB:0/A/F'), [0, 170, 255]);
|
||||
assert.deepEqual(parseColor('#FFF'), [240, 240, 240]);
|
||||
});
|
||||
it('does not parse illegal combinations', () => {
|
||||
// shifting bit width
|
||||
assert.equal(parseColor('rgb:0/11/222'), undefined);
|
||||
// unsupported scheme
|
||||
assert.equal(parseColor('rgbi:00/11/22'), undefined);
|
||||
// broken # specifier
|
||||
assert.equal(parseColor('#aabbbcc'), undefined);
|
||||
// out of range
|
||||
assert.equal(parseColor('#aabbgg'), undefined);
|
||||
assert.equal(parseColor('rgb:aa/bb/gg'), undefined);
|
||||
});
|
||||
});
|
||||
describe('toXColorRgb', () => {
|
||||
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.equal(toRgbString(parseColor('rgb:0/0/0')!, 4), 'rgb:0/0/0');
|
||||
assert.equal(toRgbString(parseColor('rgb:f/f/f')!, 4), 'rgb:f/f/f');
|
||||
assert.equal(toRgbString(parseColor('rgb:1/2/3')!, 4), 'rgb:1/2/3');
|
||||
// 8 bit
|
||||
assert.equal(toRgbString(parseColor('rgb:00/00/00')!, 8), 'rgb:00/00/00');
|
||||
assert.equal(toRgbString(parseColor('rgb:ff/ff/ff')!, 8), 'rgb:ff/ff/ff');
|
||||
assert.equal(toRgbString(parseColor('rgb:11/22/33')!, 8), 'rgb:11/22/33');
|
||||
// 12 bit
|
||||
assert.equal(toRgbString(parseColor('rgb:000/000/000')!, 12), 'rgb:000/000/000');
|
||||
assert.equal(toRgbString(parseColor('rgb:fff/fff/fff')!, 12), 'rgb:fff/fff/fff');
|
||||
assert.equal(toRgbString(parseColor('rgb:111/222/333')!, 12), 'rgb:111/222/333');
|
||||
// 16 bit
|
||||
assert.equal(toRgbString(parseColor('rgb:0000/0000/0000')!, 16), 'rgb:0000/0000/0000');
|
||||
assert.equal(toRgbString(parseColor('rgb:ffff/ffff/ffff')!, 16), 'rgb:ffff/ffff/ffff');
|
||||
assert.equal(toRgbString(parseColor('rgb:1111/2222/3333')!, 16), 'rgb:1111/2222/3333');
|
||||
});
|
||||
it('defaults to 16 bit output', () => {
|
||||
assert.equal(toRgbString(parseColor('rgb:1/2/3')!), 'rgb:1111/2222/3333');
|
||||
assert.equal(toRgbString(parseColor('rgb:11/22/33')!), 'rgb:1111/2222/3333');
|
||||
assert.equal(toRgbString(parseColor('rgb:111/222/333')!), 'rgb:1111/2222/3333');
|
||||
assert.equal(toRgbString(parseColor('rgb:123/123/123')!), 'rgb:1212/1212/1212');
|
||||
});
|
||||
it('reduces colors to 8 bit resolution', () => {
|
||||
assert.equal(toRgbString(parseColor('rgb:123/123/123')!, 12), 'rgb:121/121/121');
|
||||
assert.equal(toRgbString(parseColor('rgb:1234/1234/1234')!, 16), 'rgb:1212/1212/1212');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (c) 2021 The xterm.js authors. All rights reserved.
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
|
||||
// 'rgb:' rule - matching: r/g/b | rr/gg/bb | rrr/ggg/bbb | rrrr/gggg/bbbb (hex digits)
|
||||
const RGB_REX = /^([\da-f]{1})\/([\da-f]{1})\/([\da-f]{1})$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/;
|
||||
// '#...' rule - matching any hex digits
|
||||
const HASH_REX = /^[\da-f]+$/;
|
||||
|
||||
/**
|
||||
* Parse color spec to RGB values (8 bit per channel).
|
||||
* See `man xparsecolor` for details about certain format specifications.
|
||||
*
|
||||
* Supported formats:
|
||||
* - rgb:<red>/<green>/<blue> with <red>, <green>, <blue> in h | hh | hhh | hhhh
|
||||
* - #RGB, #RRGGBB, #RRRGGGBBB, #RRRRGGGGBBBB
|
||||
*
|
||||
* All other formats like rgbi: or device-independent string specifications
|
||||
* with float numbering are not supported.
|
||||
*/
|
||||
export function parseColor(data: string): [number, number, number] | undefined {
|
||||
if (!data) return;
|
||||
// also handle uppercases
|
||||
let low = data.toLowerCase();
|
||||
if (low.indexOf('rgb:') === 0) {
|
||||
// 'rgb:' specifier
|
||||
low = low.slice(4);
|
||||
const m = RGB_REX.exec(low);
|
||||
if (m) {
|
||||
const base = m[1] ? 15 : m[4] ? 255 : m[7] ? 4095 : 65535;
|
||||
return [
|
||||
Math.round(parseInt(m[1] || m[4] || m[7] || m[10], 16) / base * 255),
|
||||
Math.round(parseInt(m[2] || m[5] || m[8] || m[11], 16) / base * 255),
|
||||
Math.round(parseInt(m[3] || m[6] || m[9] || m[12], 16) / base * 255)
|
||||
];
|
||||
}
|
||||
} else if (low.indexOf('#') === 0) {
|
||||
// '#' specifier
|
||||
low = low.slice(1);
|
||||
if (HASH_REX.exec(low) && [3, 6, 9, 12].includes(low.length)) {
|
||||
const adv = low.length / 3;
|
||||
const result: [number, number, number] = [0, 0, 0];
|
||||
for (let i = 0; i < 3; ++i) {
|
||||
const c = parseInt(low.slice(adv * i, adv * i + adv), 16);
|
||||
result[i] = adv === 1 ? c << 4 : adv === 2 ? c : adv === 3 ? c >> 4 : c >> 8;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
// Named colors are currently not supported due to the large addition to the xterm.js bundle size
|
||||
// they would add. In order to support named colors, we would need some way of optionally loading
|
||||
// additional payloads so startup/download time is not bloated (see #3530).
|
||||
}
|
||||
|
||||
// pad hex output to requested bit width
|
||||
function pad(n: number, bits: number): string {
|
||||
const s = n.toString(16);
|
||||
const s2 = s.length < 2 ? '0' + s : s;
|
||||
switch (bits) {
|
||||
case 4:
|
||||
return s[0];
|
||||
case 8:
|
||||
return s2;
|
||||
case 12:
|
||||
return (s2 + s2).slice(0, 3);
|
||||
default:
|
||||
return s2 + s2;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a given color to rgb:../../.. string of `bits` depth.
|
||||
*/
|
||||
export function toRgbString(color: [number, number, number], bits: number = 16): string {
|
||||
const [r, g, b] = color;
|
||||
return `rgb:${pad(r, bits)}/${pad(g, bits)}/${pad(b, bits)}`;
|
||||
}
|
||||
@@ -4,7 +4,7 @@
|
||||
*/
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { pollFor, openTerminal, getBrowserType, launchBrowser } from './TestUtils';
|
||||
import { pollFor, openTerminal, getBrowserType, launchBrowser, writeSync } from './TestUtils';
|
||||
import { Browser, Page } from 'playwright';
|
||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
||||
|
||||
@@ -386,6 +386,171 @@ describe('InputHandler Integration Tests', function(): void {
|
||||
});
|
||||
});
|
||||
|
||||
describe('OSC', () => {
|
||||
describe('OSC 4', () => {
|
||||
before(async () => {
|
||||
await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()');
|
||||
});
|
||||
after(async () => {
|
||||
await page.evaluate('window._h.dispose()');
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
});
|
||||
it('query single color', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07']);
|
||||
await writeSync(page, '\x1b]4;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
|
||||
});
|
||||
it('query multiple colors', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:2e2e/3434/3636\x07', '\x1b]4;77;rgb:5f5f/d7d7/5f5f\x07']);
|
||||
});
|
||||
it('set & query single color', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?\x07');
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
|
||||
// set new color & query
|
||||
await writeSync(page, '\x1b]4;0;rgb:01/02/03\x07\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07']);
|
||||
// restore should set old color
|
||||
await writeSync(page, restore[0] + '\x1b]4;0;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], '\x1b]4;0;rgb:0101/0202/0303\x07', restore[0]]);
|
||||
});
|
||||
it('query & set colors mixed', async () => {
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// mixed call - change 0, query 43, change 77
|
||||
await writeSync(page, '\x1b]4;0;rgb:01/02/03;43;?;77;#aabbcc\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;43;rgb:0000/d7d7/afaf\x07']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// query new values for 0 + 77
|
||||
await writeSync(page, '\x1b]4;0;?;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]4;0;rgb:0101/0202/0303\x07', '\x1b]4;77;rgb:aaaa/bbbb/cccc\x07']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore old values for 0 + 77
|
||||
await writeSync(page, restore[0] + restore[1] + '\x1b]4;0;?;77;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
|
||||
});
|
||||
});
|
||||
describe('OSC 4 & 104', () => {
|
||||
before(async () => {
|
||||
await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()');
|
||||
});
|
||||
after(async () => {
|
||||
await page.evaluate('window._h.dispose()');
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
});
|
||||
it('change & restore single color', async () => {
|
||||
// test for some random color slots
|
||||
for (const i of [0, 43, 77, 255]) {
|
||||
await writeSync(page, `\x1b]4;${i};?\x07`);
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
await writeSync(page, `\x1b]4;${i};rgb:01/02/03\x07\x1b]4;${i};?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`]);
|
||||
// restore slot color
|
||||
await writeSync(page, `\x1b]104;${i}\x07\x1b]4;${i};?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), [restore[0], `\x1b]4;${i};rgb:0101/0202/0303\x07`, restore[0]]);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
}
|
||||
});
|
||||
it('restore multiple at once', async () => {
|
||||
// change 3 random slots
|
||||
await writeSync(page, `\x1b]4;0;?;43;?;77;?\x07`);
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
await writeSync(page, `\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\x07`);
|
||||
// restore specific slots
|
||||
await writeSync(page, `\x1b]104;0;43;77\x07` + `\x1b]4;0;?;43;?;77;?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
|
||||
});
|
||||
it('restore full table', async () => {
|
||||
// change 3 random slots
|
||||
await writeSync(page, `\x1b]4;0;?;43;?;77;?\x07`);
|
||||
const restore: string[] = await page.evaluate('window._recordedData');
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
await writeSync(page, `\x1b]4;0;rgb:01/02/03;43;#aabbcc;77;#123456\x07`);
|
||||
// restore all
|
||||
await writeSync(page, `\x1b]104\x07` + `\x1b]4;0;?;43;?;77;?\x07`);
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), restore);
|
||||
});
|
||||
});
|
||||
describe('OSC 10 & 11 + 110 | 111 | 112', () => {
|
||||
before(async () => {
|
||||
await page.evaluate('(() => {window._recordedData = []; window._h = term.onData(d => window._recordedData.push(d));})()');
|
||||
});
|
||||
after(async () => {
|
||||
await page.evaluate('window._h.dispose()');
|
||||
});
|
||||
beforeEach(async () => {
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
});
|
||||
it('query FG color', async () => {
|
||||
await writeSync(page, '\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
});
|
||||
it('query BG color', async () => {
|
||||
await writeSync(page, '\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
});
|
||||
it('query FG & BG color in one call', async () => {
|
||||
await writeSync(page, '\x1b]10;?;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
});
|
||||
it('set & query FG', async () => {
|
||||
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
|
||||
await writeSync(page, '\x1b]10;#ffffff\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07', '\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
});
|
||||
it('set & query BG', async () => {
|
||||
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
|
||||
await writeSync(page, '\x1b]11;#000000\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07', '\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
});
|
||||
it('set & query cursor color', async () => {
|
||||
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
|
||||
await writeSync(page, '\x1b]12;#ffffff\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07', '\x1b]12;rgb:ffff/ffff/ffff\x07']);
|
||||
});
|
||||
it('set & query FG & BG color in one call', async () => {
|
||||
await writeSync(page, '\x1b]10;#123456;rgb:aa/bb/cc\x07\x1b]10;?;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1212/3434/5656\x07', '\x1b]11;rgb:aaaa/bbbb/cccc\x07']);
|
||||
await writeSync(page, '\x1b]10;#ffffff;#000000\x07');
|
||||
});
|
||||
it('OSC 110: restore FG color', async () => {
|
||||
await writeSync(page, '\x1b]10;rgb:1/2/3\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:1111/2222/3333\x07']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]110\x07\x1b]10;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]10;rgb:ffff/ffff/ffff\x07']);
|
||||
});
|
||||
it('OSC 111: restore BG color', async () => {
|
||||
await writeSync(page, '\x1b]11;rgb:1/2/3\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:1111/2222/3333\x07']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]111\x07\x1b]11;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]11;rgb:0000/0000/0000\x07']);
|
||||
});
|
||||
it('OSC 112: restore cursor color', async () => {
|
||||
await writeSync(page, '\x1b]12;rgb:1/2/3\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:1111/2222/3333\x07']);
|
||||
await page.evaluate('window._recordedData.length = 0;');
|
||||
// restore
|
||||
await writeSync(page, '\x1b]112\x07\x1b]12;?\x07');
|
||||
assert.deepEqual(await page.evaluate('window._recordedData'), ['\x1b]12;rgb:ffff/ffff/ffff\x07']);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ESC', () => {
|
||||
describe('DECRC: Save cursor, ESC 7', () => {
|
||||
it('should save the absolute cursor position so resizing restores to the correct position', async () => {
|
||||
|
||||
Reference in New Issue
Block a user