Merge branch 'master' into run-tests-and-tslint-for-addons-directory
This commit is contained in:
@@ -369,7 +369,8 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.currentState = ParserState.ESCAPE_INTERMEDIATE;
|
||||
parser.parse(collect[i]);
|
||||
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
||||
testTerminal.compare([['esc', '', collect[i]]]);
|
||||
// '\x5c' --> ESC + \ (7bit ST) parser does not expose this as it already got handled
|
||||
testTerminal.compare((collect[i] === '\x5c') ? [] : [['esc', '', collect[i]]]);
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
}
|
||||
@@ -1051,6 +1052,13 @@ describe('EscapeSequenceParser', function (): void {
|
||||
['csi', '<', [0, 0], 'c']
|
||||
], null);
|
||||
});
|
||||
it('7bit ST should be swallowed', function(): void {
|
||||
test('abc\x9d123tzf\x1b\\defg', [
|
||||
['print', 'abc'],
|
||||
['osc', '123tzf'],
|
||||
['print', 'defg']
|
||||
], null);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1089,7 +1097,7 @@ describe('EscapeSequenceParser', function (): void {
|
||||
parser.reset();
|
||||
testTerminal.clear();
|
||||
parser.currentState = ParserState.GROUND;
|
||||
parser.parse('\x1e');
|
||||
parser.parse('\x9c');
|
||||
chai.expect(parser.currentState).equal(ParserState.GROUND);
|
||||
testTerminal.compare([]);
|
||||
parser.reset();
|
||||
|
||||
@@ -66,7 +66,7 @@ export class TransitionTable {
|
||||
const PRINTABLES = r(0x20, 0x7f);
|
||||
const EXECUTABLES = r(0x00, 0x18);
|
||||
EXECUTABLES.push(0x19);
|
||||
EXECUTABLES.concat(r(0x1c, 0x20));
|
||||
EXECUTABLES.push.apply(EXECUTABLES, r(0x1c, 0x20));
|
||||
const DEFAULT_TRANSITION = ParserAction.ERROR << 4 | ParserState.GROUND;
|
||||
|
||||
/**
|
||||
@@ -261,6 +261,9 @@ export class EscapeSequenceParser extends Disposable implements IEscapeSequenceP
|
||||
this._dcsHandlers = Object.create(null);
|
||||
this._activeDcsHandler = null;
|
||||
this._errorHandler = this._errorHandlerFb;
|
||||
|
||||
// swallow 7bit ST (ESC+\)
|
||||
this.setEscHandler('\\', () => {});
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
+22
-1
@@ -24,6 +24,26 @@ const GLEVEL: {[key: string]: number} = {'(': 0, ')': 1, '*': 2, '+': 3, '-': 1,
|
||||
* DCS subparser implementations
|
||||
*/
|
||||
|
||||
/**
|
||||
* DCS + q Pt ST (xterm)
|
||||
* Request Terminfo String
|
||||
* not supported
|
||||
*/
|
||||
class RequestTerminfo implements IDcsHandler {
|
||||
private _data: string;
|
||||
constructor(private _terminal: any) { }
|
||||
hook(collect: string, params: number[], flag: number): void {
|
||||
this._data = '';
|
||||
}
|
||||
put(data: string, start: number, end: number): void {
|
||||
this._data += data.substring(start, end);
|
||||
}
|
||||
unhook(): void {
|
||||
// invalid: DCS 0 + r Pt ST
|
||||
this._terminal.handler(`${C0.ESC}P0+r${this._data}${C0.ESC}\\`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DCS $ q Pt ST
|
||||
* DECRQSS (https://vt100.net/docs/vt510-rm/DECRQSS.html)
|
||||
@@ -66,7 +86,7 @@ class DECRQSS implements IDcsHandler {
|
||||
default:
|
||||
// invalid: DCS 0 $ r Pt ST (xterm)
|
||||
this._terminal.error('Unknown DCS $q %s', this._data);
|
||||
this._terminal.handler(`${C0.ESC}P0$r${C0.ESC}\\`);
|
||||
this._terminal.handler(`${C0.ESC}P0$r${this._data}${C0.ESC}\\`);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -267,6 +287,7 @@ export class InputHandler extends Disposable implements IInputHandler {
|
||||
* DCS handler
|
||||
*/
|
||||
this._parser.setDcsHandler('$q', new DECRQSS(this._terminal));
|
||||
this._parser.setDcsHandler('+q', new RequestTerminfo(this._terminal));
|
||||
}
|
||||
|
||||
public dispose(): void {
|
||||
|
||||
@@ -30,6 +30,7 @@ class TestSelectionManager extends SelectionManager {
|
||||
|
||||
public selectLineAt(line: number): void { this._selectLineAt(line); }
|
||||
public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); }
|
||||
public areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean { return this._areCoordsInSelection(coords, start, end); }
|
||||
|
||||
// Disable DOM interaction
|
||||
public enable(): void {}
|
||||
@@ -478,5 +479,17 @@ describe('SelectionManager', () => {
|
||||
assert.equal(selectionManager.selectionText, 'a\n😁\nc');
|
||||
});
|
||||
});
|
||||
|
||||
describe('_areCoordsInSelection', () => {
|
||||
it('should return whether coords are in the selection', () => {
|
||||
assert.isFalse(selectionManager.areCoordsInSelection([0, 0], [2, 0], [2, 1]));
|
||||
assert.isFalse(selectionManager.areCoordsInSelection([1, 0], [2, 0], [2, 1]));
|
||||
assert.isTrue(selectionManager.areCoordsInSelection([2, 0], [2, 0], [2, 1]));
|
||||
assert.isTrue(selectionManager.areCoordsInSelection([10, 0], [2, 0], [2, 1]));
|
||||
assert.isTrue(selectionManager.areCoordsInSelection([0, 1], [2, 0], [2, 1]));
|
||||
assert.isTrue(selectionManager.areCoordsInSelection([1, 1], [2, 0], [2, 1]));
|
||||
assert.isFalse(selectionManager.areCoordsInSelection([2, 1], [2, 0], [2, 1]));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -289,9 +289,14 @@ export class SelectionManager extends EventEmitter implements ISelectionManager
|
||||
return false;
|
||||
}
|
||||
|
||||
return this._areCoordsInSelection(coords, start, end);
|
||||
}
|
||||
|
||||
protected _areCoordsInSelection(coords: [number, number], start: [number, number], end: [number, number]): boolean {
|
||||
return (coords[1] > start[1] && coords[1] < end[1]) ||
|
||||
(start[1] === end[1] && coords[1] === start[1] && coords[0] > start[0] && coords[0] < end[0]) ||
|
||||
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]);
|
||||
(start[1] === end[1] && coords[1] === start[1] && coords[0] >= start[0] && coords[0] < end[0]) ||
|
||||
(start[1] < end[1] && coords[1] === end[1] && coords[0] < end[0]) ||
|
||||
(start[1] < end[1] && coords[1] === start[1] && coords[0] >= start[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+22
-16
@@ -12,7 +12,19 @@ import { ITerminal, ISoundManager } from './Types';
|
||||
export const DEFAULT_BELL_SOUND = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg==';
|
||||
|
||||
export class SoundManager implements ISoundManager {
|
||||
private _audioContext: AudioContext;
|
||||
private static _audioContext: AudioContext;
|
||||
|
||||
static get audioContext(): AudioContext | null {
|
||||
if (!SoundManager._audioContext) {
|
||||
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||
if (!audioContextCtor) {
|
||||
console.warn('Web Audio API is not supported by this browser. Consider upgrading to the latest version');
|
||||
return null;
|
||||
}
|
||||
SoundManager._audioContext = new audioContextCtor();
|
||||
}
|
||||
return SoundManager._audioContext;
|
||||
}
|
||||
|
||||
constructor(
|
||||
private _terminal: ITerminal
|
||||
@@ -20,22 +32,16 @@ export class SoundManager implements ISoundManager {
|
||||
}
|
||||
|
||||
public playBellSound(): void {
|
||||
const audioContextCtor: typeof AudioContext = (<any>window).AudioContext || (<any>window).webkitAudioContext;
|
||||
if (!this._audioContext && audioContextCtor) {
|
||||
this._audioContext = new audioContextCtor();
|
||||
}
|
||||
|
||||
if (this._audioContext) {
|
||||
const bellAudioSource = this._audioContext.createBufferSource();
|
||||
const context = this._audioContext;
|
||||
this._audioContext.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||
bellAudioSource.buffer = buffer;
|
||||
bellAudioSource.connect(context.destination);
|
||||
bellAudioSource.start(0);
|
||||
});
|
||||
} else {
|
||||
console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version');
|
||||
const ctx = SoundManager.audioContext;
|
||||
if (!ctx) {
|
||||
return;
|
||||
}
|
||||
const bellAudioSource = ctx.createBufferSource();
|
||||
ctx.decodeAudioData(this._base64ToArrayBuffer(this._removeMimeType(this._terminal.options.bellSound)), (buffer) => {
|
||||
bellAudioSource.buffer = buffer;
|
||||
bellAudioSource.connect(ctx.destination);
|
||||
bellAudioSource.start(0);
|
||||
});
|
||||
}
|
||||
|
||||
private _base64ToArrayBuffer(base64: string): ArrayBuffer {
|
||||
|
||||
+2
-1
@@ -473,6 +473,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
if (this._theme) {
|
||||
this.renderer.setTheme(this._theme);
|
||||
}
|
||||
this.mouseHelper.setRenderer(this.renderer);
|
||||
break;
|
||||
case 'scrollback':
|
||||
this.buffers.resize(this.cols, this.rows);
|
||||
@@ -984,7 +985,7 @@ export class Terminal extends EventEmitter implements ITerminal, IDisposable, II
|
||||
: 65;
|
||||
break;
|
||||
case 'wheel':
|
||||
button = (<WheelEvent>ev).wheelDeltaY > 0
|
||||
button = (<WheelEvent>ev).deltaY < 0
|
||||
? 64
|
||||
: 65;
|
||||
break;
|
||||
|
||||
@@ -39,4 +39,28 @@ describe('webLinks addon', () => {
|
||||
|
||||
assert.equal(uri, 'http://foo.com/a~b#c~d?e~f');
|
||||
});
|
||||
|
||||
it('should allow : character in URI path', () => {
|
||||
const term = new MockTerminal();
|
||||
webLinks.webLinksInit(<any>term);
|
||||
|
||||
const row = ' http://foo.com/colon:test ';
|
||||
|
||||
const match = row.match(term.regex);
|
||||
const uri = match[term.options.matchIndex];
|
||||
|
||||
assert.equal(uri, 'http://foo.com/colon:test');
|
||||
});
|
||||
|
||||
it('should not allow : character at the end of a URI path', () => {
|
||||
const term = new MockTerminal();
|
||||
webLinks.webLinksInit(<any>term);
|
||||
|
||||
const row = ' http://foo.com/colon:test: ';
|
||||
|
||||
const match = row.match(term.regex);
|
||||
const uri = match[term.options.matchIndex];
|
||||
|
||||
assert.equal(uri, 'http://foo.com/colon:test');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,7 +14,7 @@ const ipClause = '((\\d{1,3}\\.){3}\\d{1,3})';
|
||||
const localHostClause = '(localhost)';
|
||||
const portClause = '(:\\d{1,5})';
|
||||
const hostClause = '((' + domainBodyClause + '\\.' + tldClause + ')|' + ipClause + '|' + localHostClause + ')' + portClause + '?';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~]*)*';
|
||||
const pathClause = '(\\/[\\/\\w\\.\\-%~:]*)*([^:\\s])';
|
||||
const queryStringHashFragmentCharacterSet = '[0-9\\w\\[\\]\\(\\)\\/\\?\\!#@$%&\'*+,:;~\\=\\.\\-]*';
|
||||
const queryStringClause = '(\\?' + queryStringHashFragmentCharacterSet + ')?';
|
||||
const hashFragmentClause = '(#' + queryStringHashFragmentCharacterSet + ')?';
|
||||
|
||||
@@ -94,22 +94,23 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
}
|
||||
|
||||
private _updateDimensions(): void {
|
||||
this.dimensions.scaledCharWidth = this._terminal.charMeasure.width * window.devicePixelRatio;
|
||||
this.dimensions.scaledCharHeight = this._terminal.charMeasure.height * window.devicePixelRatio;
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth;
|
||||
this.dimensions.scaledCellHeight = this.dimensions.scaledCharHeight;
|
||||
this.dimensions.scaledCharWidth = Math.floor(this._terminal.charMeasure.width * window.devicePixelRatio);
|
||||
this.dimensions.scaledCharHeight = Math.ceil(this._terminal.charMeasure.height * window.devicePixelRatio);
|
||||
this.dimensions.scaledCellWidth = this.dimensions.scaledCharWidth + Math.round(this._terminal.options.letterSpacing);
|
||||
this.dimensions.scaledCellHeight = Math.floor(this.dimensions.scaledCharHeight * this._terminal.options.lineHeight);
|
||||
this.dimensions.scaledCharLeft = 0;
|
||||
this.dimensions.scaledCharTop = 0;
|
||||
this.dimensions.scaledCanvasWidth = this.dimensions.scaledCellWidth * this._terminal.cols;
|
||||
this.dimensions.scaledCanvasHeight = this.dimensions.scaledCellHeight * this._terminal.rows;
|
||||
this.dimensions.canvasWidth = this._terminal.charMeasure.width * this._terminal.cols;
|
||||
this.dimensions.canvasHeight = this._terminal.charMeasure.height * this._terminal.rows;
|
||||
this.dimensions.actualCellWidth = this._terminal.charMeasure.width;
|
||||
this.dimensions.actualCellHeight = this._terminal.charMeasure.height;
|
||||
this.dimensions.canvasWidth = Math.round(this.dimensions.scaledCanvasWidth / window.devicePixelRatio);
|
||||
this.dimensions.canvasHeight = Math.round(this.dimensions.scaledCanvasHeight / window.devicePixelRatio);
|
||||
this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols;
|
||||
this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows;
|
||||
|
||||
this._rowElements.forEach(element => {
|
||||
element.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
element.style.height = `${this._terminal.charMeasure.height}px`;
|
||||
element.style.height = `${this.dimensions.actualCellHeight}px`;
|
||||
element.style.lineHeight = `${this.dimensions.actualCellHeight}px`;
|
||||
});
|
||||
|
||||
if (!this._dimensionsStyleElement) {
|
||||
@@ -122,14 +123,14 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
` display: inline-block;` +
|
||||
` height: 100%;` +
|
||||
` vertical-align: top;` +
|
||||
` width: ${this._terminal.charMeasure.width}px` +
|
||||
` width: ${this.dimensions.actualCellWidth}px` +
|
||||
`}`;
|
||||
|
||||
this._dimensionsStyleElement.innerHTML = styles;
|
||||
|
||||
this._selectionContainer.style.height = (<any>this._terminal)._viewportElement.style.height;
|
||||
this._rowContainer.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._rowContainer.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`;
|
||||
this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`;
|
||||
}
|
||||
|
||||
public setTheme(theme: ITheme | undefined): IColorSet {
|
||||
@@ -290,10 +291,10 @@ export class DomRenderer extends EventEmitter implements IRenderer {
|
||||
*/
|
||||
private _createSelectionElement(row: number, colStart: number, colEnd: number, rowCount: number = 1): HTMLElement {
|
||||
const element = document.createElement('div');
|
||||
element.style.height = `${rowCount * this._terminal.charMeasure.height}px`;
|
||||
element.style.top = `${row * this._terminal.charMeasure.height}px`;
|
||||
element.style.left = `${colStart * this._terminal.charMeasure.width}px`;
|
||||
element.style.width = `${this._terminal.charMeasure.width * (colEnd - colStart)}px`;
|
||||
element.style.height = `${rowCount * this.dimensions.actualCellHeight}px`;
|
||||
element.style.top = `${row * this.dimensions.actualCellHeight}px`;
|
||||
element.style.left = `${colStart * this.dimensions.actualCellWidth}px`;
|
||||
element.style.width = `${this.dimensions.actualCellWidth * (colEnd - colStart)}px`;
|
||||
return element;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,10 @@ import { IRenderer } from '../renderer/Types';
|
||||
export class MouseHelper {
|
||||
constructor(private _renderer: IRenderer) {}
|
||||
|
||||
public setRenderer(renderer: IRenderer): void {
|
||||
this._renderer = renderer;
|
||||
}
|
||||
|
||||
public static getCoordsRelativeToElement(event: {pageX: number, pageY: number}, element: HTMLElement): [number, number] {
|
||||
// Ignore browsers that don't support MouseEvent.pageX
|
||||
if (event.pageX === null || event.pageX === undefined) {
|
||||
|
||||
@@ -121,7 +121,7 @@ export class MockTerminal implements ITerminal {
|
||||
handler(data: string): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
on(event: string, callback: () => void): void {
|
||||
on(event: string, callback: (...args: any[]) => void): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
off(type: string, listener: XtermListener): void {
|
||||
|
||||
Reference in New Issue
Block a user