mirror of
https://github.com/wavetermdev/xterm.js.git
synced 2026-08-05 13:43:48 -07:00
Merge branch 'master' into 622_reflow3
This commit is contained in:
+1
-2
@@ -205,8 +205,7 @@ function initOptions(term: TerminalType): void {
|
||||
fontFamily: null,
|
||||
fontWeight: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
fontWeightBold: ['normal', 'bold', '100', '200', '300', '400', '500', '600', '700', '800', '900'],
|
||||
rendererType: ['dom', 'canvas'],
|
||||
experimentalBufferLineImpl: ['JsArray', 'TypedArray']
|
||||
rendererType: ['dom', 'canvas']
|
||||
};
|
||||
const options = Object.keys((<any>term)._core.options);
|
||||
const booleanOptions = [];
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
/// <reference path="../../typings/xterm.d.ts" />
|
||||
|
||||
import { Terminal } from 'xterm';
|
||||
import { Terminal, IDisposable } from 'xterm';
|
||||
|
||||
namespace constructor {
|
||||
{
|
||||
@@ -119,6 +119,12 @@ namespace methods_core {
|
||||
const t: Terminal = new Terminal();
|
||||
t.attachCustomKeyEventHandler((e: KeyboardEvent) => true);
|
||||
t.attachCustomKeyEventHandler((e: KeyboardEvent) => false);
|
||||
const d1: IDisposable = t.addCsiHandler("x",
|
||||
(params: number[], collect: string): boolean => params[0]===1);
|
||||
d1.dispose();
|
||||
const d2: IDisposable = t.addOscHandler(199,
|
||||
(data: string): boolean => true);
|
||||
d2.dispose();
|
||||
}
|
||||
namespace options {
|
||||
{
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "xterm",
|
||||
"description": "Full xterm terminal, in your browser",
|
||||
"version": "3.9.0",
|
||||
"version": "3.10.0",
|
||||
"main": "lib/public/Terminal.js",
|
||||
"types": "typings/xterm.d.ts",
|
||||
"repository": "https://github.com/xtermjs/xterm.js",
|
||||
|
||||
+4
-32
@@ -4,10 +4,10 @@
|
||||
*/
|
||||
|
||||
import { CircularList, IInsertEvent, IDeleteEvent } from './common/CircularList';
|
||||
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult, IBufferLineConstructor } from './Types';
|
||||
import { CharData, ITerminal, IBuffer, IBufferLine, BufferIndex, IBufferStringIterator, IBufferStringIteratorResult } from './Types';
|
||||
import { EventEmitter } from './common/EventEmitter';
|
||||
import { IMarker } from 'xterm';
|
||||
import { BufferLine, BufferLineJSArray } from './BufferLine';
|
||||
import { BufferLine } from './BufferLine';
|
||||
import { DEFAULT_COLOR } from './renderer/atlas/Types';
|
||||
|
||||
export const DEFAULT_ATTR = (0 << 18) | (DEFAULT_COLOR << 9) | (256 << 0);
|
||||
@@ -47,7 +47,6 @@ export class Buffer implements IBuffer {
|
||||
public savedX: number;
|
||||
public savedCurAttr: number;
|
||||
public markers: Marker[] = [];
|
||||
private _bufferLineConstructor: IBufferLineConstructor;
|
||||
private _cols: number;
|
||||
private _rows: number;
|
||||
|
||||
@@ -66,35 +65,9 @@ export class Buffer implements IBuffer {
|
||||
this.clear();
|
||||
}
|
||||
|
||||
public setBufferLineFactory(type: string): void {
|
||||
if (type === 'JsArray') {
|
||||
if (this._bufferLineConstructor !== BufferLineJSArray) {
|
||||
this._bufferLineConstructor = BufferLineJSArray;
|
||||
this._recreateLines();
|
||||
}
|
||||
} else {
|
||||
if (this._bufferLineConstructor !== BufferLine) {
|
||||
this._bufferLineConstructor = BufferLine;
|
||||
this._recreateLines();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private _recreateLines(): void {
|
||||
if (!this.lines) return;
|
||||
for (let i = 0; i < this.lines.length; ++i) {
|
||||
const oldLine = this.lines.get(i);
|
||||
const newLine = new this._bufferLineConstructor(oldLine.length);
|
||||
for (let j = 0; j < oldLine.length; ++j) {
|
||||
newLine.set(j, oldLine.get(j));
|
||||
}
|
||||
this.lines.set(i, newLine);
|
||||
}
|
||||
}
|
||||
|
||||
public getBlankLine(attr: number, isWrapped?: boolean): IBufferLine {
|
||||
const fillCharData: CharData = [attr, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
||||
return new this._bufferLineConstructor(this._cols, fillCharData, isWrapped);
|
||||
return new BufferLine(this._cols, fillCharData, isWrapped);
|
||||
}
|
||||
|
||||
public get hasScrollback(): boolean {
|
||||
@@ -141,7 +114,6 @@ export class Buffer implements IBuffer {
|
||||
* Clears the buffer to it's initial state, discarding all previous data.
|
||||
*/
|
||||
public clear(): void {
|
||||
this.setBufferLineFactory(this._terminal.options.experimentalBufferLineImpl);
|
||||
this.ydisp = 0;
|
||||
this.ybase = 0;
|
||||
this.y = 0;
|
||||
@@ -192,7 +164,7 @@ export class Buffer implements IBuffer {
|
||||
} else {
|
||||
// Add a blank line if there is no buffer left at the top to scroll to, or if there
|
||||
// are blank lines after the cursor
|
||||
this.lines.push(new this._bufferLineConstructor(newCols, FILL_CHAR_DATA));
|
||||
this.lines.push(new BufferLine(newCols, FILL_CHAR_DATA));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+1
-126
@@ -3,133 +3,8 @@
|
||||
* @license MIT
|
||||
*/
|
||||
import { CharData, IBufferLine } from './Types';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, CHAR_DATA_CHAR_INDEX, CHAR_DATA_WIDTH_INDEX, WHITESPACE_CELL_CHAR } from './Buffer';
|
||||
import { NULL_CELL_CODE, NULL_CELL_WIDTH, NULL_CELL_CHAR, WHITESPACE_CELL_CHAR } from './Buffer';
|
||||
|
||||
/**
|
||||
* Class representing a terminal line.
|
||||
*
|
||||
* @deprecated to be removed with one of the next releases
|
||||
*/
|
||||
export class BufferLineJSArray implements IBufferLine {
|
||||
protected _data: CharData[];
|
||||
public isWrapped = false;
|
||||
public length: number;
|
||||
|
||||
constructor(cols: number, fillCharData?: CharData, isWrapped?: boolean) {
|
||||
this._data = [];
|
||||
if (!fillCharData) {
|
||||
fillCharData = [0, NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE];
|
||||
}
|
||||
for (let i = 0; i < cols; i++) {
|
||||
this._push(fillCharData); // Note: the ctor ch is not cloned (resembles old behavior)
|
||||
}
|
||||
if (isWrapped) {
|
||||
this.isWrapped = true;
|
||||
}
|
||||
this.length = this._data.length;
|
||||
}
|
||||
|
||||
private _pop(): CharData | undefined {
|
||||
const data = this._data.pop();
|
||||
this.length = this._data.length;
|
||||
return data;
|
||||
}
|
||||
|
||||
private _push(data: CharData): void {
|
||||
this._data.push(data);
|
||||
this.length = this._data.length;
|
||||
}
|
||||
|
||||
private _splice(start: number, deleteCount: number, ...items: CharData[]): CharData[] {
|
||||
const removed = this._data.splice(start, deleteCount, ...items);
|
||||
this.length = this._data.length;
|
||||
return removed;
|
||||
}
|
||||
|
||||
public get(index: number): CharData {
|
||||
return this._data[index];
|
||||
}
|
||||
|
||||
public set(index: number, data: CharData): void {
|
||||
this._data[index] = data;
|
||||
}
|
||||
|
||||
/** insert n cells ch at pos, right cells are lost (stable length) */
|
||||
public insertCells(pos: number, n: number, ch: CharData): void {
|
||||
while (n--) {
|
||||
this._splice(pos, 0, ch);
|
||||
this._pop();
|
||||
}
|
||||
}
|
||||
|
||||
/** delete n cells at pos, right side is filled with fill (stable length) */
|
||||
public deleteCells(pos: number, n: number, fillCharData: CharData): void {
|
||||
while (n--) {
|
||||
this._splice(pos, 1);
|
||||
this._push(fillCharData);
|
||||
}
|
||||
}
|
||||
|
||||
/** replace cells from pos to pos + n - 1 with fill */
|
||||
public replaceCells(start: number, end: number, fillCharData: CharData): void {
|
||||
while (start < end && start < this.length) {
|
||||
this.set(start++, fillCharData); // Note: fill is not cloned (resembles old behavior)
|
||||
}
|
||||
}
|
||||
|
||||
/** resize line to cols filling new cells with fill */
|
||||
public resize(cols: number, fillCharData: CharData, shrink: boolean = false): void {
|
||||
while (this._data.length < cols) {
|
||||
this._data.push(fillCharData);
|
||||
}
|
||||
if (shrink) {
|
||||
while (this._data.length > cols) {
|
||||
this._data.pop();
|
||||
}
|
||||
}
|
||||
this.length = this._data.length;
|
||||
}
|
||||
|
||||
public fill(fillCharData: CharData): void {
|
||||
for (let i = 0; i < this.length; ++i) {
|
||||
this.set(i, fillCharData);
|
||||
}
|
||||
}
|
||||
|
||||
public copyFrom(line: BufferLineJSArray): void {
|
||||
this._data = line._data.slice(0);
|
||||
this.length = line.length;
|
||||
this.isWrapped = line.isWrapped;
|
||||
}
|
||||
|
||||
public clone(): IBufferLine {
|
||||
const newLine = new BufferLineJSArray(0);
|
||||
newLine.copyFrom(this);
|
||||
return newLine;
|
||||
}
|
||||
|
||||
public getTrimmedLength(): number {
|
||||
for (let i = this.length - 1; i >= 0; --i) {
|
||||
const ch = this.get(i);
|
||||
if (ch[CHAR_DATA_CHAR_INDEX] !== '') {
|
||||
return i + ch[CHAR_DATA_WIDTH_INDEX];
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public translateToString(trimRight: boolean = false, startCol: number = 0, endCol: number = this.length): string {
|
||||
if (trimRight) {
|
||||
endCol = Math.min(endCol, this.getTrimmedLength());
|
||||
}
|
||||
let result = '';
|
||||
while (startCol < endCol) {
|
||||
result += this.get(startCol)[CHAR_DATA_CHAR_INDEX] || WHITESPACE_CELL_CHAR;
|
||||
startCol += this.get(startCol)[CHAR_DATA_WIDTH_INDEX] || 1;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/** typed array slots taken by one cell */
|
||||
const CELL_SIZE = 3;
|
||||
|
||||
@@ -1169,6 +1169,73 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([]);
|
||||
});
|
||||
describe('CSI custom handlers', () => {
|
||||
it('Prevent fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return false; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']], 'Should fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
const csiCustom2: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return false; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
const csiCustom2: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
parser2.addCsiHandler('m', (params, collect) => { csiCustom2.push(['m', params, collect]); return true; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(csiCustom).eql([], 'Should not fallback once');
|
||||
chai.expect(csiCustom2).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setCsiHandler('m', () => order.push(1));
|
||||
parser2.addCsiHandler('m', () => { order.push(2); return false; });
|
||||
parser2.addCsiHandler('m', () => { order.push(3); return false; });
|
||||
parser2.parse('\x1b[0m');
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
customHandler.dispose();
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const csiCustom: [string, number[], string][] = [];
|
||||
parser2.setCsiHandler('m', (params, collect) => csi.push(['m', params, collect]));
|
||||
const customHandler = parser2.addCsiHandler('m', (params, collect) => { csiCustom.push(['m', params, collect]); return true; });
|
||||
customHandler.dispose();
|
||||
customHandler.dispose();
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(csi).eql([['m', [1, 31], ''], ['m', [0], '']]);
|
||||
chai.expect(csiCustom).eql([], 'Should not use custom handler as it was disposed');
|
||||
});
|
||||
});
|
||||
it('EXECUTE handler', function (): void {
|
||||
parser2.setExecuteHandler('\n', function (): void {
|
||||
exe.push('\n');
|
||||
@@ -1196,6 +1263,73 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([]);
|
||||
});
|
||||
describe('OSC custom handlers', () => {
|
||||
it('Prevent fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
});
|
||||
it('Allow fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return false; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']], 'Should fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
});
|
||||
it('Multiple custom handlers fallback once', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
const oscCustom2: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return false; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([[1, 'foo=bar']]);
|
||||
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
|
||||
});
|
||||
it('Multiple custom handlers no fallback', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
const oscCustom2: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
parser2.addOscHandler(1, data => { oscCustom2.push([1, data]); return true; });
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([], 'Should not fallback to original handler');
|
||||
chai.expect(oscCustom).eql([], 'Should not fallback once');
|
||||
chai.expect(oscCustom2).eql([[1, 'foo=bar']]);
|
||||
});
|
||||
it('Execution order should go from latest handler down to the original', () => {
|
||||
const order: number[] = [];
|
||||
parser2.setOscHandler(1, () => order.push(1));
|
||||
parser2.addOscHandler(1, () => { order.push(2); return false; });
|
||||
parser2.addOscHandler(1, () => { order.push(3); return false; });
|
||||
parser2.parse('\x1b]1;foo=bar\x1b\\');
|
||||
chai.expect(order).eql([3, 2, 1]);
|
||||
});
|
||||
it('Dispose should work', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
customHandler.dispose();
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
|
||||
});
|
||||
it('Should not corrupt the parser when dispose is called twice', () => {
|
||||
const oscCustom: [number, string][] = [];
|
||||
parser2.setOscHandler(1, data => osc.push([1, data]));
|
||||
const customHandler = parser2.addOscHandler(1, data => { oscCustom.push([1, data]); return true; });
|
||||
customHandler.dispose();
|
||||
customHandler.dispose();
|
||||
parser2.parse(INPUT);
|
||||
chai.expect(osc).eql([[1, 'foo=bar']]);
|
||||
chai.expect(oscCustom).eql([], 'Should not use custom handler as it was disposed');
|
||||
});
|
||||
});
|
||||
it('DCS handler', function (): void {
|
||||
parser2.setDcsHandler('+p', {
|
||||
hook: function (collect: string, params: number[], flag: number): void {
|
||||
|
||||
+66
-11
@@ -4,8 +4,16 @@
|
||||
*/
|
||||
|
||||
import { ParserState, ParserAction, IParsingState, IDcsHandler, IEscapeSequenceParser } from './Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { Disposable } from './common/Lifecycle';
|
||||
|
||||
interface IHandlerCollection<T> {
|
||||
[key: string]: T[];
|
||||
}
|
||||
|
||||
type CsiHandler = (params: number[], collect: string) => boolean | void;
|
||||
type OscHandler = (data: string) => boolean | void;
|
||||
|
||||
/**
|
||||
* Returns an array filled with numbers between the low and high parameters (right exclusive).
|
||||
* @param low The low number.
|
||||
@@ -222,9 +230,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
// handler lookup containers
|
||||
protected _printHandler: (data: string, start: number, end: number) => void;
|
||||
protected _executeHandlers: any;
|
||||
protected _csiHandlers: any;
|
||||
protected _csiHandlers: IHandlerCollection<CsiHandler>;
|
||||
protected _escHandlers: any;
|
||||
protected _oscHandlers: any;
|
||||
protected _oscHandlers: IHandlerCollection<OscHandler>;
|
||||
protected _dcsHandlers: any;
|
||||
protected _activeDcsHandler: IDcsHandler | null;
|
||||
protected _errorHandler: (state: IParsingState) => IParsingState;
|
||||
@@ -278,8 +286,8 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._errorHandlerFb = null;
|
||||
this._printHandler = null;
|
||||
this._executeHandlers = null;
|
||||
this._csiHandlers = null;
|
||||
this._escHandlers = null;
|
||||
this._csiHandlers = null;
|
||||
this._oscHandlers = null;
|
||||
this._dcsHandlers = null;
|
||||
this._activeDcsHandler = null;
|
||||
@@ -303,8 +311,24 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._executeHandlerFb = callback;
|
||||
}
|
||||
|
||||
addCsiHandler(flag: string, callback: CsiHandler): IDisposable {
|
||||
const index = flag.charCodeAt(0);
|
||||
if (this._csiHandlers[index] === undefined) {
|
||||
this._csiHandlers[index] = [];
|
||||
}
|
||||
const handlerList = this._csiHandlers[index];
|
||||
handlerList.push(callback);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(callback);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void {
|
||||
this._csiHandlers[flag.charCodeAt(0)] = callback;
|
||||
this._csiHandlers[flag.charCodeAt(0)] = [callback];
|
||||
}
|
||||
clearCsiHandler(flag: string): void {
|
||||
if (this._csiHandlers[flag.charCodeAt(0)]) delete this._csiHandlers[flag.charCodeAt(0)];
|
||||
@@ -323,8 +347,23 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._escHandlerFb = callback;
|
||||
}
|
||||
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
if (this._oscHandlers[ident] === undefined) {
|
||||
this._oscHandlers[ident] = [];
|
||||
}
|
||||
const handlerList = this._oscHandlers[ident];
|
||||
handlerList.push(callback);
|
||||
return {
|
||||
dispose: () => {
|
||||
const handlerIndex = handlerList.indexOf(callback);
|
||||
if (handlerIndex !== -1) {
|
||||
handlerList.splice(handlerIndex, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
setOscHandler(ident: number, callback: (data: string) => void): void {
|
||||
this._oscHandlers[ident] = callback;
|
||||
this._oscHandlers[ident] = [callback];
|
||||
}
|
||||
clearOscHandler(ident: number): void {
|
||||
if (this._oscHandlers[ident]) delete this._oscHandlers[ident];
|
||||
@@ -461,9 +500,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
}
|
||||
break;
|
||||
case ParserAction.CSI_DISPATCH:
|
||||
callback = this._csiHandlers[code];
|
||||
if (callback) callback(params, collect);
|
||||
else this._csiHandlerFb(collect, params, code);
|
||||
// Trigger CSI Handler
|
||||
const handlers = this._csiHandlers[code];
|
||||
let j = handlers ? handlers.length - 1 : -1;
|
||||
for (; j >= 0; j--) {
|
||||
if (handlers[j](params, collect)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < 0) {
|
||||
this._csiHandlerFb(collect, params, code);
|
||||
}
|
||||
break;
|
||||
case ParserAction.PARAM:
|
||||
if (code === 0x3b) params.push(0);
|
||||
@@ -538,9 +585,17 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
// or with an explicit NaN OSC handler
|
||||
const identifier = parseInt(osc.substring(0, idx));
|
||||
const content = osc.substring(idx + 1);
|
||||
callback = this._oscHandlers[identifier];
|
||||
if (callback) callback(content);
|
||||
else this._oscHandlerFb(identifier, content);
|
||||
// Trigger OSC Handler
|
||||
const handlers = this._oscHandlers[identifier];
|
||||
let j = handlers ? handlers.length - 1 : -1;
|
||||
for (; j >= 0; j--) {
|
||||
if (handlers[j](content)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (j < 0) {
|
||||
this._oscHandlerFb(identifier, content);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (code === 0x1b) transition |= ParserState.ESCAPE;
|
||||
|
||||
@@ -12,6 +12,7 @@ import { FLAGS } from './renderer/Types';
|
||||
import { wcwidth } from './CharWidth';
|
||||
import { EscapeSequenceParser } from './EscapeSequenceParser';
|
||||
import { ICharset } from './core/Types';
|
||||
import { IDisposable } from 'xterm';
|
||||
import { Disposable } from './common/Lifecycle';
|
||||
|
||||
/**
|
||||
@@ -465,6 +466,13 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
this._terminal.updateRange(buffer.y);
|
||||
}
|
||||
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
return this._parser.addCsiHandler(flag, callback);
|
||||
}
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._parser.addOscHandler(ident, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* BEL
|
||||
* Bell (Ctrl-G).
|
||||
|
||||
+19
-25
@@ -105,8 +105,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = {
|
||||
tabStopWidth: 8,
|
||||
theme: null,
|
||||
rightClickSelectsWord: Browser.isMac,
|
||||
rendererType: 'canvas',
|
||||
experimentalBufferLineImpl: 'TypedArray'
|
||||
rendererType: 'canvas'
|
||||
};
|
||||
|
||||
export class Terminal extends EventEmitter implements ITerminal, IDisposable, IInputHandlingTerminal {
|
||||
@@ -497,11 +496,6 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
}
|
||||
break;
|
||||
case 'tabStopWidth': this.buffers.setupTabStops(); break;
|
||||
case 'experimentalBufferLineImpl':
|
||||
this.buffers.normal.setBufferLineFactory(value);
|
||||
this.buffers.alt.setBufferLineFactory(value);
|
||||
this._blankLine = null;
|
||||
break;
|
||||
}
|
||||
// Inform renderer of changes
|
||||
if (this.renderer) {
|
||||
@@ -1180,17 +1174,12 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
*/
|
||||
public scroll(isWrapped: boolean = false): void {
|
||||
let newLine: IBufferLine;
|
||||
const useRecycling = this.options.experimentalBufferLineImpl !== 'JsArray';
|
||||
if (useRecycling) {
|
||||
newLine = this._blankLine;
|
||||
if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) {
|
||||
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
|
||||
this._blankLine = newLine;
|
||||
}
|
||||
newLine.isWrapped = isWrapped;
|
||||
} else {
|
||||
newLine = this._blankLine;
|
||||
if (!newLine || newLine.length !== this.cols || newLine.get(0)[CHAR_DATA_ATTR_INDEX] !== this.eraseAttr()) {
|
||||
newLine = this.buffer.getBlankLine(this.eraseAttr(), isWrapped);
|
||||
this._blankLine = newLine;
|
||||
}
|
||||
newLine.isWrapped = isWrapped;
|
||||
|
||||
const topRow = this.buffer.ybase + this.buffer.scrollTop;
|
||||
const bottomRow = this.buffer.ybase + this.buffer.scrollBottom;
|
||||
@@ -1201,17 +1190,13 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
|
||||
// Insert the line using the fastest method
|
||||
if (bottomRow === this.buffer.lines.length - 1) {
|
||||
if (useRecycling) {
|
||||
if (willBufferBeTrimmed) {
|
||||
this.buffer.lines.recycle().copyFrom(newLine);
|
||||
} else {
|
||||
this.buffer.lines.push(newLine.clone());
|
||||
}
|
||||
if (willBufferBeTrimmed) {
|
||||
this.buffer.lines.recycle().copyFrom(newLine);
|
||||
} else {
|
||||
this.buffer.lines.push(newLine);
|
||||
this.buffer.lines.push(newLine.clone());
|
||||
}
|
||||
} else {
|
||||
this.buffer.lines.splice(bottomRow + 1, 0, (useRecycling) ? newLine.clone() : newLine);
|
||||
this.buffer.lines.splice(bottomRow + 1, 0, newLine.clone());
|
||||
}
|
||||
|
||||
// Only adjust ybase and ydisp when the buffer is not trimmed
|
||||
@@ -1233,7 +1218,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
// scrollback, instead we can just shift them in-place.
|
||||
const scrollRegionHeight = bottomRow - topRow + 1/*as it's zero-based*/;
|
||||
this.buffer.lines.shiftElements(topRow + 1, scrollRegionHeight - 1, -1);
|
||||
this.buffer.lines.set(bottomRow, (useRecycling) ? newLine.clone() : newLine);
|
||||
this.buffer.lines.set(bottomRow, newLine.clone());
|
||||
}
|
||||
|
||||
// Move the viewport to the bottom of the buffer unless the user is
|
||||
@@ -1413,6 +1398,15 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
this._customKeyEventHandler = customKeyEventHandler;
|
||||
}
|
||||
|
||||
/** Add handler for CSI escape sequence. See xterm.d.ts for details. */
|
||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
return this._inputHandler.addCsiHandler(flag, callback);
|
||||
}
|
||||
/** Add handler for OSC escape sequence. See xterm.d.ts for details. */
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._inputHandler.addOscHandler(ident, callback);
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a link matcher, allowing custom link patterns to be matched and
|
||||
* handled.
|
||||
|
||||
+2
-4
@@ -492,6 +492,8 @@ export interface IEscapeSequenceParser extends IDisposable {
|
||||
setCsiHandler(flag: string, callback: (params: number[], collect: string) => void): void;
|
||||
clearCsiHandler(flag: string): void;
|
||||
setCsiHandlerFallback(callback: (collect: string, params: number[], flag: number) => void): void;
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
|
||||
setEscHandler(collectAndFlag: string, callback: () => void): void;
|
||||
clearEscHandler(collectAndFlag: string): void;
|
||||
@@ -527,7 +529,3 @@ export interface IBufferLine {
|
||||
getTrimmedLength(): number;
|
||||
translateToString(trimRight?: boolean, startCol?: number, endCol?: number): string;
|
||||
}
|
||||
|
||||
export interface IBufferLineConstructor {
|
||||
new(cols: number, fillCharData?: CharData, isWrapped?: boolean): IBufferLine;
|
||||
}
|
||||
|
||||
@@ -59,6 +59,12 @@ export class Terminal implements ITerminalApi {
|
||||
public attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
this._core.attachCustomKeyEventHandler(customKeyEventHandler);
|
||||
}
|
||||
public addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
return this._core.addCsiHandler(flag, callback);
|
||||
}
|
||||
public addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
return this._core.addOscHandler(ident, callback);
|
||||
}
|
||||
public registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => void, options?: ILinkMatcherOptions): number {
|
||||
return this._core.registerLinkMatcher(regex, handler, options);
|
||||
}
|
||||
|
||||
@@ -236,12 +236,12 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
*/
|
||||
protected fillCharTrueColor(terminal: ITerminal, charData: CharData, x: number, y: number): void {
|
||||
this._ctx.font = this._getFont(terminal, false, false);
|
||||
this._ctx.textBaseline = 'top';
|
||||
this._ctx.textBaseline = 'middle';
|
||||
this._clipRow(terminal, y);
|
||||
this._ctx.fillText(
|
||||
charData[CHAR_DATA_CHAR_INDEX],
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop);
|
||||
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -295,7 +295,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
private _drawUncachedChars(terminal: ITerminal, chars: string, width: number, fg: number, x: number, y: number, bold: boolean, dim: boolean, italic: boolean): void {
|
||||
this._ctx.save();
|
||||
this._ctx.font = this._getFont(terminal, bold, italic);
|
||||
this._ctx.textBaseline = 'top';
|
||||
this._ctx.textBaseline = 'middle';
|
||||
|
||||
if (fg === INVERTED_DEFAULT_COLOR) {
|
||||
this._ctx.fillStyle = this._colors.background.css;
|
||||
@@ -316,7 +316,7 @@ export abstract class BaseRenderLayer implements IRenderLayer {
|
||||
this._ctx.fillText(
|
||||
chars,
|
||||
x * this._scaledCellWidth + this._scaledCharLeft,
|
||||
y * this._scaledCellHeight + this._scaledCharTop);
|
||||
(y + 0.5) * this._scaledCellHeight + this._scaledCharTop);
|
||||
this._ctx.restore();
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
||||
ctx.save();
|
||||
ctx.fillStyle = config.colors.foreground.css;
|
||||
ctx.font = getFont(config.fontWeight, config);
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.textBaseline = 'middle';
|
||||
|
||||
// Default color
|
||||
for (let i = 0; i < 256; i++) {
|
||||
@@ -37,7 +37,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, 0, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, 0);
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight / 2);
|
||||
ctx.restore();
|
||||
}
|
||||
// Default color bold
|
||||
@@ -48,7 +48,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
||||
ctx.beginPath();
|
||||
ctx.rect(i * cellWidth, cellHeight, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight);
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, cellHeight * 1.5);
|
||||
ctx.restore();
|
||||
}
|
||||
ctx.restore();
|
||||
@@ -64,7 +64,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
@@ -80,7 +80,7 @@ export function generateStaticCharAtlasTexture(context: Window, canvasFactory: (
|
||||
ctx.rect(i * cellWidth, y, cellWidth, cellHeight);
|
||||
ctx.clip();
|
||||
ctx.fillStyle = config.colors.ansi[colorIndex].css;
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y);
|
||||
ctx.fillText(String.fromCharCode(i), i * cellWidth, y + cellHeight / 2);
|
||||
ctx.restore();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
const fontStyle = glyph.italic ? 'italic' : '';
|
||||
this._tmpCtx.font =
|
||||
`${fontStyle} ${fontWeight} ${this._config.fontSize * this._config.devicePixelRatio}px ${this._config.fontFamily}`;
|
||||
this._tmpCtx.textBaseline = 'top';
|
||||
this._tmpCtx.textBaseline = 'middle';
|
||||
|
||||
this._tmpCtx.fillStyle = this._getForegroundColor(glyph).css;
|
||||
|
||||
@@ -259,7 +259,7 @@ export default class DynamicCharAtlas extends BaseCharAtlas {
|
||||
this._tmpCtx.globalAlpha = DIM_OPACITY;
|
||||
}
|
||||
// Draw the character
|
||||
this._tmpCtx.fillText(glyph.chars, 0, 0);
|
||||
this._tmpCtx.fillText(glyph.chars, 0, this._config.scaledCharHeight / 2);
|
||||
this._tmpCtx.restore();
|
||||
|
||||
// clear the background from the character to avoid issues with drawing over the previous
|
||||
|
||||
@@ -364,9 +364,11 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
return;
|
||||
}
|
||||
const span = <HTMLElement>row.children[x];
|
||||
span.style.textDecoration = enabled ? 'underline' : 'none';
|
||||
x = (x + 1) % cols;
|
||||
if (x === 0) {
|
||||
if (span) {
|
||||
span.style.textDecoration = enabled ? 'underline' : 'none';
|
||||
}
|
||||
if (++x >= cols) {
|
||||
x = 0;
|
||||
y++;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,6 +54,12 @@ export class MockTerminal implements ITerminal {
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
registerLinkMatcher(regex: RegExp, handler: (event: MouseEvent, uri: string) => boolean | void, options?: ILinkMatcherOptions): number {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
|
||||
Vendored
+23
-11
@@ -101,17 +101,6 @@ declare module 'xterm' {
|
||||
*/
|
||||
experimentalCharAtlas?: 'none' | 'static' | 'dynamic';
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Defines which implementation to use for buffer lines.
|
||||
*
|
||||
* - 'JsArray': The default/stable implementation.
|
||||
* - 'TypedArray': The new experimental implementation based on TypedArrays that is expected to
|
||||
* significantly boost performance and memory consumption. Use at your own risk.
|
||||
*
|
||||
* @deprecated This option will be removed in the future.
|
||||
*/
|
||||
experimentalBufferLineImpl?: 'JsArray' | 'TypedArray';
|
||||
|
||||
/**
|
||||
* The font size used to render text.
|
||||
*/
|
||||
@@ -481,6 +470,29 @@ declare module 'xterm' {
|
||||
*/
|
||||
attachCustomKeyEventHandler(customKeyEventHandler: (event: KeyboardEvent) => boolean): void;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for CSI escape sequences.
|
||||
* @param flag The flag should be one-character string, which specifies the
|
||||
* final character (e.g "m" for SGR) of the CSI sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with the numerical params, as well as the special characters
|
||||
* (e.g. "$" for DECSCPP). Return true if the sequence was handled; false if
|
||||
* we should try a previous handler (set by addCsiHandler or setCsiHandler).
|
||||
* The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addCsiHandler(flag: string, callback: (params: number[], collect: string) => boolean): IDisposable;
|
||||
|
||||
/**
|
||||
* (EXPERIMENTAL) Adds a handler for OSC escape sequences.
|
||||
* @param ident The number (first parameter) of the sequence.
|
||||
* @param callback The function to handle the escape sequence. The callback
|
||||
* is called with OSC data string. Return true if the sequence was handled;
|
||||
* false if we should try a previous handler (set by addOscHandler or
|
||||
* setOscHandler). The most recently-added handler is tried first.
|
||||
* @return An IDisposable you can call to remove this handler.
|
||||
*/
|
||||
addOscHandler(ident: number, callback: (data: string) => boolean): IDisposable;
|
||||
/**
|
||||
* (EXPERIMENTAL) Registers a link matcher, allowing custom link patterns to
|
||||
* be matched and handled.
|
||||
|
||||
Reference in New Issue
Block a user