OSC 10/11 working, OSC 4 partially fixed
This commit is contained in:
@@ -5,6 +5,8 @@
|
||||
|
||||
import { IColor } from 'browser/Types';
|
||||
|
||||
// FIXME: Move Color.ts lib to common?
|
||||
|
||||
/**
|
||||
* Helper functions where the source type is "channels" (individual color channels as numbers).
|
||||
*/
|
||||
@@ -17,6 +19,8 @@ export namespace channels {
|
||||
}
|
||||
|
||||
export function toRgba(r: number, g: number, b: number, a: number = 0xFF): number {
|
||||
// Note: The aggregated number is RGBA32 (BE), thus needs to be converted to ABGR32
|
||||
// on LE systems, before it can be used for direct 32-bit buffer writes.
|
||||
// >>> 0 forces an unsigned int
|
||||
return (r << 24 | g << 16 | b << 8 | a) >>> 0;
|
||||
}
|
||||
@@ -81,6 +85,11 @@ export namespace color {
|
||||
rgba: channels.toRgba(r, g, b, a)
|
||||
};
|
||||
}
|
||||
|
||||
export function toXColorName(color: IColor): string {
|
||||
const [r, g, b] = rgba.toChannels(color.rgba);
|
||||
return `rgb:${toPaddedHex(r)}/${toPaddedHex(g)}/${toPaddedHex(b)}`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -197,6 +206,7 @@ export namespace rgba {
|
||||
return (fgR << 24 | fgG << 16 | fgB << 8 | 0xFF) >>> 0;
|
||||
}
|
||||
|
||||
// FIXME: Move this to channels NS?
|
||||
export function toChannels(value: number): [number, number, number, number] {
|
||||
return [(value >> 24) & 0xFF, (value >> 16) & 0xFF, (value >> 8) & 0xFF, value & 0xFF];
|
||||
}
|
||||
@@ -207,6 +217,13 @@ export namespace rgba {
|
||||
rgba: channels.toRgba(r, g, b)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* convert 0xRRGGBBAA to 0xAABBGGRR (32-bit representation on LE systems)
|
||||
*/
|
||||
export function toABGR32(rgba: number): number {
|
||||
return ((rgba & 0xFF) << 24 | (rgba >>> 8 & 0xFF) << 16 | (rgba >>> 16 & 0xFF) << 8 | rgba >>> 24 & 0xFF) >>> 0;
|
||||
}
|
||||
}
|
||||
|
||||
export function toPaddedHex(c: number): string {
|
||||
|
||||
+53
-11
@@ -21,7 +21,7 @@
|
||||
* http://linux.die.net/man/7/urxvt
|
||||
*/
|
||||
|
||||
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler } from 'browser/Types';
|
||||
import { ICompositionHelper, ITerminal, IBrowser, CustomKeyEventHandler, ILinkifier, IMouseZoneManager, LinkMatcherHandler, ILinkMatcherOptions, IViewport, ILinkifier2, CharacterJoinerHandler, IColor } from 'browser/Types';
|
||||
import { IRenderer } from 'browser/renderer/Types';
|
||||
import { CompositionHelper } from 'browser/input/CompositionHelper';
|
||||
import { Viewport } from 'browser/Viewport';
|
||||
@@ -39,7 +39,7 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
|
||||
import { AccessibilityManager } from './AccessibilityManager';
|
||||
import { ITheme, IMarker, IDisposable, ISelectionPosition, ILinkProvider } from 'xterm';
|
||||
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { IKeyboardEvent, KeyboardResultType, CoreMouseEventType, CoreMouseButton, CoreMouseAction, ITerminalOptions, ScrollSource, IColorEvent } from 'common/Types';
|
||||
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
|
||||
import { EventEmitter, IEvent, forwardEvent } from 'common/EventEmitter';
|
||||
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
|
||||
@@ -53,7 +53,7 @@ import { Linkifier2 } from 'browser/Linkifier2';
|
||||
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
|
||||
import { CoreTerminal } from 'common/CoreTerminal';
|
||||
import { ITerminalOptions as IInitializedTerminalOptions } from 'common/services/Services';
|
||||
import { rgba } from 'browser/Color';
|
||||
import { color, rgba } from 'browser/Color';
|
||||
import { CharacterJoinerService } from 'browser/services/CharacterJoinerService';
|
||||
|
||||
// Let it work inside Node.js for automated testing purposes.
|
||||
@@ -167,7 +167,7 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._inputHandler.onRequestSendFocus(() => this._reportFocus()));
|
||||
this.register(this._inputHandler.onRequestReset(() => this.reset()));
|
||||
this.register(this._inputHandler.onRequestWindowsOptionsReport(type => this._reportWindowsOptions(type)));
|
||||
this.register(this._inputHandler.onAnsiColorChange((event) => this._changeAnsiColor(event)));
|
||||
this.register(this._inputHandler.onColor((event) => this._handleColorEvent(event)));
|
||||
this.register(forwardEvent(this._inputHandler.onCursorMove, this._onCursorMove));
|
||||
this.register(forwardEvent(this._inputHandler.onTitleChange, this._onTitleChange));
|
||||
this.register(forwardEvent(this._inputHandler.onA11yChar, this._onA11yCharEmitter));
|
||||
@@ -177,17 +177,59 @@ export class Terminal extends CoreTerminal implements ITerminal {
|
||||
this.register(this._bufferService.onResize(e => this._afterResize(e.cols, e.rows)));
|
||||
}
|
||||
|
||||
private _changeAnsiColor(event: IAnsiColorChangeEvent): void {
|
||||
private _handleColorEvent(event: IColorEvent): void {
|
||||
if (!this._colorManager) { return; }
|
||||
|
||||
for (const ansiColor of event.colors) {
|
||||
const color = rgba.toColor(ansiColor.red, ansiColor.green, ansiColor.blue);
|
||||
|
||||
this._colorManager!.colors.ansi[ansiColor.colorIndex] = color;
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this._renderService?.setColors(this._colorManager!.colors);
|
||||
this.viewport?.onThemeChange(this._colorManager!.colors);
|
||||
if (query.length) {
|
||||
this.coreService.triggerDataEvent(query.join(''));
|
||||
}
|
||||
if (hasSet) {
|
||||
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, IAnsiColorChangeEvent } from 'common/Types';
|
||||
import { IBufferLine, IAttributeData, IColorEvent } 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): IAnsiColorChangeEvent | null { return this._parseAnsiColorChange(data); }
|
||||
public parseAnsiColorChange(data: string): IColorEvent | null { return this._parseAnsiColorChange(data); }
|
||||
|
||||
/**
|
||||
* Promise based parse call to await the full resolve of given input data.
|
||||
@@ -1924,14 +1924,12 @@ describe('InputHandler', () => {
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
it('4: should parse correct Ansi color change data', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3');
|
||||
|
||||
assert.isNotNull(event);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
});
|
||||
|
||||
it('4: should ignore incorrect Ansi color change data', () => {
|
||||
@@ -1947,33 +1945,33 @@ describe('InputHandler', () => {
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d');
|
||||
|
||||
assert.isNotNull(event);
|
||||
assert.equal(event!.colors.length, 3);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 });
|
||||
assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
|
||||
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] });
|
||||
});
|
||||
it('4: should ignore incorrect colors in a list of Ansi color changes', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d');
|
||||
|
||||
assert.equal(event!.colors.length, 2);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
|
||||
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] });
|
||||
});
|
||||
it('4: should be case insensitive when parsing Ansi color changes', () => {
|
||||
// this is testing a private method
|
||||
const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3');
|
||||
|
||||
assert.equal(event!.colors.length, 1);
|
||||
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
|
||||
assert.equal(event!.requests.length, 1);
|
||||
assert.deepEqual(event!.requests[0], { index: 19, color: [0xa1, 0xb2, 0xc3] });
|
||||
});
|
||||
it('4: should fire event on Ansi color change', async () => {
|
||||
return new Promise(async r => {
|
||||
inputHandler.onAnsiColorChange(e => {
|
||||
inputHandler.onColor(e => {
|
||||
assert.isNotNull(e);
|
||||
assert.isNotNull(e!.colors);
|
||||
assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c });
|
||||
assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 });
|
||||
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] });
|
||||
r();
|
||||
});
|
||||
await inputHandler.parseP('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
|
||||
|
||||
+18
-12
@@ -4,7 +4,7 @@
|
||||
* @license MIT
|
||||
*/
|
||||
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent, IParseStack } from 'common/Types';
|
||||
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IColorEvent, IParseStack } from 'common/Types';
|
||||
import { C0, C1 } from 'common/data/EscapeSequences';
|
||||
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
|
||||
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
|
||||
@@ -262,8 +262,8 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
public get onScroll(): IEvent<number> { return this._onScroll.event; }
|
||||
private _onTitleChange = new EventEmitter<string>();
|
||||
public get onTitleChange(): IEvent<string> { return this._onTitleChange.event; }
|
||||
private _onAnsiColorChange = new EventEmitter<IAnsiColorChangeEvent>();
|
||||
public get onAnsiColorChange(): IEvent<IAnsiColorChangeEvent> { return this._onAnsiColorChange.event; }
|
||||
private _onColor = new EventEmitter<IColorEvent>();
|
||||
public get onColor(): IEvent<IColorEvent> { return this._onColor.event; }
|
||||
|
||||
private _parseStack: IParseStack = {
|
||||
paused: false,
|
||||
@@ -2841,22 +2841,24 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null {
|
||||
const result: IAnsiColorChangeEvent = { colors: [] };
|
||||
protected _parseAnsiColorChange(data: string): IColorEvent | null {
|
||||
const result: IColorEvent = { requests: [] };
|
||||
// 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)
|
||||
result.requests.push({
|
||||
index: parseInt(match[1]),
|
||||
color: [
|
||||
parseInt(match[2], 16),
|
||||
parseInt(match[3], 16),
|
||||
parseInt(match[4], 16)
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
if (result.colors.length === 0) {
|
||||
if (result.requests.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -2923,7 +2925,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
public setAnsiColor(data: string): boolean {
|
||||
const event = this._parseAnsiColorChange(data);
|
||||
if (event) {
|
||||
this._onAnsiColorChange.fire(event);
|
||||
this._onColor.fire(event);
|
||||
}
|
||||
else {
|
||||
this._logService.warn(`Expected format <num>;rgb:<rr>/<gg>/<bb> but got data: ${data}`);
|
||||
@@ -2939,11 +2941,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (slots[0] === '?') {
|
||||
// TODO: query FG color
|
||||
console.log('query FG');
|
||||
this._onColor.fire({ requests: [{ index: 256, color: '?' }] });
|
||||
} else {
|
||||
const color = this._parseXColorName(slots[0]);
|
||||
if (color) {
|
||||
// set new FG color
|
||||
console.log('set FG', color);
|
||||
this._onColor.fire({ requests: [{ index: 256, color }] });
|
||||
}
|
||||
}
|
||||
if (slots.length === 2) {
|
||||
@@ -2957,11 +2961,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
if (slots[0] === '?') {
|
||||
// TODO: query BG color
|
||||
console.log('query BG');
|
||||
this._onColor.fire({ requests: [{ index: 257, color: '?' }] });
|
||||
} else {
|
||||
const color = this._parseXColorName(slots[0]);
|
||||
if (color) {
|
||||
// set new BG color
|
||||
console.log('set BG', color);
|
||||
this._onColor.fire({ requests: [{ index: 257, color }] });
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
Vendored
+5
-7
@@ -347,18 +347,16 @@ export interface IWindowOptions {
|
||||
setWinLines?: boolean;
|
||||
}
|
||||
|
||||
export interface IAnsiColorChangeEventColor {
|
||||
colorIndex: number;
|
||||
red: number;
|
||||
green: number;
|
||||
blue: number;
|
||||
export interface IColorEventColor {
|
||||
index: number;
|
||||
color: [number, number, number] | '?';
|
||||
}
|
||||
|
||||
/**
|
||||
* Event fired for OSC 4 command - to change ANSI color based on its index.
|
||||
*/
|
||||
export interface IAnsiColorChangeEvent {
|
||||
colors: IAnsiColorChangeEventColor[];
|
||||
export interface IColorEvent {
|
||||
requests: IColorEventColor[];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user