mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
properly parse xcolor names
This commit is contained in:
@@ -54,6 +54,10 @@ class TestInputHandler extends InputHandler {
|
||||
prev = await result;
|
||||
}
|
||||
}
|
||||
|
||||
public parseXColorName(data: string): void | [number, number, number] {
|
||||
return this._parseXColorName(data);
|
||||
}
|
||||
}
|
||||
|
||||
describe('InputHandler', () => {
|
||||
@@ -1865,6 +1869,63 @@ describe('InputHandler', () => {
|
||||
});
|
||||
});
|
||||
describe('OSC', () => {
|
||||
describe.only('parse xcolor names', () => {
|
||||
it('rgb:<r>/<g>/<b> scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:0/0/0'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:f/f/f'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:1/2/3'), [17, 34, 51]);
|
||||
// 8 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:00/00/00'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:ff/ff/ff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:11/22/33'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:000/000/000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:fff/fff/fff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:111/222/333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:0000/0000/0000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:ffff/ffff/ffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('rgb:1111/2222/3333'), [17, 34, 51]);
|
||||
});
|
||||
it('#RGB scheme in 4/8/12/16 bit', () => {
|
||||
// 4 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('#000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#fff'), [240, 240, 240]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#123'), [16, 32, 48]);
|
||||
// 8 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('#000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#ffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#112233'), [17, 34, 51]);
|
||||
// 12 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('#000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#fffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#111222333'), [17, 34, 51]);
|
||||
// 16 bit
|
||||
assert.deepEqual(inputHandler.parseXColorName('#000000000000'), [0, 0, 0]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#ffffffffffff'), [255, 255, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#111122223333'), [17, 34, 51]);
|
||||
});
|
||||
it('supports upper case', () => {
|
||||
assert.deepEqual(inputHandler.parseXColorName('RGB:0/A/F'), [0, 170, 255]);
|
||||
assert.deepEqual(inputHandler.parseXColorName('#FFF'), [240, 240, 240]);
|
||||
});
|
||||
it('does not parse illegal combinations', () => {
|
||||
// shifting bit width
|
||||
assert.equal(inputHandler.parseXColorName('rgb:0/11/222'), undefined);
|
||||
// unsupported scheme
|
||||
assert.equal(inputHandler.parseXColorName('rgbi:00/11/22'), undefined);
|
||||
// broken # specifier
|
||||
assert.equal(inputHandler.parseXColorName('#aabbbcc'), undefined);
|
||||
// out of range
|
||||
assert.equal(inputHandler.parseXColorName('#aabbgg'), undefined);
|
||||
assert.equal(inputHandler.parseXColorName('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');
|
||||
|
||||
+98
-11
@@ -25,7 +25,7 @@ import { IBuffer } from 'common/buffer/Types';
|
||||
/**
|
||||
* Map collect to glevel. Used in `selectCharset`.
|
||||
*/
|
||||
const GLEVEL: {[key: string]: number} = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
|
||||
const GLEVEL: { [key: string]: number } = { '(': 0, ')': 1, '*': 2, '+': 3, '-': 1, '.': 2 };
|
||||
|
||||
/**
|
||||
* VT commands done by the parser - FIXME: move this to the parser?
|
||||
@@ -167,7 +167,7 @@ class DECRQSS implements IDcsHandler {
|
||||
break;
|
||||
case 'r': // DECSTBM
|
||||
const pt = '' + (this._bufferService.buffer.scrollTop + 1) +
|
||||
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
|
||||
';' + (this._bufferService.buffer.scrollBottom + 1) + 'r';
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${pt}${C0.ESC}\\`);
|
||||
break;
|
||||
case 'm': // SGR
|
||||
@@ -175,7 +175,7 @@ class DECRQSS implements IDcsHandler {
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r0m${C0.ESC}\\`);
|
||||
break;
|
||||
case ' q': // DECSCUSR
|
||||
const STYLES: {[key: string]: number} = { 'block': 2, 'underline': 4, 'bar': 6 };
|
||||
const STYLES: { [key: string]: number } = { 'block': 2, 'underline': 4, 'bar': 6 };
|
||||
let style = STYLES[this._optionsService.options.cursorStyle];
|
||||
style -= this._optionsService.options.cursorBlink ? 1 : 0;
|
||||
this._coreService.triggerDataEvent(`${C0.ESC}P1$r${style} q${C0.ESC}\\`);
|
||||
@@ -403,7 +403,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)));
|
||||
// 11 - Change VT100 text background color to Pt.
|
||||
this._parser.registerOscHandler(11, new OscHandler(data => this.queryOrSetBgColor(data)));
|
||||
// 12 - Change text cursor color to Pt.
|
||||
// 13 - Change mouse foreground color to Pt.
|
||||
// 14 - Change mouse background color to Pt.
|
||||
@@ -855,10 +857,9 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* - any cursor movement sequence keeps working as expected
|
||||
*/
|
||||
if (this._activeBuffer.x === 0
|
||||
&& this._activeBuffer.y > this._activeBuffer.scrollTop
|
||||
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
|
||||
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped)
|
||||
{
|
||||
&& this._activeBuffer.y > this._activeBuffer.scrollTop
|
||||
&& this._activeBuffer.y <= this._activeBuffer.scrollBottom
|
||||
&& this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)?.isWrapped) {
|
||||
this._activeBuffer.lines.get(this._activeBuffer.ybase + this._activeBuffer.y)!.isWrapped = false;
|
||||
this._activeBuffer.y--;
|
||||
this._activeBuffer.x = this._bufferService.cols - 1;
|
||||
@@ -1977,7 +1978,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
this.saveCursor();
|
||||
// FALL-THROUGH
|
||||
// FALL-THROUGH
|
||||
case 47: // alt screen buffer
|
||||
case 1047: // alt screen buffer
|
||||
this._bufferService.buffers.activateAltBuffer(this._eraseAttrData());
|
||||
@@ -2197,7 +2198,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this.restoreCursor();
|
||||
break;
|
||||
case 1049: // alt screen buffer cursor
|
||||
// FALL-THROUGH
|
||||
// FALL-THROUGH
|
||||
case 47: // normal screen buffer
|
||||
case 1047: // normal screen buffer - clearing it first
|
||||
// Ensure the selection manager has the correct buffer
|
||||
@@ -2264,7 +2265,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
}
|
||||
// exit early if can decide color mode with semicolons
|
||||
if ((accu[1] === 5 && advance + cSpace >= 2)
|
||||
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|
||||
|| (accu[1] === 2 && advance + cSpace >= 5)) {
|
||||
break;
|
||||
}
|
||||
// offset colorSpace slot for semicolon mode
|
||||
@@ -2683,7 +2684,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
const top = params.params[0] || 1;
|
||||
let bottom: number;
|
||||
|
||||
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
|
||||
if (params.length < 2 || (bottom = params.params[1]) > this._bufferService.rows || bottom === 0) {
|
||||
bottom = this._bufferService.rows;
|
||||
}
|
||||
|
||||
@@ -2862,6 +2863,56 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse xcolor name 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 _parseXColorName(data: string): [number, number, number] | void {
|
||||
// also handle uppercases
|
||||
data = data.toLowerCase();
|
||||
if (data.indexOf('rgb:') === 0) {
|
||||
// 'rgb:' specifier
|
||||
data = data.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(data);
|
||||
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 (data.indexOf('#') === 0) {
|
||||
// '#' specifier
|
||||
data = data.slice(1);
|
||||
const rex = /^[\da-f]+$/;
|
||||
if (rex.exec(data) && [3, 6, 9, 12].includes(data.length)) {
|
||||
const adv = data.length / 3;
|
||||
const r = parseInt(data.slice(0, adv), 16);
|
||||
const g = parseInt(data.slice(adv, 2 * adv), 16);
|
||||
const b = parseInt(data.slice(2 * adv, 3 * adv), 16);
|
||||
switch (adv) {
|
||||
case 1:
|
||||
return [r << 4, g << 4, b << 4];
|
||||
case 2:
|
||||
return [r, g, b];
|
||||
case 3:
|
||||
return [r >> 4, g >> 4, b >> 4];
|
||||
case 4:
|
||||
return [r >> 8, g >> 8, b >> 8];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
|
||||
*
|
||||
@@ -2880,6 +2931,42 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
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 occurences.
|
||||
const slots = data.split(';').slice(0, 2);
|
||||
if (slots[0] === '?') {
|
||||
// TODO: query FG color
|
||||
console.log('query FG');
|
||||
} else {
|
||||
const color = this._parseXColorName(slots[0]);
|
||||
if (color) {
|
||||
// set new FG color
|
||||
console.log('set FG', color);
|
||||
}
|
||||
}
|
||||
if (slots.length === 2) {
|
||||
this.queryOrSetBgColor(slots[1]);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public queryOrSetBgColor(data: string): boolean {
|
||||
const slots = data.split(';').slice(0, 1);
|
||||
if (slots[0] === '?') {
|
||||
// TODO: query BG color
|
||||
console.log('query BG');
|
||||
} else {
|
||||
const color = this._parseXColorName(slots[0]);
|
||||
if (color) {
|
||||
// set new BG color
|
||||
console.log('set BG', color);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* ESC E
|
||||
* C1.NEL
|
||||
|
||||
Reference in New Issue
Block a user