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 { IColor } from 'browser/Types';
|
||||||
|
import { IColorRGB } from 'common/Types';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper functions where the source type is "channels" (individual color channels as numbers).
|
* 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 {
|
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
|
// >>> 0 forces an unsigned int
|
||||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||||
}
|
}
|
||||||
@@ -81,6 +84,10 @@ export namespace color {
|
|||||||
rgba: channels.toRgba(r, g, b, a)
|
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;
|
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Move this to channels NS?
|
||||||
export function toChannels(value: number): [number, number, number, number] {
|
export function toChannels(value: number): [number, number, number, number] {
|
||||||
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
|
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 { ITheme } from 'common/services/Services';
|
||||||
import { channels, color, css } from 'browser/Color';
|
import { channels, color, css } from 'browser/Color';
|
||||||
import { ColorContrastCache } from 'browser/ColorContrastCache';
|
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_FOREGROUND = css.toColor('#ffffff');
|
||||||
const DEFAULT_BACKGROUND = css.toColor('#000000');
|
const DEFAULT_BACKGROUND = css.toColor('#000000');
|
||||||
@@ -73,6 +83,7 @@ export class ColorManager implements IColorManager {
|
|||||||
private _ctx: CanvasRenderingContext2D;
|
private _ctx: CanvasRenderingContext2D;
|
||||||
private _litmusColor: CanvasGradient;
|
private _litmusColor: CanvasGradient;
|
||||||
private _contrastCache: IColorContrastCache;
|
private _contrastCache: IColorContrastCache;
|
||||||
|
private _restoreColors!: IRestoreColorSet;
|
||||||
|
|
||||||
constructor(document: Document, public allowTransparency: boolean) {
|
constructor(document: Document, public allowTransparency: boolean) {
|
||||||
const canvas = document.createElement('canvas');
|
const canvas = document.createElement('canvas');
|
||||||
@@ -96,6 +107,7 @@ export class ColorManager implements IColorManager {
|
|||||||
ansi: DEFAULT_ANSI_COLORS.slice(),
|
ansi: DEFAULT_ANSI_COLORS.slice(),
|
||||||
contrastCache: this._contrastCache
|
contrastCache: this._contrastCache
|
||||||
};
|
};
|
||||||
|
this._updateRestoreColors();
|
||||||
}
|
}
|
||||||
|
|
||||||
public onOptionsChange(key: string): void {
|
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]);
|
this.colors.ansi[15] = this._parseColor(theme.brightWhite, DEFAULT_ANSI_COLORS[15]);
|
||||||
// Clear our the cache
|
// Clear our the cache
|
||||||
this._contrastCache.clear();
|
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(
|
private _parseColor(
|
||||||
|
|||||||
+52
-14
@@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
|
|||||||
import { AccessibilityManager } from './AccessibilityManager';
|
import { AccessibilityManager } from './AccessibilityManager';
|
||||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
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 { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||||
@@ -52,9 +52,9 @@ import { MouseService } from 'browser/services/MouseService';
|
|||||||
import { Linkifier2 } from 'browser/Linkifier2';
|
import { Linkifier2 } from 'browser/Linkifier2';
|
||||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||||
import { CoreTerminal } from 'common/CoreTerminal';
|
import { CoreTerminal } from 'common/CoreTerminal';
|
||||||
import { rgba } from 'browser/Color';
|
import { color, rgba } from 'browser/Color';
|
||||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
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.
|
// Let it work inside Node.js for automated testing purposes.
|
||||||
const document: Document = (typeof window !== 'undefined') ? window.document : null as any;
|
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.onRequestSendFocus(() => this._reportFocus()));
|
||||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
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.onCursorMove, this._onCursorMove));
|
||||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
||||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
|
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)));
|
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
|
/**
|
||||||
if (!this._colorManager) { return; }
|
* 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
|
||||||
for (const ansiColor of event.colors) {
|
* or none restore requests (resetting all),
|
||||||
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
|
* while an event from OSC 10|110 | 11|111 | 12|112 always contains a single request.
|
||||||
|
*/
|
||||||
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
|
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._renderService?.setColors(this._colorManager!.colors);
|
this.viewport?.onThemeChange(this._colorManager.colors);
|
||||||
this.viewport?.onThemeChange(this._colorManager!.colors);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public dispose(): void {
|
public dispose(): void {
|
||||||
|
|||||||
+134
-49
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
import { assert } from 'chai';
|
import { assert } from 'chai';
|
||||||
import { InputHandler } from 'common/InputHandler';
|
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 { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||||
import { CellData } from 'common/buffer/CellData';
|
import { CellData } from 'common/buffer/CellData';
|
||||||
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
|
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
|
||||||
@@ -17,7 +17,7 @@ import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
|
|||||||
import { clone } from 'common/Clone';
|
import { clone } from 'common/Clone';
|
||||||
import { BufferService } from 'common/services/BufferService';
|
import { BufferService } from 'common/services/BufferService';
|
||||||
import { CoreService } from 'common/services/CoreService';
|
import { CoreService } from 'common/services/CoreService';
|
||||||
import { OscHandler } from 'common/parser/OscParser';
|
|
||||||
|
|
||||||
function getCursor(bufferService: IBufferService): number[] {
|
function getCursor(bufferService: IBufferService): number[] {
|
||||||
return [
|
return [
|
||||||
@@ -41,7 +41,6 @@ class TestInputHandler extends InputHandler {
|
|||||||
public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
|
public get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
|
||||||
public get windowTitleStack(): string[] { return this._windowTitleStack; }
|
public get windowTitleStack(): string[] { return this._windowTitleStack; }
|
||||||
public get iconNameStack(): string[] { return this._iconNameStack; }
|
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.
|
* Promise based parse call to await the full resolve of given input data.
|
||||||
@@ -1915,58 +1914,144 @@ describe('InputHandler', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
describe('OSC', () => {
|
describe('OSC', () => {
|
||||||
it('4: should parse correct Ansi color change data', () => {
|
it('4: query color events', async () => {
|
||||||
// this is testing a private method
|
const stack: IColorEvent[] = [];
|
||||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3');
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
|
// single color query
|
||||||
assert.isNotNull(event);
|
await inputHandler.parseP('\x1b]4;0;?\x07');
|
||||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
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', () => {
|
it('10: FG set & query events', async () => {
|
||||||
// this is testing a private method
|
const stack: IColorEvent[] = [];
|
||||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c'));
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc'));
|
// single foreground query --> color undefined
|
||||||
assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc'));
|
await inputHandler.parseP('\x1b]10;?\x07');
|
||||||
assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc'));
|
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('110: restore FG color', async () => {
|
||||||
it('4: should parse a list of Ansi color changes', () => {
|
const stack: IColorEvent[] = [];
|
||||||
// this is testing a private method
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d');
|
await inputHandler.parseP('\x1b]110\x07');
|
||||||
|
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.FOREGROUND }]]);
|
||||||
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('4: should ignore incorrect colors in a list of Ansi color changes', () => {
|
it('11: BG set & query events', async () => {
|
||||||
// this is testing a private method
|
const stack: IColorEvent[] = [];
|
||||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d');
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
|
// single background query --> color undefined
|
||||||
assert.equal(event!.colors.length, 2);
|
await inputHandler.parseP('\x1b]11;?\x07');
|
||||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.BACKGROUND }]]);
|
||||||
assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
|
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', () => {
|
it('111: restore BG color', async () => {
|
||||||
// this is testing a private method
|
const stack: IColorEvent[] = [];
|
||||||
const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3');
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
|
await inputHandler.parseP('\x1b]111\x07');
|
||||||
assert.equal(event!.colors.length, 1);
|
assert.deepEqual(stack, [[{ type: ColorRequestType.RESTORE, index: ColorIndex.BACKGROUND }]]);
|
||||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
|
||||||
});
|
});
|
||||||
it('4: should fire event on Ansi color change', async () => {
|
it('12: cursor color set & query events', async () => {
|
||||||
return new Promise(async r => {
|
const stack: IColorEvent[] = [];
|
||||||
inputHandler.onAnsiColorChange(e => {
|
inputHandler.onColor(ev => stack.push(ev));
|
||||||
assert.isNotNull(e);
|
// single cursor query --> color undefined
|
||||||
assert.isNotNull(e!.colors);
|
await inputHandler.parseP('\x1b]12;?\x07');
|
||||||
assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c });
|
assert.deepEqual(stack, [[{ type: ColorRequestType.REPORT, index: ColorIndex.CURSOR }]]);
|
||||||
assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 });
|
stack.length = 0;
|
||||||
r();
|
// OSC 12 with multiple values creates only cursor event
|
||||||
});
|
await inputHandler.parseP('\x1b]12;?;?;?;?\x07');
|
||||||
await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
|
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
|
* @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 { C0, C1 } from 'common/data/EscapeSequences';
|
||||||
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
|
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
|
||||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||||
@@ -21,6 +21,7 @@ import { ICoreService, IBufferService, IOptionsService, ILogService, IDirtyRowSe
|
|||||||
import { OscHandler } from 'common/parser/OscParser';
|
import { OscHandler } from 'common/parser/OscParser';
|
||||||
import { DcsHandler } from 'common/parser/DcsParser';
|
import { DcsHandler } from 'common/parser/DcsParser';
|
||||||
import { IBuffer } from 'common/buffer/Types';
|
import { IBuffer } from 'common/buffer/Types';
|
||||||
|
import { parseColor } from 'common/input/XParseColor';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Map collect to glevel. Used in `selectCharset`.
|
* 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; }
|
public get onScroll(): IEvent<number> { return this._onScroll.event; }
|
||||||
private _onTitleChange = new EventEmitter<string>();
|
private _onTitleChange = new EventEmitter<string>();
|
||||||
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
|
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
|
||||||
private _onAnsiColorChange = new EventEmitter<IAnsiColorChangeEvent>();
|
private _onColor = new EventEmitter<IColorEvent>();
|
||||||
public get onAnsiColorChange(): IEvent<IAnsiColorChangeEvent> { return this._onAnsiColorChange.event; }
|
public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
|
||||||
|
|
||||||
private _parseStack: IParseStack = {
|
private _parseStack: IParseStack = {
|
||||||
paused: false,
|
paused: false,
|
||||||
@@ -398,13 +399,16 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
|
this._parser.registerOscHandler(2, new OscHandler(data => this.setTitle(data)));
|
||||||
// 3 - set property X in the form "prop=value"
|
// 3 - set property X in the form "prop=value"
|
||||||
// 4 - Change Color Number
|
// 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
|
// 5 - Change Special Color Number
|
||||||
// 6 - Enable/disable Special Color Number c
|
// 6 - Enable/disable Special Color Number c
|
||||||
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
|
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
|
||||||
// 10 - Change VT100 text foreground color to Pt.
|
// 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.
|
// 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.
|
// 12 - Change text cursor color to Pt.
|
||||||
|
this._parser.registerOscHandler(12, new OscHandler(data => this.setOrReportCursorColor(data)));
|
||||||
// 13 - Change mouse foreground color to Pt.
|
// 13 - Change mouse foreground color to Pt.
|
||||||
// 14 - Change mouse background color to Pt.
|
// 14 - Change mouse background color to Pt.
|
||||||
// 15 - Change Tektronix foreground 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.
|
// 51 - reserved for Emacs shell.
|
||||||
// 52 - Manipulate Selection Data.
|
// 52 - Manipulate Selection Data.
|
||||||
// 104 ; c - Reset Color Number c.
|
// 104 ; c - Reset Color Number c.
|
||||||
|
this._parser.registerOscHandler(104, new OscHandler(data => this.restoreIndexedColor(data)));
|
||||||
// 105 ; c - Reset Special Color Number c.
|
// 105 ; c - Reset Special Color Number c.
|
||||||
// 106 ; c; f - Enable/disable Special Color Number c.
|
// 106 ; c; f - Enable/disable Special Color Number c.
|
||||||
// 110 - Reset VT100 text foreground color.
|
// 110 - Reset VT100 text foreground color.
|
||||||
|
this._parser.registerOscHandler(110, new OscHandler(data => this.restoreFgColor(data)));
|
||||||
// 111 - Reset VT100 text background color.
|
// 111 - Reset VT100 text background color.
|
||||||
|
this._parser.registerOscHandler(111, new OscHandler(data => this.restoreBgColor(data)));
|
||||||
// 112 - Reset text cursor color.
|
// 112 - Reset text cursor color.
|
||||||
|
this._parser.registerOscHandler(112, new OscHandler(data => this.restoreCursorColor(data)));
|
||||||
// 113 - Reset mouse foreground color.
|
// 113 - Reset mouse foreground color.
|
||||||
// 114 - Reset mouse background color.
|
// 114 - Reset mouse background color.
|
||||||
// 115 - Reset Tektronix foreground color.
|
// 115 - Reset Tektronix foreground color.
|
||||||
@@ -1977,7 +1985,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
break;
|
break;
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
this.saveCursor();
|
this.saveCursor();
|
||||||
// FALL-THROUGH
|
// FALL-THROUGH
|
||||||
case 47: // alt screen buffer
|
case 47: // alt screen buffer
|
||||||
case 1047: // alt screen buffer
|
case 1047: // alt screen buffer
|
||||||
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
|
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
|
||||||
@@ -2197,7 +2205,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
this.restoreCursor();
|
this.restoreCursor();
|
||||||
break;
|
break;
|
||||||
case 1049: // alt screen buffer cursor
|
case 1049: // alt screen buffer cursor
|
||||||
// FALL-THROUGH
|
// FALL-THROUGH
|
||||||
case 47: // normal screen buffer
|
case 47: // normal screen buffer
|
||||||
case 1047: // normal screen buffer - clearing it first
|
case 1047: // normal screen buffer - clearing it first
|
||||||
// Ensure the selection manager has the correct buffer
|
// Ensure the selection manager has the correct buffer
|
||||||
@@ -2840,46 +2848,167 @@ export class InputHandler extends Disposable implements IInputHandler {
|
|||||||
return true;
|
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>)
|
* 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`."
|
* @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.
|
* `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 elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0.
|
* 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 {
|
public setOrReportIndexedColor(data: string): boolean {
|
||||||
const event = this._parseAnsiColorChange(data);
|
const event: IColorEvent = [];
|
||||||
if (event) {
|
const slots = data.split(';');
|
||||||
this._onAnsiColorChange.fire(event);
|
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 {
|
if (event.length) {
|
||||||
this._logService.warn(`Expected format <num>;rgb:<rr>/<gg>/<bb> but got data: ${data}`);
|
this._onColor.fire(event);
|
||||||
}
|
}
|
||||||
return true;
|
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
|
* ESC E
|
||||||
* C1.NEL
|
* C1.NEL
|
||||||
|
|||||||
Vendored
+94
-72
@@ -349,19 +349,32 @@ export interface IWindowOptions {
|
|||||||
setWinLines?: boolean;
|
setWinLines?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IAnsiColorChangeEventColor {
|
// color events from common, used for OSC 4/10/11/12 and 104/110/111/112
|
||||||
colorIndex: number;
|
export const enum ColorRequestType {
|
||||||
red: number;
|
REPORT = 0,
|
||||||
green: number;
|
SET = 1,
|
||||||
blue: number;
|
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.
|
* 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;
|
registerEscHandler(id: IFunctionIdentifier, callback: () => boolean | Promise<boolean>): IDisposable;
|
||||||
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
registerOscHandler(ident: number, callback: (data: string) => boolean | Promise<boolean>): IDisposable;
|
||||||
|
|
||||||
/** C0 BEL */ bell(): void;
|
/** C0 BEL */ bell(): boolean;
|
||||||
/** C0 LF */ lineFeed(): void;
|
/** C0 LF */ lineFeed(): boolean;
|
||||||
/** C0 CR */ carriageReturn(): void;
|
/** C0 CR */ carriageReturn(): boolean;
|
||||||
/** C0 BS */ backspace(): void;
|
/** C0 BS */ backspace(): boolean;
|
||||||
/** C0 HT */ tab(): void;
|
/** C0 HT */ tab(): boolean;
|
||||||
/** C0 SO */ shiftOut(): void;
|
/** C0 SO */ shiftOut(): boolean;
|
||||||
/** C0 SI */ shiftIn(): void;
|
/** 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 0
|
||||||
OSC 2 */ setTitle(data: string): void;
|
OSC 2 */ setTitle(data: string): boolean;
|
||||||
/** OSC 4 */ setAnsiColor(data: string): void;
|
/** OSC 4 */ setOrReportIndexedColor(data: string): boolean;
|
||||||
/** ESC E */ nextLine(): void;
|
/** OSC 10 */ setOrReportFgColor(data: string): boolean;
|
||||||
/** ESC = */ keypadApplicationMode(): void;
|
/** OSC 11 */ setOrReportBgColor(data: string): boolean;
|
||||||
/** ESC > */ keypadNumericMode(): void;
|
/** 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 % G
|
||||||
ESC % @ */ selectDefaultCharset(): void;
|
ESC % @ */ selectDefaultCharset(): boolean;
|
||||||
/** ESC ( C
|
/** ESC ( C
|
||||||
ESC ) C
|
ESC ) C
|
||||||
ESC * C
|
ESC * C
|
||||||
ESC + C
|
ESC + C
|
||||||
ESC - C
|
ESC - C
|
||||||
ESC . C
|
ESC . C
|
||||||
ESC / C */ selectCharset(collectAndFlag: string): void;
|
ESC / C */ selectCharset(collectAndFlag: string): boolean;
|
||||||
/** ESC D */ index(): void;
|
/** ESC D */ index(): boolean;
|
||||||
/** ESC H */ tabSet(): void;
|
/** ESC H */ tabSet(): boolean;
|
||||||
/** ESC M */ reverseIndex(): void;
|
/** ESC M */ reverseIndex(): boolean;
|
||||||
/** ESC c */ fullReset(): void;
|
/** ESC c */ fullReset(): boolean;
|
||||||
/** ESC n
|
/** ESC n
|
||||||
ESC o
|
ESC o
|
||||||
ESC |
|
ESC |
|
||||||
ESC }
|
ESC }
|
||||||
ESC ~ */ setgLevel(level: number): void;
|
ESC ~ */ setgLevel(level: number): boolean;
|
||||||
/** ESC # 8 */ screenAlignmentPattern(): void;
|
/** ESC # 8 */ screenAlignmentPattern(): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IParseStack {
|
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 { assert } from 'chai';
|
||||||
import { pollFor, openTerminal, getBrowserType, launchBrowser } from './TestUtils';
|
import { pollFor, openTerminal, getBrowserType, launchBrowser, writeSync } from './TestUtils';
|
||||||
import { Browser, Page } from 'playwright';
|
import { Browser, Page } from 'playwright';
|
||||||
import { IRenderDimensions } from 'browser/renderer/Types';
|
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('ESC', () => {
|
||||||
describe('DECRC: Save cursor, ESC 7', () => {
|
describe('DECRC: Save cursor, ESC 7', () => {
|
||||||
it('should save the absolute cursor position so resizing restores to the correct position', async () => {
|
it('should save the absolute cursor position so resizing restores to the correct position', async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user