Merge branch 'feat/serialize-addon' into feat/serialize-addon

This commit is contained in:
Daniel Imms
2020-02-03 11:54:11 -08:00
committed by GitHub
41 changed files with 1291 additions and 849 deletions
+15 -15
View File
@@ -5,28 +5,28 @@
import { IDisposable } from 'common/Types';
interface IListener<T> {
(e: T): void;
interface IListener<T, U = void> {
(arg1: T, arg2: U): void;
}
export interface IEvent<T> {
(listener: (e: T) => any): IDisposable;
export interface IEvent<T, U = void> {
(listener: (arg1: T, arg2: U) => any): IDisposable;
}
export interface IEventEmitter<T> {
event: IEvent<T>;
fire(data: T): void;
export interface IEventEmitter<T, U = void> {
event: IEvent<T, U>;
fire(arg1: T, arg2: U): void;
dispose(): void;
}
export class EventEmitter<T> implements IEventEmitter<T> {
private _listeners: IListener<T>[] = [];
private _event?: IEvent<T>;
export class EventEmitter<T, U = void> implements IEventEmitter<T, U> {
private _listeners: IListener<T, U>[] = [];
private _event?: IEvent<T, U>;
private _disposed: boolean = false;
public get event(): IEvent<T> {
public get event(): IEvent<T, U> {
if (!this._event) {
this._event = (listener: (e: T) => any) => {
this._event = (listener: (arg1: T, arg2: U) => any) => {
this._listeners.push(listener);
const disposable = {
dispose: () => {
@@ -46,13 +46,13 @@ export class EventEmitter<T> implements IEventEmitter<T> {
return this._event;
}
public fire(data: T): void {
const queue: IListener<T>[] = [];
public fire(arg1: T, arg2: U): void {
const queue: IListener<T, U>[] = [];
for (let i = 0; i < this._listeners.length; i++) {
queue.push(this._listeners[i]);
}
for (let i = 0; i < queue.length; i++) {
queue[i].call(undefined, data);
queue[i].call(undefined, arg1, arg2);
}
}
+19 -4
View File
@@ -3,13 +3,13 @@
* @license MIT
*/
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService } from 'common/services/Services';
import { IBufferService, ICoreService, ILogService, IOptionsService, ITerminalOptions, IPartialTerminalOptions, IDirtyRowService, ICoreMouseService, ICharsetService } from 'common/services/Services';
import { IEvent, EventEmitter } from 'common/EventEmitter';
import { clone } from 'common/Clone';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { BufferSet } from 'common/buffer/BufferSet';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEventType, ICharset } from 'common/Types';
export class MockBufferService implements IBufferService {
serviceBrand: any;
@@ -42,12 +42,27 @@ export class MockCoreMouseService implements ICoreMouseService {
}
}
export class MockCharsetService implements ICharsetService {
serviceBrand: any;
charset: ICharset | undefined;
glevel: number = 0;
charsets: readonly ICharset[] = [];
reset(): void {}
setgLevel(g: number): void {}
setgCharset(g: number, charset: ICharset): void {}
}
export class MockCoreService implements ICoreService {
serviceBrand: any;
isCursorInitialized: boolean = false;
isCursorHidden: boolean = false;
isFocused: boolean = false;
serviceBrand: any;
decPrivateModes: IDecPrivateModes = {} as any;
decPrivateModes: IDecPrivateModes = {
applicationCursorKeys: false,
applicationKeypad: false,
origin: false,
wraparound: true
};
onData: IEvent<string> = new EventEmitter<string>().event;
onUserInput: IEvent<void> = new EventEmitter<void>().event;
onBinary: IEvent<string> = new EventEmitter<string>().event;
+7 -4
View File
@@ -62,7 +62,7 @@ export interface IKeyboardResult {
}
export interface ICharset {
[key: string]: string;
[key: string]: string | undefined;
}
export type CharData = [number, string, number, number];
@@ -124,9 +124,9 @@ export interface IBufferLine {
setCell(index: number, cell: ICellData): void;
setCellFromCodePoint(index: number, codePoint: number, width: number, fg: number, bg: number): void;
addCodepointToCell(index: number, codePoint: number): void;
insertCells(pos: number, n: number, ch: ICellData): void;
deleteCells(pos: number, n: number, fill: ICellData): void;
replaceCells(start: number, end: number, fill: ICellData): void;
insertCells(pos: number, n: number, ch: ICellData, eraseAttr?: IAttributeData): void;
deleteCells(pos: number, n: number, fill: ICellData, eraseAttr?: IAttributeData): void;
replaceCells(start: number, end: number, fill: ICellData, eraseAttr?: IAttributeData): void;
resize(cols: number, fill: ICellData): void;
fill(fillCellData: ICellData): void;
copyFrom(line: IBufferLine): void;
@@ -153,6 +153,9 @@ export interface IMarker extends IDisposable {
export interface IDecPrivateModes {
applicationCursorKeys: boolean;
applicationKeypad: boolean;
origin: boolean;
wraparound: boolean; // defaults: xterm - true, vt100 - false
}
export interface IRowRange {
+1 -1
View File
@@ -6,7 +6,7 @@
import { CHAR_DATA_CODE_INDEX, NULL_CELL_CODE, WHITESPACE_CELL_CODE } from 'common/buffer/Constants';
import { IBufferService } from 'common/services/Services';
export function handleWindowsModeLineFeed(bufferService: IBufferService): void {
export function updateWindowsModeWrappedState(bufferService: IBufferService): void {
// Winpty does not support wraparound mode which means that lines will never
// be marked as wrapped. This causes issues for things like copying a line
// retaining the wrapped new line characters or if consumers are listening
+90
View File
@@ -366,4 +366,94 @@ describe('BufferLine', function(): void {
chai.assert.equal(cell.isCombined(), Content.IS_COMBINED_MASK);
});
});
describe('correct fullwidth handling', () => {
function populate(line: BufferLine): void {
const cell = CellData.fromCharData([1, '¥', 2, '¥'.charCodeAt(0)]);
for (let i = 0; i < line.length; i += 2) {
line.setCell(i, cell);
}
}
it('insert - wide char at pos', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(9, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥ a');
line.insertCells(8, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), '¥¥¥¥a ');
line.insertCells(1, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a ¥¥¥a');
});
it('insert - wide char at end', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.insertCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa¥¥¥ ');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa a ¥¥');
line.insertCells(4, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa aa ¥ ');
});
it('delete', () => {
const line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.deleteCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥¥a');
line.deleteCells(5, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥¥aaa');
line.deleteCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' ¥¥aaaaa');
});
it('replace - start at 0', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 1, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'a ¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aa¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(0, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), 'aaaaaaaaaa');
});
it('replace - start at 1', () => {
let line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 2, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' a¥¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 3, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aa ¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 4, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaa¥¥¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 8, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaa¥');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 9, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaa ');
line = new TestBufferLine(10, CellData.fromCharData([DEFAULT_ATTR, NULL_CELL_CHAR, 0, NULL_CELL_CODE]), false);
populate(line);
line.replaceCells(1, 10, CellData.fromCharData([1, 'a', 1, 'a'.charCodeAt(0)]));
chai.assert.equal(line.translateToString(), ' aaaaaaaaa');
});
});
});
+34 -4
View File
@@ -3,7 +3,7 @@
* @license MIT
*/
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { CharData, IBufferLine, ICellData, IAttributeData } from 'common/Types';
import { stringFromCodePoint } from 'common/input/TextDecoder';
import { CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, CHAR_DATA_ATTR_INDEX, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, WHITESPACE_CELL_CHAR, Content } from 'common/buffer/Constants';
import { CellData } from 'common/buffer/CellData';
@@ -228,8 +228,14 @@ export class BufferLine implements IBufferLine {
}
}
public insertCells(pos: number, n: number, fillCellData: ICellData): void {
public insertCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
pos %= this.length;
// handle fullwidth at pos: reset cell one to the left if pos is second cell of a wide char
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
if (n < this.length - pos) {
const cell = new CellData();
for (let i = this.length - pos - n - 1; i >= 0; --i) {
@@ -243,9 +249,14 @@ export class BufferLine implements IBufferLine {
this.setCell(i, fillCellData);
}
}
// handle fullwidth at line end: reset last cell if it is first cell of a wide char
if (this.getWidth(this.length - 1) === 2) {
this.setCellFromCodePoint(this.length - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
}
public deleteCells(pos: number, n: number, fillCellData: ICellData): void {
public deleteCells(pos: number, n: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
pos %= this.length;
if (n < this.length - pos) {
const cell = new CellData();
@@ -260,9 +271,28 @@ export class BufferLine implements IBufferLine {
this.setCell(i, fillCellData);
}
}
// handle fullwidth at pos:
// - reset pos-1 if wide char
// - reset pos if width==0 (previous second cell of a wide char)
if (pos && this.getWidth(pos - 1) === 2) {
this.setCellFromCodePoint(pos - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
if (this.getWidth(pos) === 0 && !this.hasContent(pos)) {
this.setCellFromCodePoint(pos, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
}
public replaceCells(start: number, end: number, fillCellData: ICellData): void {
public replaceCells(start: number, end: number, fillCellData: ICellData, eraseAttr?: IAttributeData): void {
// handle fullwidth at start: reset cell one to the left if start is second cell of a wide char
if (start && this.getWidth(start - 1) === 2) {
this.setCellFromCodePoint(start - 1, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
// handle fullwidth at last cell + 1: reset to empty cell if it is second part of a wide char
if (end < this.length && this.getWidth(end - 1) === 2) {
this.setCellFromCodePoint(end, 0, 1, eraseAttr?.fg || 0, eraseAttr?.bg || 0);
}
while (start < end && start < this.length) {
this.setCell(start++, fillCellData);
}
+33
View File
@@ -0,0 +1,33 @@
/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { ICharsetService } from 'common/services/Services';
import { ICharset } from 'common/Types';
export class CharsetService implements ICharsetService {
serviceBrand: any;
public charset: ICharset | undefined;
public charsets: ICharset[] = [];
public glevel: number = 0;
public reset(): void {
this.charset = undefined;
this.charsets = [];
this.glevel = 0;
}
public setgLevel(g: number): void {
this.glevel = g;
this.charset = this.charsets[g];
}
public setgCharset(g: number, charset: ICharset): void {
this.charsets[g] = charset;
if (this.glevel === g) {
this.charset = charset;
}
}
}
+5 -2
View File
@@ -5,11 +5,14 @@
import { ICoreService, ILogService, IOptionsService, IBufferService } from 'common/services/Services';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { IDecPrivateModes } from 'common/Types';
import { IDecPrivateModes, ICharset } from 'common/Types';
import { clone } from 'common/Clone';
const DEFAULT_DEC_PRIVATE_MODES: IDecPrivateModes = Object.freeze({
applicationCursorKeys: false
applicationCursorKeys: false,
applicationKeypad: false,
origin: false,
wraparound: true // defaults: xterm - true, vt100 - false
});
export class CoreService implements ICoreService {
+5
View File
@@ -20,6 +20,7 @@ export const DEFAULT_OPTIONS: ITerminalOptions = Object.freeze({
rows: 24,
cursorBlink: false,
cursorStyle: 'block',
cursorWidth: 1,
bellSound: DEFAULT_BELL_SOUND,
bellStyle: 'none',
drawBoldTextInBrightColors: true,
@@ -111,6 +112,9 @@ export class OptionsService implements IOptionsService {
value = DEFAULT_OPTIONS[key];
}
break;
case 'cursorWidth':
value = Math.floor(value);
// Fall through for bounds check
case 'lineHeight':
case 'tabStopWidth':
if (value < 1) {
@@ -119,6 +123,7 @@ export class OptionsService implements IOptionsService {
break;
case 'minimumContrastRatio':
value = Math.max(1, Math.min(21, Math.round(value * 10) / 10));
break;
case 'scrollback':
value = Math.min(value, 4294967295);
if (value < 0) {
+28 -1
View File
@@ -5,7 +5,7 @@
import { IEvent } from 'common/EventEmitter';
import { IBuffer, IBufferSet } from 'common/buffer/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType } from 'common/Types';
import { IDecPrivateModes, ICoreMouseEvent, CoreMouseEncoding, ICoreMouseProtocol, CoreMouseEventType, ICharset } from 'common/Types';
import { createDecorator } from 'common/services/ServiceRegistry';
export const IBufferService = createDecorator<IBufferService>('BufferService');
@@ -64,6 +64,7 @@ export interface ICoreService {
*/
isCursorInitialized: boolean;
isCursorHidden: boolean;
readonly decPrivateModes: IDecPrivateModes;
readonly onData: IEvent<string>;
@@ -89,6 +90,30 @@ export interface ICoreService {
triggerBinaryEvent(data: string): void;
}
export const ICharsetService = createDecorator<ICharsetService>('CharsetService');
export interface ICharsetService {
serviceBrand: any;
charset: ICharset | undefined;
readonly glevel: number;
readonly charsets: ReadonlyArray<ICharset>;
reset(): void;
/**
* Set the G level of the terminal.
* @param g
*/
setgLevel(g: number): void;
/**
* Set the charset for the given G level of the terminal.
* @param g
* @param charset
*/
setgCharset(g: number, charset: ICharset): void;
}
export const IDirtyRowService = createDecorator<IDirtyRowService>('DirtyRowService');
export interface IDirtyRowService {
serviceBrand: any;
@@ -191,6 +216,7 @@ export interface IPartialTerminalOptions {
cols?: number;
cursorBlink?: boolean;
cursorStyle?: 'block' | 'underline' | 'bar';
cursorWidth?: number;
disableStdin?: boolean;
drawBoldTextInBrightColors?: boolean;
fastScrollModifier?: 'alt' | 'ctrl' | 'shift';
@@ -223,6 +249,7 @@ export interface ITerminalOptions {
cols: number;
cursorBlink: boolean;
cursorStyle: 'block' | 'underline' | 'bar';
cursorWidth: number;
disableStdin: boolean;
drawBoldTextInBrightColors: boolean;
fastScrollModifier: 'alt' | 'ctrl' | 'shift' | undefined;