mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
simplify color events
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
import { IColorRGB } from 'common/Types';
|
||||
|
||||
// FIXME: Move Color.ts lib to common?
|
||||
|
||||
@@ -86,9 +87,8 @@ export namespace color {
|
||||
};
|
||||
}
|
||||
|
||||
export function toXColorName(color: IColor): string {
|
||||
const [r, g, b] = rgba.toChannels(color.rgba);
|
||||
return `rgb:${toPaddedHex(r)}${toPaddedHex(r)}/${toPaddedHex(g)}${toPaddedHex(g)}/${toPaddedHex(b)}${toPaddedHex(b)}`;
|
||||
export function toColorRGB(color: IColor): IColorRGB {
|
||||
return [(color.rgba >> 24) & 0xFF, (color.rgba >> 16) & 0xFF, (color.rgba >> 8) & 0xFF];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+20
-51
@@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent } from 'common/Types';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent, ColorIndex, IColorRGB } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
@@ -55,6 +55,7 @@ import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
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;
|
||||
@@ -178,58 +179,26 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
}
|
||||
|
||||
private _handleColorEvent(event: IColorEvent): void {
|
||||
if (!this._colorManager) { return; }
|
||||
|
||||
let hasSet = false;
|
||||
const query: string[] = [];
|
||||
for (const req of event.requests) {
|
||||
if (req.color === '?') {
|
||||
// query color
|
||||
let ident = '';
|
||||
let c: IColor;
|
||||
switch (req.index) {
|
||||
case 256:
|
||||
ident = '10';
|
||||
c = this._colorManager.colors.foreground;
|
||||
break;
|
||||
case 257:
|
||||
ident = '11';
|
||||
c = this._colorManager.colors.background;
|
||||
break;
|
||||
default:
|
||||
if (0 <= req.index && req.index < 256) {
|
||||
ident = '4;' + req.index;
|
||||
c = this._colorManager.colors.ansi[req.index];
|
||||
}
|
||||
}
|
||||
if (ident) {
|
||||
query.push(`${C0.ESC}]${ident};${color.toXColorName(c!)}${C0.BEL}`);
|
||||
}
|
||||
} else {
|
||||
// set color
|
||||
hasSet = true;
|
||||
switch (req.index) {
|
||||
case 256:
|
||||
this._colorManager.colors.foreground = rgba.toColor(...req.color);
|
||||
break;
|
||||
case 257:
|
||||
this._colorManager.colors.background = rgba.toColor(...req.color);
|
||||
break;
|
||||
default:
|
||||
if (0 <= req.index && req.index < 256) {
|
||||
this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
}
|
||||
}
|
||||
if (!this._colorManager) return;
|
||||
for (const req of event) {
|
||||
switch (req.index) {
|
||||
case ColorIndex.FOREGROUND: // OSC 10
|
||||
if (req.color) this._colorManager.colors.foreground = rgba.toColor(...req.color);
|
||||
else this.coreService.triggerDataEvent(`${C0.ESC}]10;${toRgbString(color.toColorRGB(this._colorManager.colors.foreground))}${C0.BEL}`);
|
||||
break;
|
||||
case ColorIndex.BACKGROUND: // OSC 11
|
||||
if (req.color) this._colorManager.colors.background = rgba.toColor(...req.color);
|
||||
else this.coreService.triggerDataEvent(`${C0.ESC}]11;${toRgbString(color.toColorRGB(this._colorManager.colors.background))}${C0.BEL}`);
|
||||
break;
|
||||
default: // OSC 4
|
||||
if (0 <= req.index && req.index < 256) {
|
||||
if (req.color) this._colorManager.colors.ansi[req.index] = rgba.toColor(...req.color);
|
||||
else this.coreService.triggerDataEvent(`${C0.ESC}]4;${req.index};${toRgbString(color.toColorRGB(this._colorManager.colors.ansi[req.index]))}${C0.BEL}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (query.length) {
|
||||
this.coreService.triggerDataEvent(query.join(''));
|
||||
}
|
||||
if (hasSet) {
|
||||
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 {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { assert } from 'chai';
|
||||
import { InputHandler } from 'common/InputHandler';
|
||||
import { IBufferLine, IAttributeData, IColorEvent } from 'common/Types';
|
||||
import { IBufferLine, IAttributeData, IColorEvent, ColorIndex } from 'common/Types';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
import { CellData } from 'common/buffer/CellData';
|
||||
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
|
||||
@@ -41,7 +41,7 @@ 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): IColorEvent | null { return this._parseAnsiColorChange(data); }
|
||||
public parseAnsiColorChange(data: string): IColorEvent{ return this._parseAnsiColorChange(data); }
|
||||
|
||||
/**
|
||||
* Promise based parse call to await the full resolve of given input data.
|
||||
@@ -54,10 +54,6 @@ class TestInputHandler extends InputHandler {
|
||||
prev = await result;
|
||||
}
|
||||
}
|
||||
|
||||
public parseColorSpec(data: string): undefined | [number, number, number] {
|
||||
return this._parseColorSpec(data);
|
||||
}
|
||||
}
|
||||
|
||||
describe('InputHandler', () => {
|
||||
@@ -1869,114 +1865,98 @@ describe('InputHandler', () => {
|
||||
});
|
||||
});
|
||||
describe('OSC', () => {
|
||||
describe('parse xcolor names', () => {
|
||||
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:0/0/0'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:f/f/f'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:1/2/3'), [17, 34, 51]);
|
||||
// 8 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:00/00/00'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:ff/ff/ff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:11/22/33'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:000/000/000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:fff/fff/fff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:111/222/333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:0000/0000/0000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:ffff/ffff/ffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('rgb:1111/2222/3333'), [17, 34, 51]);
|
||||
});
|
||||
it('#RGB scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#fff'), [240, 240, 240]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#123'), [16, 32, 48]);
|
||||
// 8 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#ffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#112233'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#fffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#111222333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#000000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#ffffffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#111122223333'), [17, 34, 51]);
|
||||
});
|
||||
it('supports upper case', () => {
|
||||
assert.deepEqual(inputHandler.parseColorSpec('RGB:0/A/F'), [0, 170, 255]);
|
||||
assert.deepEqual(inputHandler.parseColorSpec('#FFF'), [240, 240, 240]);
|
||||
});
|
||||
it('does not parse illegal combinations', () => {
|
||||
// shifting bit width
|
||||
assert.equal(inputHandler.parseColorSpec('rgb:0/11/222'), undefined);
|
||||
// unsupported scheme
|
||||
assert.equal(inputHandler.parseColorSpec('rgbi:00/11/22'), undefined);
|
||||
// broken # specifier
|
||||
assert.equal(inputHandler.parseColorSpec('#aabbbcc'), undefined);
|
||||
// out of range
|
||||
assert.equal(inputHandler.parseColorSpec('#aabbgg'), undefined);
|
||||
assert.equal(inputHandler.parseColorSpec('rgb:aa/bb/gg'), undefined);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
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!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.deepEqual(event![0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
});
|
||||
|
||||
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'));
|
||||
assert.equal(inputHandler.parseAnsiColorChange('17;rgb:a/b/c').length, 0);
|
||||
assert.equal(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc').length, 0);
|
||||
assert.equal(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc').length, 0);
|
||||
assert.equal(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc').length, 0);
|
||||
});
|
||||
|
||||
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!.requests.length, 3);
|
||||
assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.deepEqual(event!.requests[1], { index: 17, color: [0x00, 0x11, 0x22] });
|
||||
assert.deepEqual(event!.requests[2], { index: 255, color: [0x01, 0xef, 0x2d] });
|
||||
assert.equal(event.length, 3);
|
||||
assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.deepEqual(event[1], { index: 17, color: [0x00, 0x11, 0x22] });
|
||||
assert.deepEqual(event[2], { index: 255, color: [0x01, 0xef, 0x2d] });
|
||||
});
|
||||
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!.requests.length, 2);
|
||||
assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.deepEqual(event!.requests[1], { index: 255, color: [0x01, 0xef, 0x2d] });
|
||||
assert.equal(event.length, 2);
|
||||
assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.deepEqual(event[1], { index: 255, color: [0x01, 0xef, 0x2d] });
|
||||
});
|
||||
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!.requests.length, 1);
|
||||
assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
assert.equal(event.length, 1);
|
||||
assert.deepEqual(event[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
});
|
||||
it('4: should fire event on Ansi color change', async () => {
|
||||
return new Promise(async r => {
|
||||
inputHandler.onColor(e => {
|
||||
assert.isNotNull(e);
|
||||
assert.isNotNull(e!.requests);
|
||||
assert.deepEqual(e!.requests[0], { index: 17, color: [0x1a, 0x2b, 0x3c] });
|
||||
assert.deepEqual(e!.requests[1], { index: 12, color: [0x11, 0x22, 0x33] });
|
||||
assert.deepEqual(e[0], { index: 17, color: [0x1a, 0x2b, 0x3c] });
|
||||
assert.deepEqual(e[1], { index: 12, color: [0x11, 0x22, 0x33] });
|
||||
r();
|
||||
});
|
||||
await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
|
||||
});
|
||||
});
|
||||
|
||||
it('10: should create appropriate 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, [[{ index: ColorIndex.FOREGROUND }]]);
|
||||
stack.length = 0;
|
||||
// OSC with multiple values maps to OSC 10 & OSC 11
|
||||
await inputHandler.parseP('\x1b]10;?;?;?;?\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND }], [{ index: ColorIndex.BACKGROUND }]]);
|
||||
stack.length = 0;
|
||||
// set foreground color events
|
||||
await inputHandler.parseP('\x1b]10;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]10;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }]]);
|
||||
stack.length = 0;
|
||||
// set FG and BG at once
|
||||
await inputHandler.parseP('\x1b]10;rgb:aa/bb/cc;#001122\x07');
|
||||
assert.deepEqual(stack, [
|
||||
[{ index: ColorIndex.FOREGROUND, color: [170, 187, 204] }],
|
||||
[{ index: ColorIndex.BACKGROUND, color: [0, 17, 34] }],
|
||||
]);
|
||||
});
|
||||
it('11: should create appropriate events', async () => {
|
||||
const stack: IColorEvent[] = [];
|
||||
inputHandler.onColor(ev => stack.push(ev));
|
||||
// single foreground query --> color undefined
|
||||
await inputHandler.parseP('\x1b]11;?\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]);
|
||||
stack.length = 0;
|
||||
// OSC 11 with multiple values creates only one BG event
|
||||
await inputHandler.parseP('\x1b]11;?;?;?;?\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND }]]);
|
||||
stack.length = 0;
|
||||
// set background color events
|
||||
await inputHandler.parseP('\x1b]11;rgb:01/02/03\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [1, 2, 3] }]]);
|
||||
stack.length = 0;
|
||||
await inputHandler.parseP('\x1b]11;#aabbcc\x07');
|
||||
assert.deepEqual(stack, [[{ index: ColorIndex.BACKGROUND, color: [170, 187, 204] }]]);
|
||||
});
|
||||
});
|
||||
|
||||
// issue #3362 and #2979
|
||||
|
||||
+24
-72
@@ -4,7 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack } from 'common/Types';
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack, ColorIndex } 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`.
|
||||
@@ -403,9 +404,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
// 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.queryOrSetFgColor(data)));
|
||||
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.queryOrSetBgColor(data)));
|
||||
this._parser.registerOscHandler(11, new OscHandler(data => this.setOrReportBgColor(data)));
|
||||
// 12 - Change text cursor color to Pt.
|
||||
// 13 - Change mouse foreground color to Pt.
|
||||
// 14 - Change mouse background color to Pt.
|
||||
@@ -2841,14 +2842,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected _parseAnsiColorChange(data: string): IColorEvent | null {
|
||||
const result: IColorEvent = { requests: [] };
|
||||
protected _parseAnsiColorChange(data: string): IColorEvent {
|
||||
const result: IColorEvent = [];
|
||||
// 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.requests.push({
|
||||
result.push({
|
||||
index: parseInt(match[1]),
|
||||
color: [
|
||||
parseInt(match[2], 16),
|
||||
@@ -2857,58 +2857,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (result.requests.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
protected _parseColorSpec(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 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})$/;
|
||||
const m = 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);
|
||||
const rex = /^[\da-f]+$/;
|
||||
if (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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
|
||||
*
|
||||
@@ -2918,7 +2869,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
*/
|
||||
public setAnsiColor(data: string): boolean {
|
||||
const event = this._parseAnsiColorChange(data);
|
||||
if (event) {
|
||||
if (event.length) {
|
||||
this._onColor.fire(event);
|
||||
}
|
||||
else {
|
||||
@@ -2940,6 +2891,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* - `#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).
|
||||
@@ -2947,21 +2900,20 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* **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 queryOrSetFgColor(data: string): boolean {
|
||||
// note: data may contain multiple ? or color names separated with ;
|
||||
// Multiple values will map through to OSC 10 - 19, but we only support 10 and 11 currently,
|
||||
// thus truncate to max. 2 entries.
|
||||
const slots = data.split(';').slice(0, 2);
|
||||
public setOrReportFgColor(data: string): boolean {
|
||||
// Note: data may contain multiple values separated with ; mapping to OSC 10 - 19
|
||||
const slots = data.split(';');
|
||||
if (slots[0] === '?') {
|
||||
this._onColor.fire({ requests: [{ index: 256, color: '?' }] });
|
||||
this._onColor.fire([{ index: ColorIndex.FOREGROUND }]);
|
||||
} else {
|
||||
const color = this._parseColorSpec(slots[0]);
|
||||
const color = parseColor(slots[0]);
|
||||
if (color) {
|
||||
this._onColor.fire({ requests: [{ index: 256, color }] });
|
||||
this._onColor.fire([{ index: ColorIndex.FOREGROUND, color }]);
|
||||
}
|
||||
}
|
||||
if (slots.length === 2) {
|
||||
this.queryOrSetBgColor(slots[1]);
|
||||
// forward second slot to OSC 11 (higher slots are not supported)
|
||||
if (slots.length > 1) {
|
||||
this.setOrReportBgColor(slots[1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
@@ -2971,14 +2923,14 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
*
|
||||
* @vt: #Y OSC 11 "Set or query default background color" "OSC 11 ; Pt BEL" "Same as OSC 10, but for default background."
|
||||
*/
|
||||
public queryOrSetBgColor(data: string): boolean {
|
||||
const slots = data.split(';').slice(0, 1);
|
||||
public setOrReportBgColor(data: string): boolean {
|
||||
const slots = data.split(';');
|
||||
if (slots[0] === '?') {
|
||||
this._onColor.fire({ requests: [{ index: 257, color: '?' }] });
|
||||
this._onColor.fire([{ index: ColorIndex.BACKGROUND }]);
|
||||
} else {
|
||||
const color = this._parseColorSpec(slots[0]);
|
||||
const color = parseColor(slots[0]);
|
||||
if (color) {
|
||||
this._onColor.fire({ requests: [{ index: 257, color }] });
|
||||
this._onColor.fire([{ index: ColorIndex.BACKGROUND, color }]);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
Vendored
+12
-9
@@ -348,17 +348,20 @@ export interface IWindowOptions {
|
||||
setWinLines?: boolean;
|
||||
}
|
||||
|
||||
export interface IColorEventColor {
|
||||
index: number;
|
||||
color: [number, number, number] | '?';
|
||||
// color events from common, used for OSC 4/10/11
|
||||
export const enum ColorIndex {
|
||||
FOREGROUND = 256,
|
||||
BACKGROUND = 257
|
||||
}
|
||||
export interface IColorReportRequest {
|
||||
index: ColorIndex;
|
||||
color?: IColorRGB;
|
||||
}
|
||||
export interface IColorSetRequest extends IColorReportRequest {
|
||||
color: IColorRGB;
|
||||
}
|
||||
export type IColorEvent = (IColorReportRequest | IColorSetRequest)[];
|
||||
|
||||
/**
|
||||
* Event fired for OSC 4 command - to change ANSI color based on its index.
|
||||
*/
|
||||
export interface IColorEvent {
|
||||
requests: IColorEventColor[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls the parser and handles actions generated by the parser.
|
||||
|
||||
@@ -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,77 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
// FIXME: Once #3530 is resolved, implement named colors.
|
||||
}
|
||||
|
||||
// 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)}`;
|
||||
}
|
||||
Reference in New Issue
Block a user