Merge branch 'master' into fix/scroll-events

This commit is contained in:
Jakob
2021-01-13 11:12:14 +01:00
committed by GitHub
19 changed files with 206 additions and 48 deletions
+53 -1
View File
@@ -5,7 +5,7 @@
import { assert, expect } from 'chai';
import { InputHandler } from 'common/InputHandler';
import { IBufferLine, IAttributeData } from 'common/Types';
import { IBufferLine, IAttributeData, IAnsiColorChangeEvent } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { Attributes, UnderlineStyle } from 'common/buffer/Constants';
@@ -40,6 +40,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); }
}
describe('InputHandler', () => {
@@ -1692,4 +1693,55 @@ describe('InputHandler', () => {
assert.equal(coreService.decPrivateModes.origin, false);
});
});
describe('OSC', () => {
it('4: should parse correct Ansi color change data', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3');
assert.isNotNull(event);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
}),
it('4: should ignore incorrect Ansi color change data', () => {
// this is testing a private method
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:a/b/c'));
assert.isNull(inputHandler.parseAnsiColorChange('17;rgb:#aabbcc'));
assert.isNull(inputHandler.parseAnsiColorChange('17;rgba:aa/bb/cc'));
assert.isNull(inputHandler.parseAnsiColorChange('rgb:aa/bb/cc'));
});
it('4: should parse a list of Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:00/11/22;255;rgb:01/ef/2d');
assert.isNotNull(event);
assert.equal(event!.colors.length, 3);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
assert.deepEqual(event!.colors[1], { colorIndex: 17, red: 0x00, green: 0x11, blue: 0x22 });
assert.deepEqual(event!.colors[2], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
});
it('4: should ignore incorrect colors in a list of Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rgb:a1/b2/c3;17;rgb:WR/ON/G;255;rgb:01/ef/2d');
assert.equal(event!.colors.length, 2);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
assert.deepEqual(event!.colors[1], { colorIndex: 255, red: 0x01, green: 0xef, blue: 0x2d });
});
it('4: should be case insensitive when parsing Ansi color changes', () => {
// this is testing a private method
const event = inputHandler.parseAnsiColorChange('19;rGb:A1/b2/C3');
assert.equal(event!.colors.length, 1);
assert.deepEqual(event!.colors[0], { colorIndex: 19, red: 0xa1, green: 0xb2, blue: 0xc3 });
});
it('4: should fire event on Ansi color change', (done) => {
inputHandler.onAnsiColorChange(e => {
assert.isNotNull(e);
assert.isNotNull(e!.colors);
assert.deepEqual(e!.colors[0], { colorIndex: 17, red: 0x1a, green: 0x2b, blue: 0x3c });
assert.deepEqual(e!.colors[1], { colorIndex: 12, red: 0x11, green: 0x22, blue: 0x33 });
done();
});
inputHandler.parse('\x1b]4;17;rgb:1a/2b/3c;12;rgb:11/22/33\x1b\\');
});
});
});
+43 -1
View File
@@ -4,7 +4,7 @@
* @license MIT
*/
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions } from 'common/Types';
import { IInputHandler, IAttributeData, IDisposable, IWindowOptions, IAnsiColorChangeEvent } from 'common/Types';
import { C0, C1 } from 'common/data/EscapeSequences';
import { CHARSETS, DEFAULT_CHARSET } from 'common/data/Charsets';
import { EscapeSequenceParser } from 'common/parser/EscapeSequenceParser';
@@ -250,6 +250,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; }
constructor(
private readonly _bufferService: IBufferService,
@@ -372,6 +374,7 @@ export class InputHandler extends Disposable implements IInputHandler {
this._parser.setOscHandler(2, new OscHandler((data: string) => this.setTitle(data)));
// 3 - set property X in the form "prop=value"
// 4 - Change Color Number
this._parser.setOscHandler(4, new OscHandler((data: string) => this.setAnsiColor(data)));
// 5 - Change Special Color Number
// 6 - Enable/disable Special Color Number c
// 7 - current directory? (not in xterm spec, see https://gitlab.com/gnachman/iterm2/issues/3939)
@@ -2711,6 +2714,45 @@ export class InputHandler extends Disposable implements IInputHandler {
this._iconName = data;
}
protected _parseAnsiColorChange(data: string): IAnsiColorChangeEvent | null {
const result: IAnsiColorChangeEvent = { colors: [] };
// example data: 5;rgb:aa/bb/cc
const regex = /(\d+);rgb:([0-9a-f]{2})\/([0-9a-f]{2})\/([0-9a-f]{2})/gi;
let match;
while ((match = regex.exec(data)) !== null) {
result.colors.push({
colorIndex: parseInt(match[1]),
red: parseInt(match[2], 16),
green: parseInt(match[3], 16),
blue: parseInt(match[4], 16)
});
}
if (result.colors.length === 0) {
return null;
}
return result;
}
/**
* OSC 4; <num> ; <text> ST (set ANSI color <num> to <text>)
*
* @vt: #Y OSC 4 "Set ANSI color" "OSC 4 ; c ; spec BEL" "Change color number `c` to the color specified by `spec`."
* `c` is the color index between 0 and 255. `spec` color format is 'rgb:hh/hh/hh' where `h` are hexadecimal digits.
* There may be multipe c ; spec elements present in the same instruction, e.g. 1;rgb:10/20/30;2;rgb:a0/b0/c0.
*/
public setAnsiColor(data: string): void {
const event = this._parseAnsiColorChange(data);
if (event) {
this._onAnsiColorChange.fire(event);
}
else {
this._logService.warn(`Expected format <num>;rgb:<rr>/<gg>/<bb> but got data: ${data}`);
}
}
/**
* ESC E
* C1.NEL
+15
View File
@@ -338,6 +338,20 @@ export interface IWindowOptions {
setWinLines?: boolean;
}
export interface IAnsiColorChangeEventColor {
colorIndex: number;
red: number;
green: number;
blue: number;
}
/**
* 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.
*/
@@ -399,6 +413,7 @@ export interface IInputHandler {
/** CSI ' ~ */ deleteColumns(params: IParams): void;
/** OSC 0
OSC 2 */ setTitle(data: string): void;
/** OSC 4 */ setAnsiColor(data: string): void;
/** ESC E */ nextLine(): void;
/** ESC = */ keypadApplicationMode(): void;
/** ESC > */ keypadNumericMode(): void;
+10 -8
View File
@@ -15,10 +15,9 @@ import { Disposable } from 'common/Lifecycle';
* provides also utilities for working with them.
*/
export class BufferSet extends Disposable implements IBufferSet {
private _normal: Buffer;
private _alt: Buffer;
private _activeBuffer: Buffer;
private _normal!: Buffer;
private _alt!: Buffer;
private _activeBuffer!: Buffer;
private _onBufferActivate = this.register(new EventEmitter<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}>());
public get onBufferActivate(): IEvent<{activeBuffer: IBuffer, inactiveBuffer: IBuffer}> { return this._onBufferActivate.event; }
@@ -28,17 +27,20 @@ export class BufferSet extends Disposable implements IBufferSet {
* @param _terminal - The terminal the BufferSet will belong to
*/
constructor(
optionsService: IOptionsService,
bufferService: IBufferService
private readonly _optionsService: IOptionsService,
private readonly _bufferService: IBufferService
) {
super();
this.reset();
}
this._normal = new Buffer(true, optionsService, bufferService);
public reset(): void {
this._normal = new Buffer(true, this._optionsService, this._bufferService);
this._normal.fillViewportRows();
// The alt buffer should never have scrollback.
// See http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#h2-The-Alternate-Screen-Buffer
this._alt = new Buffer(false, optionsService, bufferService);
this._alt = new Buffer(false, this._optionsService, this._bufferService);
this._activeBuffer = this._normal;
this.setupTabStops();
+1
View File
@@ -56,6 +56,7 @@ export interface IBufferSet extends IDisposable {
activateNormalBuffer(): void;
activateAltBuffer(fillAttr?: IAttributeData): void;
reset(): void;
resize(newCols: number, newRows: number): void;
setupTabStops(i?: number): void;
}
+1 -1
View File
@@ -356,7 +356,7 @@ export function evaluateKeyboardEvent(
const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32;
result.key = C0.ESC + String.fromCharCode(keyCode);
}
} else if (isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) {
} else if (isMac && !ev.altKey && !ev.ctrlKey && !ev.shiftKey && ev.metaKey) {
if (ev.keyCode === 65) { // cmd + a
result.type = KeyboardResultType.SELECT_ALL;
}
+1 -2
View File
@@ -49,8 +49,7 @@ export class BufferService extends Disposable implements IBufferService {
}
public reset(): void {
this.buffers.dispose();
this.buffers = new BufferSet(this._optionsService, this);
this.buffers.reset();
this.isUserScrolling = false;
}
}