From 1877e7019d479724ae45568d0b99c080a3bd949c Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 6 Nov 2017 23:20:57 +0000 Subject: [PATCH 01/86] Implement #778 --- src/Interfaces.ts | 3 +++ src/SelectionManager.ts | 26 +++++++++++++++++++++----- src/Terminal.ts | 7 ++++--- src/handlers/Clipboard.ts | 6 +++++- typings/xterm.d.ts | 4 ++-- 5 files changed, 35 insertions(+), 11 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index e98385c11..ea1d00a0e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -149,6 +149,7 @@ export interface ITerminalOptions { termName?: string; theme?: ITheme; useFlowControl?: boolean; + rightClickSelectsWord?: boolean; } export interface IBuffer { @@ -194,11 +195,13 @@ export interface ISelectionManager { selectionText: string; selectionStart: [number, number]; selectionEnd: [number, number]; + hasSelection: boolean; disable(): void; enable(): void; setBuffer(buffer: IBuffer): void; setSelection(row: number, col: number, length: number): void; + selectWordAtCursor(event: MouseEvent): void; } export interface ICompositionHelper { diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index bf5144cba..87b71a129 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -249,6 +249,18 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd }); } + /** + * Selects word at the current mouse event coordenates. + * @param event The mouse event. + */ + public selectWordAtCursor(event: MouseEvent): void { + const coords = this._getMouseBufferCoords(event); + if (coords) { + this._selectWordAt(coords, false); + this.refresh(true); + } + } + /** * Selects all text within the terminal. */ @@ -439,7 +451,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager const coords = this._getMouseBufferCoords(event); if (coords) { this._activeSelectionMode = SelectionMode.WORD; - this._selectWordAt(coords); + this._selectWordAt(coords, true); } } @@ -578,7 +590,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Gets positional information for the word at the coordinated specified. * @param coords The coordinates to get the word at. */ - private _getWordAt(coords: [number, number]): IWordPosition { + private _getWordAt(coords: [number, number], selectWhiteSpace: boolean): IWordPosition { const bufferLine = this._buffer.lines.get(coords[1]); if (!bufferLine) { return null; @@ -684,15 +696,19 @@ export class SelectionManager extends EventEmitter implements ISelectionManager - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis) - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis) + if (!selectWhiteSpace && line.slice(startIndex, endIndex).trim() === '') + return null; + return { start, length }; } /** * Selects the word at the coordinates specified. * @param coords The coordinates to get the word at. + * @param selectWhiteSpace If whitespace should be selected */ - protected _selectWordAt(coords: [number, number]): void { - const wordPosition = this._getWordAt(coords); + protected _selectWordAt(coords: [number, number], selectWhiteSpace: boolean): void { + const wordPosition = this._getWordAt(coords, selectWhiteSpace); if (wordPosition) { this._model.selectionStart = [wordPosition.start, coords[1]]; this._model.selectionStartLength = wordPosition.length; @@ -704,7 +720,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param coords The coordinates to get the word at. */ private _selectToWordAt(coords: [number, number]): void { - const wordPosition = this._getWordAt(coords); + const wordPosition = this._getWordAt(coords, true); if (wordPosition) { this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; } diff --git a/src/Terminal.ts b/src/Terminal.ts index 5ac8a1a0c..7dbdeb760 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -89,7 +89,8 @@ const DEFAULT_OPTIONS: ITerminalOptions = { disableStdin: false, useFlowControl: false, tabStopWidth: 8, - theme: null + theme: null, + rightClickSelectsWord: Browser.isMac // programFeatures: false, // focusKeys: false, }; @@ -492,12 +493,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // Firefox doesn't appear to fire the contextmenu event on right click on(this.element, 'mousedown', (event: MouseEvent) => { if (event.button === 2) { - rightClickHandler(event, this.textarea, this.selectionManager); + rightClickHandler(event, this.textarea, this.selectionManager, this.options.rightClickSelectsWord); } }); } else { on(this.element, 'contextmenu', (event: MouseEvent) => { - rightClickHandler(event, this.textarea, this.selectionManager); + rightClickHandler(event, this.textarea, this.selectionManager, this.options.rightClickSelectsWord); }); } diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index a97353520..86a604f28 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -103,10 +103,14 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA * @param ev The original right click event to be handled. * @param textarea The terminal's textarea. * @param selectionManager The terminal's selection manager. + * @param shouldSelectWord If true and there is no selection the current word will be selected */ -export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager): void { +export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea); + if (shouldSelectWord && !selectionManager.hasSelection) + selectionManager.selectWordAtCursor(ev); + // Get textarea ready to copy from the context menu textarea.value = selectionManager.selectionText; textarea.select(); diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 9ab61ce67..a5137797d 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -409,7 +409,7 @@ declare module 'xterm' { * Retrieves an option's value from the terminal. * @param key The option key. */ - getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell'): boolean; + getOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'rightClickSelectsWord'): boolean; /** * Retrieves an option's value from the terminal. * @param key The option key. @@ -459,7 +459,7 @@ declare module 'xterm' { * @param key The option key. * @param value The option value. */ - setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell', value: boolean): void; + setOption(key: 'cancelEvents' | 'convertEol' | 'cursorBlink' | 'debug' | 'disableStdin' | 'enableBold' | 'popOnBell' | 'screenKeys' | 'useFlowControl' | 'visualBell' | 'rightClickSelectsWord', value: boolean): void; /** * Sets an option on the terminal. * @param key The option key. From 2865d935b646a43b2dbaa7f8e5f176b4d2f90518 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Tue, 7 Nov 2017 21:21:44 +0000 Subject: [PATCH 02/86] Adress feedback --- fixtures/typings-test/typings-test.ts | 2 ++ src/SelectionManager.test.ts | 2 +- src/SelectionManager.ts | 13 +++++++------ 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/fixtures/typings-test/typings-test.ts b/fixtures/typings-test/typings-test.ts index 9d73535ec..d61b22f20 100644 --- a/fixtures/typings-test/typings-test.ts +++ b/fixtures/typings-test/typings-test.ts @@ -143,6 +143,7 @@ namespace methods_core { const r20: string = t.getOption('bellStyle'); const r21: boolean = t.getOption('enableBold'); const r22: number = t.getOption('letterSpacing'); + const r23: boolean = t.getOption('rightClickSelectsWord'); } { const t: Terminal = new Terminal(); @@ -177,6 +178,7 @@ namespace methods_core { t.setOption('lineHeight', 1); t.setOption('fontFamily', 'foo'); t.setOption('theme', {background: '#ff0000'}); + t.setOption('rightClickSelectsWord', false); } } namespace scrolling { diff --git a/src/SelectionManager.test.ts b/src/SelectionManager.test.ts index 8c6ea3e18..e229889ee 100644 --- a/src/SelectionManager.test.ts +++ b/src/SelectionManager.test.ts @@ -26,7 +26,7 @@ class TestSelectionManager extends SelectionManager { public get model(): SelectionModel { return this._model; } public selectLineAt(line: number): void { this._selectLineAt(line); } - public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords); } + public selectWordAt(coords: [number, number]): void { this._selectWordAt(coords, true); } // Disable DOM interaction public enable(): void {} diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 87b71a129..1fccc8b17 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -250,7 +250,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } /** - * Selects word at the current mouse event coordenates. + * Selects word at the current mouse event coordinates. * @param event The mouse event. */ public selectWordAtCursor(event: MouseEvent): void { @@ -590,7 +590,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * Gets positional information for the word at the coordinated specified. * @param coords The coordinates to get the word at. */ - private _getWordAt(coords: [number, number], selectWhiteSpace: boolean): IWordPosition { + private _getWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): IWordPosition { const bufferLine = this._buffer.lines.get(coords[1]); if (!bufferLine) { return null; @@ -696,8 +696,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager - leftLongCharOffset // The number of additional chars left of the initial char added by columns with strings longer than 1 (emojis) - rightLongCharOffset); // The number of additional chars right of the initial char (inclusive) added by columns with strings longer than 1 (emojis) - if (!selectWhiteSpace && line.slice(startIndex, endIndex).trim() === '') + if (!allowWhitespaceOnlySelection && line.slice(startIndex, endIndex).trim() === '') { return null; + } return { start, length }; } @@ -705,10 +706,10 @@ export class SelectionManager extends EventEmitter implements ISelectionManager /** * Selects the word at the coordinates specified. * @param coords The coordinates to get the word at. - * @param selectWhiteSpace If whitespace should be selected + * @param allowWhitespaceOnlySelection If whitespace should be selected */ - protected _selectWordAt(coords: [number, number], selectWhiteSpace: boolean): void { - const wordPosition = this._getWordAt(coords, selectWhiteSpace); + protected _selectWordAt(coords: [number, number], allowWhitespaceOnlySelection: boolean): void { + const wordPosition = this._getWordAt(coords, allowWhitespaceOnlySelection); if (wordPosition) { this._model.selectionStart = [wordPosition.start, coords[1]]; this._model.selectionStartLength = wordPosition.length; From 18b86a1cbc4cb1d99f67599992985b733fa6e1a8 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Wed, 8 Nov 2017 23:27:00 +0000 Subject: [PATCH 03/86] Do the selection even if there is already a selection --- src/Interfaces.ts | 1 + src/SelectionManager.ts | 26 +++++++++++++++++++++++--- src/handlers/Clipboard.ts | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index ea1d00a0e..69e7fcb2b 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -201,6 +201,7 @@ export interface ISelectionManager { enable(): void; setBuffer(buffer: IBuffer): void; setSelection(row: number, col: number, length: number): void; + isClickInSelection(event: MouseEvent): boolean; selectWordAtCursor(event: MouseEvent): void; } diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 1fccc8b17..3bf366ce1 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -249,6 +249,22 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this.emit('refresh', { start: this._model.finalSelectionStart, end: this._model.finalSelectionEnd }); } + /** + * Checks if the current click was inside the current selection + * @param event The mouse event + */ + public isClickInSelection(event: MouseEvent): boolean { + const coords = this._getMouseBufferCoords(event); + const start = this._model.finalSelectionStart; + const end = this._model.finalSelectionEnd; + + if (!start || !end) { + return false; + } + + return (start[1] < coords[1] && end[1] > coords[1]) || (start[1] === coords[1] && coords[0] > start[0]) || (end[1] === coords[1] && coords[0] < end[0]); + } + /** * Selects word at the current mouse event coordinates. * @param event The mouse event. @@ -256,10 +272,14 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectWordAtCursor(event: MouseEvent): void { const coords = this._getMouseBufferCoords(event); if (coords) { - this._selectWordAt(coords, false); - this.refresh(true); + const wordPosition = this._getWordAt(coords, true); + if (wordPosition) { + this._model.selectionStart = [wordPosition.start, coords[1]]; + this._model.selectionStartLength = wordPosition.length; + this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; + this.refresh(true); + } } - } /** * Selects all text within the terminal. diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index 86a604f28..6814d269c 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -108,7 +108,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea); - if (shouldSelectWord && !selectionManager.hasSelection) + if (shouldSelectWord && (!selectionManager.hasSelection || !selectionManager.isClickInSelection(ev))) selectionManager.selectWordAtCursor(ev); // Get textarea ready to copy from the context menu From 6822070222945574d4b017b4bb80b725e133a255 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Sun, 12 Nov 2017 23:15:09 +0000 Subject: [PATCH 04/86] Fix typo + Cleanup logic --- src/Interfaces.ts | 1 - src/SelectionManager.ts | 1 + src/handlers/Clipboard.ts | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 69e7fcb2b..39b81baf9 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -195,7 +195,6 @@ export interface ISelectionManager { selectionText: string; selectionStart: [number, number]; selectionEnd: [number, number]; - hasSelection: boolean; disable(): void; enable(): void; diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 3bf366ce1..7e04119e2 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -280,6 +280,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager this.refresh(true); } } + } /** * Selects all text within the terminal. diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index 6814d269c..f110ed1fa 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -108,7 +108,7 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea); - if (shouldSelectWord && (!selectionManager.hasSelection || !selectionManager.isClickInSelection(ev))) + if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) selectionManager.selectWordAtCursor(ev); // Get textarea ready to copy from the context menu From 399779692d667a6bb35b44cc33c3d5245a33edca Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 19 Dec 2017 12:57:58 -0800 Subject: [PATCH 05/86] Initial prototype --- src/AccessibilityManager.ts | 137 ++++++++++++++++++++++++++++++++++++ src/InputHandler.ts | 4 ++ src/Interfaces.ts | 5 ++ src/Terminal.ts | 19 +++++ src/xterm.css | 21 ++++++ 5 files changed, 186 insertions(+) create mode 100644 src/AccessibilityManager.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts new file mode 100644 index 000000000..41931477e --- /dev/null +++ b/src/AccessibilityManager.ts @@ -0,0 +1,137 @@ +import { ITerminal, IBuffer, IDisposable } from './Interfaces'; + +export class AccessibilityManager implements IDisposable { + private _accessibilityTreeRoot: HTMLElement; + private _rowContainer: HTMLElement; + private _rowElements: HTMLElement[] = []; + private _liveRegion: HTMLElement; + + private _disposables: IDisposable[] = []; + + /** + * This queue has a character pushed to it for keys that are pressed, if the + * next character added to the terminal is equal to the key char then it is + * not announced (added to live region) because it has already been announced + * by the textarea event (which cannot be canceled). There are some race + * condition cases if there is typing while data is streaming, but this covers + * the main case of typing into the prompt and inputting the answer to a + * question (Y/N, etc.). + */ + private _charsToConsume: string[] = []; + + constructor(private _terminal: ITerminal) { + this._accessibilityTreeRoot = document.createElement('div'); + this._accessibilityTreeRoot.classList.add('accessibility'); + this._rowContainer = document.createElement('div'); + this._rowContainer.classList.add('accessibility-tree'); + for (let i = 0; i < this._terminal.rows; i++) { + this._rowElements[i] = document.createElement('div'); + this._rowContainer.appendChild(this._rowElements[i]); + } + this._accessibilityTreeRoot.appendChild(this._rowContainer); + + this._liveRegion = document.createElement('div'); + this._liveRegion.classList.add('live-region'); + this._liveRegion.setAttribute('aria-live', 'polite'); + this._accessibilityTreeRoot.appendChild(this._liveRegion); + + this._terminal.element.appendChild(this._accessibilityTreeRoot); + + this._addTerminalEventListener('resize', data => this._onResize(data.cols, data.rows)); + this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); + // Line feed is an issue as the prompt won't be read out after a command is run + // this._terminal.on('lineFeed', () => this._onLineFeed()); + this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); + this._addTerminalEventListener('lineFeed', () => this._onChar('\n')); + this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); + this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); + } + + private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { + this._terminal.on(type, listener); + this._disposables.push({ + dispose: () => { + this._terminal.off(type, listener); + } + }); + } + + public dispose(): void { + this._terminal.element.removeChild(this._accessibilityTreeRoot); + this._accessibilityTreeRoot = null; + this._rowContainer = null; + this._liveRegion = null; + this._rowContainer = null; + this._rowElements = null; + this._disposables.forEach(d => d.dispose()); + this._disposables = null; + } + + private _onResize(cols: number, rows: number): void { + for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { + this._rowElements[i] = document.createElement('div'); + this._rowContainer.appendChild(this._rowElements[i]); + } + // TODO: Handle case when rows reduces + + this._refreshRowsDimensions(); + } + + private _onChar(char: string): void { + if (this._charsToConsume.length > 0) { + // Have the screen reader ignore the char if it was just input + if (this._charsToConsume.shift() !== char) { + this._liveRegion.textContent += char; + } + } else { + this._liveRegion.textContent += char; + } + // TODO: Clear at some point + // TOOD: Handle heaps of data + + // This is temporary, should refresh at a much slower rate + this._refreshRows(); + } + + private _onKey(keyChar: string): void { + this._charsToConsume.push(keyChar); + } + + // private _onLineFeed(): void { + // const buffer: IBuffer = (this._terminal.buffer); + // const newLine = buffer.lines.get(buffer.ybase + buffer.y); + // // Only use the data when the new line is ready + // if (!(newLine).isWrapped) { + // this._accessibilityTreeRoot.textContent += `${this._getWrappedLineData(buffer, buffer.ybase + buffer.y - 1)}\n`; + // } + // } + + // private _getWrappedLineData(buffer: IBuffer, lineIndex: number): string { + // let lineData = buffer.translateBufferLineToString(lineIndex, true); + // while (lineIndex >= 0 && (buffer.lines.get(lineIndex--)).isWrapped) { + // lineData = buffer.translateBufferLineToString(lineIndex, true) + lineData; + // } + // return lineData; + // } + + // TODO: Hook up to refresh when the renderer refreshes the range? Slower to prevent layout thrashing? + private _refreshRows(start?: number, end?: number): void { + const buffer: IBuffer = (this._terminal.buffer); + start = start || 0; + end = end || this._terminal.rows - 1; + for (let i = start; i <= end; i++) { + const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); + this._rowElements[i].textContent = lineData; + } + } + + private _refreshRowsDimensions(): void { + const buffer: IBuffer = (this._terminal.buffer); + const dimensions = this._terminal.renderer.dimensions; + for (let i = 0; i < this._terminal.rows; i++) { + this._rowElements[i].style.height = `${dimensions.actualCellHeight}px`; + } + // TODO: Verify it works on macOS and varying zoom levels + // TODO: Fire when window resizes + } +} diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 512cf8782..464792368 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -32,6 +32,10 @@ export class InputHandler implements IInputHandler { char = this._terminal.charset[char]; } + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.char', char); + } + let row = this._terminal.buffer.y + this._terminal.buffer.ybase; // insert combining char in last cell diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f90cb8c04..423f8d114 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -145,6 +145,7 @@ export interface ITerminalOptions { lineHeight?: number; rows?: number; screenKeys?: boolean; + screenReaderMode?: boolean; scrollback?: number; tabStopWidth?: number; termName?: string; @@ -352,3 +353,7 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } + +export interface IDisposable { + dispose(): void; +} diff --git a/src/Terminal.ts b/src/Terminal.ts index 9e6312a59..aa9a37716 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -46,6 +46,7 @@ import { IMouseZoneManager } from './input/Interfaces'; import { MouseZoneManager } from './input/MouseZoneManager'; import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; import { IRenderer } from './renderer/Interfaces'; +import { AccessibilityManager } from './AccessibilityManager'; // Declares required for loadAddon declare var exports: any; @@ -85,6 +86,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { letterSpacing: 0, scrollback: 1000, screenKeys: false, + screenReaderMode: false, debug: false, cancelEvents: false, disableStdin: false, @@ -204,6 +206,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public charMeasure: CharMeasure; private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; + private _accessibilityManager: AccessibilityManager; public cols: number; public rows: number; @@ -427,6 +430,18 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.buffers.resize(this.cols, this.rows); this.viewport.syncScrollArea(); break; + case 'screenReaderMode': + if (value) { + if (!this._accessibilityManager) { + this._accessibilityManager = new AccessibilityManager(this); + } + } else { + if (this._accessibilityManager) { + this._accessibilityManager.dispose(); + this._accessibilityManager = null; + } + } + break; case 'tabStopWidth': this.buffers.setupTabStops(); break; case 'bellSound': case 'bellStyle': this.syncBellSound(); break; @@ -661,6 +676,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.mouseHelper = new MouseHelper(this.renderer); + if (this.options.screenReaderMode) { + this._accessibilityManager = new AccessibilityManager(this); + } + // Measure the character size this.charMeasure.measure(this.options); diff --git a/src/xterm.css b/src/xterm.css index 35e92f636..085d910af 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -122,3 +122,24 @@ .xterm:not(.enable-mouse-events) { cursor: text; } + +.xterm .accessibility { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 100; +} + +.xterm .accessibility-tree { + color: transparent; +} + +.xterm .live-region { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} From d03f5b0c2a5f1d8ed5ab8bc7232725184d34e13d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 19 Dec 2017 15:16:28 -0800 Subject: [PATCH 06/86] Range of improvements for macOS VoiceOver --- src/AccessibilityManager.ts | 36 +++++++++++++++++------------------- src/InputHandler.ts | 3 +++ src/Terminal.ts | 15 +++++---------- 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 41931477e..4b23fcf43 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -40,11 +40,13 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('resize', data => this._onResize(data.cols, data.rows)); this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); // Line feed is an issue as the prompt won't be read out after a command is run - // this._terminal.on('lineFeed', () => this._onLineFeed()); this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); this._addTerminalEventListener('lineFeed', () => this._onChar('\n')); + // Ensure \t is covered, if not a line of output from `ls` is read as one word + this._addTerminalEventListener('a11y.tab', () => this._onChar(' ')); this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); + this._addTerminalEventListener('blur', () => this._clearLiveRegion()); } private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { @@ -86,6 +88,10 @@ export class AccessibilityManager implements IDisposable { } else { this._liveRegion.textContent += char; } + + if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + this._accessibilityTreeRoot.appendChild(this._liveRegion); + } // TODO: Clear at some point // TOOD: Handle heaps of data @@ -93,26 +99,18 @@ export class AccessibilityManager implements IDisposable { this._refreshRows(); } - private _onKey(keyChar: string): void { - this._charsToConsume.push(keyChar); + private _clearLiveRegion(): void { + if (this._liveRegion.parentNode) { + this._accessibilityTreeRoot.removeChild(this._liveRegion); + } + this._liveRegion.textContent = ''; } - // private _onLineFeed(): void { - // const buffer: IBuffer = (this._terminal.buffer); - // const newLine = buffer.lines.get(buffer.ybase + buffer.y); - // // Only use the data when the new line is ready - // if (!(newLine).isWrapped) { - // this._accessibilityTreeRoot.textContent += `${this._getWrappedLineData(buffer, buffer.ybase + buffer.y - 1)}\n`; - // } - // } - - // private _getWrappedLineData(buffer: IBuffer, lineIndex: number): string { - // let lineData = buffer.translateBufferLineToString(lineIndex, true); - // while (lineIndex >= 0 && (buffer.lines.get(lineIndex--)).isWrapped) { - // lineData = buffer.translateBufferLineToString(lineIndex, true) + lineData; - // } - // return lineData; - // } + private _onKey(keyChar: string): void { + console.log('key event', keyChar); + this._clearLiveRegion(); + this._charsToConsume.push(keyChar); + } // TODO: Hook up to refresh when the renderer refreshes the range? Slower to prevent layout thrashing? private _refreshRows(start?: number, end?: number): void { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 464792368..40dde4a49 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -167,6 +167,9 @@ export class InputHandler implements IInputHandler { */ public tab(): void { this._terminal.buffer.x = this._terminal.buffer.nextStop(); + if (this._terminal.options.screenReaderMode) { + this._terminal.emit('a11y.tab'); + } } /** diff --git a/src/Terminal.ts b/src/Terminal.ts index a1e5586fb..d45b12af2 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -470,6 +470,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Binds the desired blur behavior on a given terminal object. */ private _onTextAreaBlur(): void { + // Text can safely be removed on blur. Doing it earlier could interfere with + // screen readers reading it out. + this.textarea.value = ''; this.refresh(this.buffer.y, this.buffer.y); if (this.sendFocus) { this.send(C0.ESC + '[O'); @@ -550,16 +553,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } }, true); - on(this.textarea, 'keydown', (ev: KeyboardEvent) => { - this._keyDown(ev); - }, true); - - on(this.textarea, 'keypress', (ev: KeyboardEvent) => { - this._keyPress(ev); - // Truncate the textarea's value, since it is not needed - this.textarea.value = ''; - }, true); - + on(this.textarea, 'keydown', (ev: KeyboardEvent) => this._keyDown(ev), true); + on(this.textarea, 'keypress', (ev: KeyboardEvent) => this._keyPress(ev), true); on(this.textarea, 'compositionstart', () => this.compositionHelper.compositionstart()); on(this.textarea, 'compositionupdate', (e: CompositionEvent) => this.compositionHelper.compositionupdate(e)); on(this.textarea, 'compositionend', () => this.compositionHelper.compositionend()); From ae957ecb3eea3f9350daa9e920f631e638a8bf46 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 19 Dec 2017 15:48:58 -0800 Subject: [PATCH 07/86] Get output reading working on Windows, refresh on window resize --- src/AccessibilityManager.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 4b23fcf43..92ce6b5c1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -1,3 +1,8 @@ +/** + * Copyright (c) 2017 The xterm.js authors. All rights reserved. + * @license MIT + */ + import { ITerminal, IBuffer, IDisposable } from './Interfaces'; export class AccessibilityManager implements IDisposable { @@ -47,6 +52,9 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); this._addTerminalEventListener('blur', () => this._clearLiveRegion()); + // TODO: Dispose of this listener when disposed + // TODO: Only refresh when devicePixelRatio changed + window.addEventListener('resize', () => this._refreshRowsDimensions()); } private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { @@ -89,10 +97,6 @@ export class AccessibilityManager implements IDisposable { this._liveRegion.textContent += char; } - if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { - this._accessibilityTreeRoot.appendChild(this._liveRegion); - } - // TODO: Clear at some point // TOOD: Handle heaps of data // This is temporary, should refresh at a much slower rate @@ -100,14 +104,10 @@ export class AccessibilityManager implements IDisposable { } private _clearLiveRegion(): void { - if (this._liveRegion.parentNode) { - this._accessibilityTreeRoot.removeChild(this._liveRegion); - } this._liveRegion.textContent = ''; } private _onKey(keyChar: string): void { - console.log('key event', keyChar); this._clearLiveRegion(); this._charsToConsume.push(keyChar); } From 0d65cb3f69a1b042b079e9844c1891bc611b4bc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 19 Dec 2017 16:27:03 -0800 Subject: [PATCH 08/86] Add max rows to read to handle spam case --- src/AccessibilityManager.ts | 32 ++++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 92ce6b5c1..2a15482d1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -5,11 +5,14 @@ import { ITerminal, IBuffer, IDisposable } from './Interfaces'; +const MAX_ROWS_TO_READ = 20; + export class AccessibilityManager implements IDisposable { private _accessibilityTreeRoot: HTMLElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _liveRegion: HTMLElement; + private _liveRegionLineCount: number = 0; private _disposables: IDisposable[] = []; @@ -78,26 +81,38 @@ export class AccessibilityManager implements IDisposable { } private _onResize(cols: number, rows: number): void { + // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { this._rowElements[i] = document.createElement('div'); this._rowContainer.appendChild(this._rowElements[i]); } - // TODO: Handle case when rows reduces + // Shrink rows as required + while (this._rowElements.length > rows) { + this._rowContainer.removeChild(this._rowElements.pop()); + } this._refreshRowsDimensions(); } private _onChar(char: string): void { - if (this._charsToConsume.length > 0) { - // Have the screen reader ignore the char if it was just input - if (this._charsToConsume.shift() !== char) { + if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { + if (this._charsToConsume.length > 0) { + // Have the screen reader ignore the char if it was just input + if (this._charsToConsume.shift() !== char) { + this._liveRegion.textContent += char; + } + } else { this._liveRegion.textContent += char; } - } else { - this._liveRegion.textContent += char; - } - // TOOD: Handle heaps of data + if (char === '\n') { + this._liveRegionLineCount++; + if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { + // TODO: Enable localization + this._liveRegion.textContent += 'Too much output to announce, navigate to rows manually to read'; + } + } + } // This is temporary, should refresh at a much slower rate this._refreshRows(); @@ -105,6 +120,7 @@ export class AccessibilityManager implements IDisposable { private _clearLiveRegion(): void { this._liveRegion.textContent = ''; + this._liveRegionLineCount = 0; } private _onKey(keyChar: string): void { From 5dc57a1305cd7eca10bf01793645441f711c2048 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 20 Dec 2017 11:06:32 -0800 Subject: [PATCH 09/86] Add detach logic back for mac only --- src/AccessibilityManager.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 2a15482d1..bda634d0e 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -4,6 +4,7 @@ */ import { ITerminal, IBuffer, IDisposable } from './Interfaces'; +import { isMac } from './utils/Browser'; const MAX_ROWS_TO_READ = 20; @@ -112,6 +113,13 @@ export class AccessibilityManager implements IDisposable { this._liveRegion.textContent += 'Too much output to announce, navigate to rows manually to read'; } } + + // Only detach/attach on mac as otherwise messages can go unaccounced + if (isMac) { + if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { + this._accessibilityTreeRoot.appendChild(this._liveRegion); + } + } } // This is temporary, should refresh at a much slower rate @@ -121,6 +129,13 @@ export class AccessibilityManager implements IDisposable { private _clearLiveRegion(): void { this._liveRegion.textContent = ''; this._liveRegionLineCount = 0; + + // Only detach/attach on mac as otherwise messages can go unaccounced + if (isMac) { + if (this._liveRegion.parentNode) { + this._accessibilityTreeRoot.removeChild(this._liveRegion); + } + } } private _onKey(keyChar: string): void { From 17964a517cc282d007ac4abe7b95471836dfa49c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Jan 2018 09:28:24 -0800 Subject: [PATCH 10/86] Add screenReaderMode to typings --- typings/xterm.d.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 83199e08b..5ebe578e4 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -72,6 +72,13 @@ declare module 'xterm' { */ rows?: number; + /** + * Whether screen reader support is enabled. When on this will expose + * supporting elements in the DOM to support NVDA on Windows and VoiceOver + * on macOS. + */ + screenReaderMode?: boolean; + /** * The amount of scrollback in the terminal. Scrollback is the amount of rows * that are retained when lines are scrolled beyond the initial viewport. From 5ca15cfe3be47841c9878f4467ab9f1f06fe0d47 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Jan 2018 09:50:03 -0800 Subject: [PATCH 11/86] Fix linefeed event This was causing new lines to join words together --- src/AccessibilityManager.ts | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index bda634d0e..8de8d459d 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -50,14 +50,14 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); // Line feed is an issue as the prompt won't be read out after a command is run this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); - this._addTerminalEventListener('lineFeed', () => this._onChar('\n')); + this._addTerminalEventListener('linefeed', () => this._onChar('\n')); // Ensure \t is covered, if not a line of output from `ls` is read as one word this._addTerminalEventListener('a11y.tab', () => this._onChar(' ')); this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); this._addTerminalEventListener('blur', () => this._clearLiveRegion()); // TODO: Dispose of this listener when disposed - // TODO: Only refresh when devicePixelRatio changed + // TODO: Only refresh when devicePixelRatio changed (depends on PR #1172) window.addEventListener('resize', () => this._refreshRowsDimensions()); } @@ -97,9 +97,13 @@ export class AccessibilityManager implements IDisposable { private _onChar(char: string): void { if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { + // \n needs to be printed as a space, otherwise it will be collapsed to + // "" in the DOM and the last and first words of the rows will be read + // as a single word if (this._charsToConsume.length > 0) { // Have the screen reader ignore the char if it was just input - if (this._charsToConsume.shift() !== char) { + const shiftedChar = this._charsToConsume.shift(); + if (shiftedChar !== char) { this._liveRegion.textContent += char; } } else { From 6d1064fc019a61d4d3503e471e6480d48ac0c31c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 3 Jan 2018 10:08:24 -0800 Subject: [PATCH 12/86] Set height of a11y rows on creation --- src/AccessibilityManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 8de8d459d..20373bf95 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -37,6 +37,7 @@ export class AccessibilityManager implements IDisposable { this._rowElements[i] = document.createElement('div'); this._rowContainer.appendChild(this._rowElements[i]); } + this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); this._liveRegion = document.createElement('div'); From bb830eb1ae5b0800482c922890d447ff8ab5b205 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 5 Jan 2018 07:39:35 -0800 Subject: [PATCH 13/86] Rate limit row refresh, tweak live region --- src/AccessibilityManager.ts | 48 +++++++++++++++++++++++++++++++------ src/InputHandler.ts | 3 ++- src/renderer/Renderer.ts | 2 +- 3 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 20373bf95..401175f23 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -15,6 +15,10 @@ export class AccessibilityManager implements IDisposable { private _liveRegion: HTMLElement; private _liveRegionLineCount: number = 0; + private _refreshRowStart: number; + private _refreshRowEnd: number; + private _refreshAnimationFrame: number = null; + private _disposables: IDisposable[] = []; /** @@ -42,7 +46,7 @@ export class AccessibilityManager implements IDisposable { this._liveRegion = document.createElement('div'); this._liveRegion.classList.add('live-region'); - this._liveRegion.setAttribute('aria-live', 'polite'); + this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityTreeRoot.appendChild(this._liveRegion); this._terminal.element.appendChild(this._accessibilityTreeRoot); @@ -52,8 +56,12 @@ export class AccessibilityManager implements IDisposable { // Line feed is an issue as the prompt won't be read out after a command is run this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); this._addTerminalEventListener('linefeed', () => this._onChar('\n')); - // Ensure \t is covered, if not a line of output from `ls` is read as one word - this._addTerminalEventListener('a11y.tab', () => this._onChar(' ')); + // Ensure \t is covered, if not 2 words separated by only a tab will be read as 1 word + this._addTerminalEventListener('a11y.tab', spaceCount => { + for (let i = 0; i < spaceCount; i++) { + this._onChar(' '); + } + }); this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); this._addTerminalEventListener('blur', () => this._clearLiveRegion()); @@ -105,9 +113,18 @@ export class AccessibilityManager implements IDisposable { // Have the screen reader ignore the char if it was just input const shiftedChar = this._charsToConsume.shift(); if (shiftedChar !== char) { - this._liveRegion.textContent += char; + if (char === ' ') { + // Always use nbsp for spaces in order to preserve the space between characters in + // voiceover's caption window + this._liveRegion.innerHTML += ' '; + } else { + this._liveRegion.textContent += char; + } } } else { + if (char === ' ') { + this._liveRegion.innerHTML += ' '; + } else this._liveRegion.textContent += char; } @@ -122,7 +139,9 @@ export class AccessibilityManager implements IDisposable { // Only detach/attach on mac as otherwise messages can go unaccounced if (isMac) { if (this._liveRegion.textContent.length > 0 && !this._liveRegion.parentNode) { - this._accessibilityTreeRoot.appendChild(this._liveRegion); + setTimeout(() => { + this._accessibilityTreeRoot.appendChild(this._liveRegion); + }, 0); } } } @@ -150,13 +169,28 @@ export class AccessibilityManager implements IDisposable { // TODO: Hook up to refresh when the renderer refreshes the range? Slower to prevent layout thrashing? private _refreshRows(start?: number, end?: number): void { - const buffer: IBuffer = (this._terminal.buffer); start = start || 0; end = end || this._terminal.rows - 1; - for (let i = start; i <= end; i++) { + this._refreshRowStart = this._refreshRowStart ? Math.min(this._refreshRowStart, start) : start; + this._refreshRowEnd = this._refreshRowEnd ? Math.max(this._refreshRowEnd, end) : end; + + if (this._refreshAnimationFrame) { + return; + } + + this._refreshAnimationFrame = window.requestAnimationFrame(() => this._innerRefreshRows()); + } + + private _innerRefreshRows(): void { + console.log('innerRefreshRows'); + const buffer: IBuffer = (this._terminal.buffer); + for (let i = this._refreshRowStart; i <= this._refreshRowEnd; i++) { const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); this._rowElements[i].textContent = lineData; } + this._refreshRowStart = null; + this._refreshRowEnd = null; + this._refreshAnimationFrame = null; } private _refreshRowsDimensions(): void { diff --git a/src/InputHandler.ts b/src/InputHandler.ts index 2fe3a5f59..5716ace4d 100644 --- a/src/InputHandler.ts +++ b/src/InputHandler.ts @@ -166,9 +166,10 @@ export class InputHandler implements IInputHandler { * Horizontal Tab (HT) (Ctrl-I). */ public tab(): void { + const originalX = this._terminal.buffer.x; this._terminal.buffer.x = this._terminal.buffer.nextStop(); if (this._terminal.options.screenReaderMode) { - this._terminal.emit('a11y.tab'); + this._terminal.emit('a11y.tab', this._terminal.buffer.x - originalX); } } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 36727eac5..4cdf9c525 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -17,7 +17,7 @@ import { EventEmitter } from '../EventEmitter'; export class Renderer extends EventEmitter implements IRenderer { /** A queue of the rows to be refreshed */ private _refreshRowsQueue: {start: number, end: number}[] = []; - private _refreshAnimationFrame = null; + private _refreshAnimationFrame: number = null; private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; From c332aeb51f34d3bb23befeb8f2f5a5843eda08d7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 5 Jan 2018 07:51:22 -0800 Subject: [PATCH 14/86] Remove obsolete code and resolved TODOs --- src/AccessibilityManager.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 401175f23..6f80b08c1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -66,7 +66,7 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); this._addTerminalEventListener('blur', () => this._clearLiveRegion()); // TODO: Dispose of this listener when disposed - // TODO: Only refresh when devicePixelRatio changed (depends on PR #1172) + // TODO: Listen instead to when devicePixelRatio changed (depends on PR #1172) window.addEventListener('resize', () => this._refreshRowsDimensions()); } @@ -80,6 +80,10 @@ export class AccessibilityManager implements IDisposable { } public dispose(): void { + if (this._refreshAnimationFrame) { + window.cancelAnimationFrame(this._refreshAnimationFrame); + this._refreshAnimationFrame = null; + } this._terminal.element.removeChild(this._accessibilityTreeRoot); this._accessibilityTreeRoot = null; this._rowContainer = null; @@ -145,9 +149,6 @@ export class AccessibilityManager implements IDisposable { } } } - - // This is temporary, should refresh at a much slower rate - this._refreshRows(); } private _clearLiveRegion(): void { @@ -167,7 +168,6 @@ export class AccessibilityManager implements IDisposable { this._charsToConsume.push(keyChar); } - // TODO: Hook up to refresh when the renderer refreshes the range? Slower to prevent layout thrashing? private _refreshRows(start?: number, end?: number): void { start = start || 0; end = end || this._terminal.rows - 1; @@ -182,7 +182,6 @@ export class AccessibilityManager implements IDisposable { } private _innerRefreshRows(): void { - console.log('innerRefreshRows'); const buffer: IBuffer = (this._terminal.buffer); for (let i = this._refreshRowStart; i <= this._refreshRowEnd; i++) { const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); @@ -199,7 +198,5 @@ export class AccessibilityManager implements IDisposable { for (let i = 0; i < this._terminal.rows; i++) { this._rowElements[i].style.height = `${dimensions.actualCellHeight}px`; } - // TODO: Verify it works on macOS and varying zoom levels - // TODO: Fire when window resizes } } From a846e8c08d323cebecf542af37865ba57c06041e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 5 Jan 2018 08:15:21 -0800 Subject: [PATCH 15/86] Pull render animation frame logic into a helper class --- src/AccessibilityManager.ts | 30 ++++++---------------- src/renderer/Renderer.ts | 38 ++++----------------------- src/utils/RenderDebouncer.ts | 50 ++++++++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+), 55 deletions(-) create mode 100644 src/utils/RenderDebouncer.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 6f80b08c1..ed1904000 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -5,6 +5,7 @@ import { ITerminal, IBuffer, IDisposable } from './Interfaces'; import { isMac } from './utils/Browser'; +import { RenderDebouncer } from './utils/RenderDebouncer'; const MAX_ROWS_TO_READ = 20; @@ -15,9 +16,7 @@ export class AccessibilityManager implements IDisposable { private _liveRegion: HTMLElement; private _liveRegionLineCount: number = 0; - private _refreshRowStart: number; - private _refreshRowEnd: number; - private _refreshAnimationFrame: number = null; + private _renderRowsDebouncer: RenderDebouncer; private _disposables: IDisposable[] = []; @@ -44,6 +43,8 @@ export class AccessibilityManager implements IDisposable { this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); + this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._liveRegion = document.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); @@ -80,10 +81,7 @@ export class AccessibilityManager implements IDisposable { } public dispose(): void { - if (this._refreshAnimationFrame) { - window.cancelAnimationFrame(this._refreshAnimationFrame); - this._refreshAnimationFrame = null; - } + this._renderRowsDebouncer.dispose(); this._terminal.element.removeChild(this._accessibilityTreeRoot); this._accessibilityTreeRoot = null; this._rowContainer = null; @@ -169,27 +167,15 @@ export class AccessibilityManager implements IDisposable { } private _refreshRows(start?: number, end?: number): void { - start = start || 0; - end = end || this._terminal.rows - 1; - this._refreshRowStart = this._refreshRowStart ? Math.min(this._refreshRowStart, start) : start; - this._refreshRowEnd = this._refreshRowEnd ? Math.max(this._refreshRowEnd, end) : end; - - if (this._refreshAnimationFrame) { - return; - } - - this._refreshAnimationFrame = window.requestAnimationFrame(() => this._innerRefreshRows()); + this._renderRowsDebouncer.refresh(start, end); } - private _innerRefreshRows(): void { + private _renderRows(start: number, end: number): void { const buffer: IBuffer = (this._terminal.buffer); - for (let i = this._refreshRowStart; i <= this._refreshRowEnd; i++) { + for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); this._rowElements[i].textContent = lineData; } - this._refreshRowStart = null; - this._refreshRowEnd = null; - this._refreshAnimationFrame = null; } private _refreshRowsDimensions(): void { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 4cdf9c525..3d2a4d954 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -13,11 +13,10 @@ import { BaseRenderLayer } from './BaseRenderLayer'; import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfaces'; import { LinkRenderLayer } from './LinkRenderLayer'; import { EventEmitter } from '../EventEmitter'; +import { RenderDebouncer } from '../utils/RenderDebouncer'; export class Renderer extends EventEmitter implements IRenderer { - /** A queue of the rows to be refreshed */ - private _refreshRowsQueue: {start: number, end: number}[] = []; - private _refreshAnimationFrame: number = null; + private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; @@ -53,6 +52,7 @@ export class Renderer extends EventEmitter implements IRenderer { }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); + this._renderDebouncer = new RenderDebouncer(this._terminal, this._refreshLoop.bind(this)); } public onWindowResize(devicePixelRatio: number): void { @@ -129,42 +129,14 @@ export class Renderer extends EventEmitter implements IRenderer { * @param {number} end The end row. */ public queueRefresh(start: number, end: number): void { - this._refreshRowsQueue.push({ start: start, end: end }); - if (!this._refreshAnimationFrame) { - this._refreshAnimationFrame = window.requestAnimationFrame(this._refreshLoop.bind(this)); - } + this._renderDebouncer.refresh(start, end); } /** * Performs the refresh loop callback, calling refresh only if a refresh is * necessary before queueing up the next one. */ - private _refreshLoop(): void { - let start; - let end; - if (this._refreshRowsQueue.length > 4) { - // Just do a full refresh when 5+ refreshes are queued - start = 0; - end = this._terminal.rows - 1; - } else { - // Get start and end rows that need refreshing - start = this._refreshRowsQueue[0].start; - end = this._refreshRowsQueue[0].end; - for (let i = 1; i < this._refreshRowsQueue.length; i++) { - if (this._refreshRowsQueue[i].start < start) { - start = this._refreshRowsQueue[i].start; - } - if (this._refreshRowsQueue[i].end > end) { - end = this._refreshRowsQueue[i].end; - } - } - } - this._refreshRowsQueue = []; - this._refreshAnimationFrame = null; - - // Render - start = Math.max(start, 0); - end = Math.min(end, this._terminal.rows - 1); + private _refreshLoop(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); this._terminal.emit('refresh', {start, end}); } diff --git a/src/utils/RenderDebouncer.ts b/src/utils/RenderDebouncer.ts new file mode 100644 index 000000000..c494fb98e --- /dev/null +++ b/src/utils/RenderDebouncer.ts @@ -0,0 +1,50 @@ +import { ITerminal, IDisposable } from '../Interfaces'; + +/** + * Debounces calls to render terminal rows using animation frames. + */ +export class RenderDebouncer implements IDisposable { + private _rowStart: number; + private _rowEnd: number; + private _animationFrame: number = null; + + constructor( + private _terminal: ITerminal, + private _callback: (start: number, end: number) => void + ) { + } + + public dispose(): void { + if (this._animationFrame) { + window.cancelAnimationFrame(this._animationFrame); + this._animationFrame = null; + } + } + + public refresh(rowStart?: number, rowEnd?: number): void { + rowStart = rowStart || 0; + rowEnd = rowEnd || this._terminal.rows - 1; + this._rowStart = this._rowStart ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd ? Math.max(this._rowEnd, rowEnd) : rowEnd; + + if (this._animationFrame) { + return; + } + + this._animationFrame = window.requestAnimationFrame(() => this._innerRefresh()); + } + + private _innerRefresh(): void { + // Clamp values + this._rowStart = Math.max(this._rowStart, 0); + this._rowEnd = Math.min(this._rowEnd, this._terminal.rows - 1); + + // Run render callback + this._callback(this._rowStart, this._rowEnd); + + // Reset debouncer + this._rowStart = null; + this._rowEnd = null; + this._animationFrame = null; + } +} From e20d2caa0e3183a3d7f6982be309a2ed46faad52 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 5 Jan 2018 08:19:30 -0800 Subject: [PATCH 16/86] Use consistent refresh/render method names --- src/Terminal.ts | 2 +- src/renderer/Interfaces.ts | 2 +- src/renderer/Renderer.ts | 6 +++--- src/utils/TestUtils.test.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 833c18c61..dbffb8a08 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1041,7 +1041,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public refresh(start: number, end: number): void { if (this.renderer) { - this.renderer.queueRefresh(start, end); + this.renderer.refreshRows(start, end); } } diff --git a/src/renderer/Interfaces.ts b/src/renderer/Interfaces.ts index be1b39dd1..74f80a580 100644 --- a/src/renderer/Interfaces.ts +++ b/src/renderer/Interfaces.ts @@ -19,7 +19,7 @@ export interface IRenderer extends IEventEmitter { onCursorMove(): void; onOptionsChanged(): void; clear(): void; - queueRefresh(start: number, end: number): void; + refreshRows(start: number, end: number): void; } export interface IRenderLayer { diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 3d2a4d954..26a57033b 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -52,7 +52,7 @@ export class Renderer extends EventEmitter implements IRenderer { }; this._devicePixelRatio = window.devicePixelRatio; this._updateDimensions(); - this._renderDebouncer = new RenderDebouncer(this._terminal, this._refreshLoop.bind(this)); + this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); } public onWindowResize(devicePixelRatio: number): void { @@ -128,7 +128,7 @@ export class Renderer extends EventEmitter implements IRenderer { * @param {number} start The start row. * @param {number} end The end row. */ - public queueRefresh(start: number, end: number): void { + public refreshRows(start: number, end: number): void { this._renderDebouncer.refresh(start, end); } @@ -136,7 +136,7 @@ export class Renderer extends EventEmitter implements IRenderer { * Performs the refresh loop callback, calling refresh only if a refresh is * necessary before queueing up the next one. */ - private _refreshLoop(start: number, end: number): void { + private _renderRows(start: number, end: number): void { this._renderLayers.forEach(l => l.onGridChanged(this._terminal, start, end)); this._terminal.emit('refresh', {start, end}); } diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 80e252774..ee28bd112 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -240,7 +240,7 @@ export class MockRenderer implements IRenderer { onOptionsChanged(): void {} onWindowResize(devicePixelRatio: number): void {} clear(): void {} - queueRefresh(start: number, end: number): void {} + refreshRows(start: number, end: number): void {} } export class MockViewport implements IViewport { From a19b05f8139754069fdd2bf0387b6111f92905ac Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Mon, 8 Jan 2018 09:53:06 -0800 Subject: [PATCH 17/86] Fix bad type check that caused rows to not refresh --- src/utils/RenderDebouncer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/RenderDebouncer.ts b/src/utils/RenderDebouncer.ts index c494fb98e..5a9c93d6d 100644 --- a/src/utils/RenderDebouncer.ts +++ b/src/utils/RenderDebouncer.ts @@ -24,8 +24,8 @@ export class RenderDebouncer implements IDisposable { public refresh(rowStart?: number, rowEnd?: number): void { rowStart = rowStart || 0; rowEnd = rowEnd || this._terminal.rows - 1; - this._rowStart = this._rowStart ? Math.min(this._rowStart, rowStart) : rowStart; - this._rowEnd = this._rowEnd ? Math.max(this._rowEnd, rowEnd) : rowEnd; + this._rowStart = this._rowStart !== null ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== null ? Math.max(this._rowEnd, rowEnd) : rowEnd; if (this._animationFrame) { return; From b75f52734d182f546cfba074d6ee25ec4f8d5706 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 8 Jan 2018 20:53:26 +0000 Subject: [PATCH 18/86] Use Web audio API --- src/Interfaces.ts | 3 +++ src/SoundManager.ts | 61 +++++++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 38 +++++++--------------------- src/utils/Sounds.ts | 10 -------- 4 files changed, 73 insertions(+), 39 deletions(-) create mode 100644 src/SoundManager.ts delete mode 100644 src/utils/Sounds.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts index f90cb8c04..e9a347d86 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -352,3 +352,6 @@ export interface ITheme { brightCyan?: string; brightWhite?: string; } +export interface ISoundManager { + playBellSound(): void; +} diff --git a/src/SoundManager.ts b/src/SoundManager.ts new file mode 100644 index 000000000..b95a253e5 --- /dev/null +++ b/src/SoundManager.ts @@ -0,0 +1,61 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +import { ITerminal, ISoundManager } from './Interfaces'; + +// Source: https://freesound.org/people/altemark/sounds/45759/ +// This sound is released under the Creative Commons Attribution 3.0 Unported +// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been +// made, apart from the conversion to base64. +export const DefaultBellSound = '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 _terminal: ITerminal; + private _audioContext: AudioContext; + + constructor(_terminal: ITerminal) { + this._terminal = _terminal; + } + + public playBellSound(): void { + if (!this._audioContext) { + this._audioContext = new (window.AudioContext || window.webkitAudioContext)(); + } + + if (this._audioContext) { + let bellAudioSource = this._audioContext.createBufferSource(); + let context = this._audioContext; + this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), function (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'); + } + } + + private base64ToArrayBuffer(base64: string): ArrayBuffer { + const binaryString = window.atob(base64); + const len = binaryString.length; + let bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + return bytes.buffer; + } + + private removeMimeType(dataURI: string): string { + // Split the input to get the mime-type and the data itself + const SplitURI = dataURI.split(','); + + // Return only the data + return SplitURI[1]; + } +} diff --git a/src/Terminal.ts b/src/Terminal.ts index f95cc0eaf..f12f42b7d 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -40,7 +40,7 @@ import { MouseHelper } from './utils/MouseHelper'; import { CHARSETS } from './Charsets'; import { CustomKeyEventHandler, Charset, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; import { ITerminal, IBrowser, ITerminalOptions, IInputHandlingTerminal, ILinkMatcherOptions, IViewport, ICompositionHelper, ITheme, ILinkifier } from './Interfaces'; -import { BellSound } from './utils/Sounds'; +import { DefaultBellSound, SoundManager } from './SoundManager'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { IMouseZoneManager } from './input/Interfaces'; import { MouseZoneManager } from './input/MouseZoneManager'; @@ -70,7 +70,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { termName: 'xterm', cursorBlink: false, cursorStyle: 'block', - bellSound: BellSound, + bellSound: DefaultBellSound, bellStyle: 'none', enableBold: true, fontFamily: 'courier-new, courier, monospace', @@ -105,7 +105,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private helperContainer: HTMLElement; private compositionView: HTMLElement; private charSizeStyleElement: HTMLStyleElement; - private bellAudioElement: HTMLAudioElement; + private visualBellTimer: number; public browser: IBrowser = Browser; @@ -187,9 +187,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private userScrolling: boolean; private inputHandler: InputHandler; + public soundManager: SoundManager; private parser: Parser; public renderer: IRenderer; - public selectionManager: SelectionManager; public linkifier: ILinkifier; public buffers: BufferSet; public buffer: Buffer; @@ -198,6 +198,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT public charMeasure: CharMeasure; private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; + public selectionManager: SelectionManager; public cols: number; public rows: number; @@ -290,6 +291,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.selectionManager = this.selectionManager || null; this.linkifier = this.linkifier || new Linkifier(this); this._mouseZoneManager = this._mouseZoneManager || null; + this.soundManager = this.soundManager || new SoundManager(this); // Create the terminal's buffers and set the current buffer this.buffers = new BufferSet(this); @@ -424,8 +426,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewport.syncScrollArea(); break; case 'tabStopWidth': this.buffers.setupTabStops(); break; - case 'bellSound': - case 'bellStyle': this.syncBellSound(); break; } // Inform renderer of changes if (this.renderer) { @@ -622,9 +622,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.helperContainer.appendChild(this.charSizeStyleElement); this.charMeasure = new CharMeasure(document, this.helperContainer); - // Preload audio, this relied on helperContainer - this.syncBellSound(); - // Performance: Add viewport and helper elements from the fragment this.element.appendChild(fragment); @@ -1797,7 +1794,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT */ public bell(): void { this.emit('bell'); - if (this.soundBell()) this.bellAudioElement.play(); + if (this.soundBell()) { + this.soundManager.playBellSound(); + } if (this.visualBell()) { this.element.classList.add('visual-bell-active'); @@ -2117,25 +2116,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.options.bellStyle === 'both'; } - private syncBellSound(): void { - // Don't update anything if the terminal has not been opened yet - if (!this.element) { - return; - } - - if (this.soundBell() && this.bellAudioElement) { - this.bellAudioElement.setAttribute('src', this.options.bellSound); - } else if (this.soundBell()) { - this.bellAudioElement = document.createElement('audio'); - this.bellAudioElement.setAttribute('preload', 'auto'); - this.bellAudioElement.setAttribute('src', this.options.bellSound); - this.helperContainer.appendChild(this.bellAudioElement); - } else if (this.bellAudioElement) { - this.helperContainer.removeChild(this.bellAudioElement); - } - } -} - /** * Helpers */ diff --git a/src/utils/Sounds.ts b/src/utils/Sounds.ts deleted file mode 100644 index 03f36f426..000000000 --- a/src/utils/Sounds.ts +++ /dev/null @@ -1,10 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -// Source: https://freesound.org/people/altemark/sounds/45759/ -// This sound is released under the Creative Commons Attribution 3.0 Unported -// (CC BY 3.0) license. It was created by 'altemark'. No modifications have been -// made, apart from the conversion to base64. -export const BellSound = 'data:audio/wav;base64,UklGRigBAABXQVZFZm10IBAAAAABAAEARKwAAIhYAQACABAAZGF0YQQBAADpAFgCwAMlBZoG/wdmCcoKRAypDQ8PbRDBEQQTOxRtFYcWlBePGIUZXhoiG88bcBz7HHIdzh0WHlMeZx51HmkeUx4WHs8dah0AHXwc3hs9G4saxRnyGBIYGBcQFv8U4RPAEoYRQBACD70NWwwHC6gJOwjWBloF7gOBAhABkf8b/qv8R/ve+Xf4Ife79W/0JfPZ8Z/wde9N7ijtE+wU6xvqM+lb6H7nw+YX5mrlxuQz5Mzje+Ma49fioeKD4nXiYeJy4pHitOL04j/jn+MN5IPkFOWs5U3mDefM55/ogOl36m7rdOyE7abuyu8D8Unyj/Pg9D/2qfcb+Yn6/vuK/Qj/lAAlAg=='; From bbc9a010b9584dedea98ec7cda515662146f30ec Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Jan 2018 09:27:37 -0800 Subject: [PATCH 19/86] Listen to dpr change in a11y manager --- src/AccessibilityManager.ts | 4 ++++ src/Terminal.ts | 11 +++++++++++ src/renderer/Renderer.ts | 5 ----- 3 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index ed1904000..9b790abf5 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -66,6 +66,10 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); this._addTerminalEventListener('blur', () => this._clearLiveRegion()); + // TODO: Maybe renderer should fire an event on terminal when the characters change and that + // should be listened to instead? That would mean that the order of events are always + // guarenteed + this._addTerminalEventListener('dprchange', () => this._refreshRowsDimensions()); // TODO: Dispose of this listener when disposed // TODO: Listen instead to when devicePixelRatio changed (depends on PR #1172) window.addEventListener('resize', () => this._refreshRowsDimensions()); diff --git a/src/Terminal.ts b/src/Terminal.ts index 118b3fc14..9e338c625 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,6 +47,7 @@ import { MouseZoneManager } from './input/MouseZoneManager'; import { initialize as initializeCharAtlas } from './renderer/CharAtlas'; import { IRenderer } from './renderer/Interfaces'; import { AccessibilityManager } from './AccessibilityManager'; +import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -201,6 +202,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT private _mouseZoneManager: IMouseZoneManager; public mouseHelper: MouseHelper; private _accessibilityManager: AccessibilityManager; + private _screenDprMonitor: ScreenDprMonitor; public cols: number; public rows: number; @@ -586,6 +588,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT initializeCharAtlas(this.document); + this._screenDprMonitor = new ScreenDprMonitor(); + this._screenDprMonitor.setListener(() => this.emit('dprchange', window.devicePixelRatio)); + // Create main element container this.element = this.document.createElement('div'); this.element.classList.add('terminal'); @@ -647,6 +652,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.on('resize', () => this.renderer.onResize(this.cols, this.rows, false)); this.on('blur', () => this.renderer.onBlur()); this.on('focus', () => this.renderer.onFocus()); + this.on('dprchange', () => this.renderer.onWindowResize(window.devicePixelRatio)); + // dprchange should handle this case, we need this as well for browsers that don't support the + // matchMedia query. + window.addEventListener('resize', () => this.renderer.onWindowResize(window.devicePixelRatio)); this.charMeasure.on('charsizechanged', () => this.renderer.onResize(this.cols, this.rows, true)); this.renderer.on('resize', (dimensions) => this.viewport.syncScrollArea()); @@ -670,6 +679,8 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.mouseHelper = new MouseHelper(this.renderer); if (this.options.screenReaderMode) { + // Note that this must be done *after* the renderer is created in order to + // ensure the correct order of the dprchange event this._accessibilityManager = new AccessibilityManager(this); } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index e828fbb9c..dda4b5682 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -14,14 +14,12 @@ import { IRenderLayer, IColorSet, IRenderer, IRenderDimensions } from './Interfa import { LinkRenderLayer } from './LinkRenderLayer'; import { EventEmitter } from '../EventEmitter'; import { RenderDebouncer } from '../utils/RenderDebouncer'; -import { ScreenDprMonitor } from '../utils/ScreenDprMonitor'; export class Renderer extends EventEmitter implements IRenderer { private _renderDebouncer: RenderDebouncer; private _renderLayers: IRenderLayer[]; private _devicePixelRatio: number; - private _screenDprMonitor: ScreenDprMonitor; private _isPaused: boolean = false; private _needsFullRefresh: boolean = false; @@ -58,9 +56,6 @@ export class Renderer extends EventEmitter implements IRenderer { this._updateDimensions(); this._renderDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); - this._screenDprMonitor = new ScreenDprMonitor(); - this._screenDprMonitor.setListener(() => this.onWindowResize(window.devicePixelRatio)); - // Detect whether IntersectionObserver is detected and enable renderer pause // and resume based on terminal visibility if so if ('IntersectionObserver' in window) { From 2311d7d66bb9fab873657190081e3bae34ba04a1 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Jan 2018 22:41:23 -0800 Subject: [PATCH 20/86] Add basic navigation mode support for current viewport --- src/AccessibilityManager.ts | 91 +++++++++++++++++++++++++++++++++++-- src/Terminal.ts | 7 +++ src/xterm.css | 8 +++- 3 files changed, 100 insertions(+), 6 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 9b790abf5..47839a708 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -8,8 +8,13 @@ import { isMac } from './utils/Browser'; import { RenderDebouncer } from './utils/RenderDebouncer'; const MAX_ROWS_TO_READ = 20; +const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-'; export class AccessibilityManager implements IDisposable { + private _activeItemId: string; + private _isNavigationModeActive: boolean = false; + private _navigationModeFocusedRow: number; + private _accessibilityTreeRoot: HTMLElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; @@ -32,12 +37,13 @@ export class AccessibilityManager implements IDisposable { private _charsToConsume: string[] = []; constructor(private _terminal: ITerminal) { + this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000)); this._accessibilityTreeRoot = document.createElement('div'); - this._accessibilityTreeRoot.classList.add('accessibility'); + this._accessibilityTreeRoot.classList.add('xterm-accessibility'); this._rowContainer = document.createElement('div'); - this._rowContainer.classList.add('accessibility-tree'); + this._rowContainer.classList.add('xterm-accessibility-tree'); for (let i = 0; i < this._terminal.rows; i++) { - this._rowElements[i] = document.createElement('div'); + this._rowElements[i] = this._createAccessibilityTreeNode(); this._rowContainer.appendChild(this._rowElements[i]); } this._refreshRowsDimensions(); @@ -73,6 +79,41 @@ export class AccessibilityManager implements IDisposable { // TODO: Dispose of this listener when disposed // TODO: Listen instead to when devicePixelRatio changed (depends on PR #1172) window.addEventListener('resize', () => this._refreshRowsDimensions()); + + this._rowContainer.addEventListener('keyup', e => { + switch (e.keyCode) { + case 27: // Escape + this.leaveNavigationMode(); + break; + // TODO: Jump up/down to next non-blank row + case 38: /*ArrowUp*/ + this._navigateToElement(this._navigationModeFocusedRow - 1); + break; + case 40: /*ArrowDown*/ + this._navigateToElement(this._navigationModeFocusedRow + 1); + break; + } + this._rowContainer.focus(); + console.log('keydown2', e); + e.preventDefault(); + e.stopPropagation(); + return true; + + // no handler + //return false; + }); + this._rowContainer.addEventListener('keydown', e => { + if (this._isNavigationModeActive) { + e.preventDefault(); + e.stopPropagation(); + return true; + } + return false; + }); + } + + public get isNavigationModeActive(): boolean { + return this._isNavigationModeActive; } private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { @@ -99,7 +140,7 @@ export class AccessibilityManager implements IDisposable { private _onResize(cols: number, rows: number): void { // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { - this._rowElements[i] = document.createElement('div'); + this._rowElements[i] = this._createAccessibilityTreeNode(); this._rowContainer.appendChild(this._rowElements[i]); } // Shrink rows as required @@ -110,6 +151,12 @@ export class AccessibilityManager implements IDisposable { this._refreshRowsDimensions(); } + private _createAccessibilityTreeNode(): HTMLElement { + const element = document.createElement('div'); + element.setAttribute('role', 'menuitem'); + return element; + } + private _onChar(char: string): void { if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { // \n needs to be printed as a space, otherwise it will be collapsed to @@ -179,6 +226,7 @@ export class AccessibilityManager implements IDisposable { for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); this._rowElements[i].textContent = lineData; + this._rowElements[i].setAttribute('aria-label', lineData); } } @@ -189,4 +237,39 @@ export class AccessibilityManager implements IDisposable { this._rowElements[i].style.height = `${dimensions.actualCellHeight}px`; } } + + public enterNavigationMode(): void { + this._isNavigationModeActive = true; + this._clearLiveRegion(); + this._liveRegion.textContent += 'Entered line navigation mode'; + this._rowContainer.tabIndex = 0; + this._rowContainer.setAttribute('role', 'menu'); + this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); + this._rowContainer.focus(); + this._navigateToElement(this._terminal.buffer.y); + } + + public leaveNavigationMode(): void { + this._isNavigationModeActive = false; + this._liveRegion.textContent += 'Left line navigation mode'; + this._rowContainer.removeAttribute('tabindex'); + this._rowContainer.removeAttribute('aria-activedescendant'); + this._rowContainer.removeAttribute('role'); + const selected = document.querySelector('#' + this._activeItemId); + if (selected) { + selected.removeAttribute('id'); + } + this._terminal.textarea.focus(); + } + + private _navigateToElement(row: number): void { + // TODO: Store this state + const selected = document.querySelector('#' + this._activeItemId); + if (selected) { + selected.removeAttribute('id'); + } + this._navigationModeFocusedRow = row; + const selectedElement = this._rowElements[row]; + selectedElement.id = this._activeItemId; + } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 9e338c625..5fbd2b976 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -454,6 +454,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Binds the desired focus behavior on a given terminal object. */ private _onTextAreaFocus(): void { + console.log('textarea.focus'); if (this.sendFocus) { this.send(C0.ESC + '[I'); } @@ -1374,6 +1375,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {KeyboardEvent} ev The keydown event to be handled. */ protected _keyDown(ev: KeyboardEvent): boolean { + console.log('a'); + if (this._accessibilityManager && this._accessibilityManager.isNavigationModeActive) { + console.log('b'); + return; + } + if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { return false; } diff --git a/src/xterm.css b/src/xterm.css index 68d0fd7d5..583aa9071 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -124,7 +124,7 @@ cursor: text; } -.xterm .accessibility { +.xterm .xterm-accessibility { position: absolute; left: 0; top: 0; @@ -133,10 +133,14 @@ z-index: 100; } -.xterm .accessibility-tree { +.xterm .xterm-accessibility-tree { color: transparent; } +.xterm .xterm-accessibility-tree:focus [id^="xterm-active-item-"] { + outline: 1px solid #F80; +} + .xterm .live-region { position: absolute; left: -9999px; From 2a187a26e02f7437d289426b407c16c40a783d3d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 11 Jan 2018 23:15:02 -0800 Subject: [PATCH 21/86] Update a11y rows on scroll --- src/AccessibilityManager.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 47839a708..87dbd3c0d 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -60,6 +60,7 @@ export class AccessibilityManager implements IDisposable { this._addTerminalEventListener('resize', data => this._onResize(data.cols, data.rows)); this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); + this._addTerminalEventListener('scroll', data => this._refreshRows()); // Line feed is an issue as the prompt won't be read out after a command is run this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); this._addTerminalEventListener('linefeed', () => this._onChar('\n')); @@ -224,7 +225,7 @@ export class AccessibilityManager implements IDisposable { private _renderRows(start: number, end: number): void { const buffer: IBuffer = (this._terminal.buffer); for (let i = start; i <= end; i++) { - const lineData = buffer.translateBufferLineToString(buffer.ybase + i, true); + const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); this._rowElements[i].textContent = lineData; this._rowElements[i].setAttribute('aria-label', lineData); } @@ -263,6 +264,15 @@ export class AccessibilityManager implements IDisposable { } private _navigateToElement(row: number): void { + if (row < 0) { + if (this._terminal.buffer.ydisp > 0) { + this._terminal.scrollLines(-1); + row = 0; + // TODO: This doesn't reliably read the element, maybe a new row needs to be inserted at the top? + // Exit early since the same element is focused + return; + } + } // TODO: Store this state const selected = document.querySelector('#' + this._activeItemId); if (selected) { From 1e41b113195ae7da84fc4a1d2a7f7305def98075 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 12 Jan 2018 21:15:53 -0800 Subject: [PATCH 22/86] Remove logs --- src/AccessibilityManager.ts | 1 - src/Terminal.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 87dbd3c0d..5a27d34f5 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -95,7 +95,6 @@ export class AccessibilityManager implements IDisposable { break; } this._rowContainer.focus(); - console.log('keydown2', e); e.preventDefault(); e.stopPropagation(); return true; diff --git a/src/Terminal.ts b/src/Terminal.ts index 5fbd2b976..619387e29 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -454,7 +454,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * Binds the desired focus behavior on a given terminal object. */ private _onTextAreaFocus(): void { - console.log('textarea.focus'); if (this.sendFocus) { this.send(C0.ESC + '[I'); } @@ -1375,9 +1374,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {KeyboardEvent} ev The keydown event to be handled. */ protected _keyDown(ev: KeyboardEvent): boolean { - console.log('a'); if (this._accessibilityManager && this._accessibilityManager.isNavigationModeActive) { - console.log('b'); return; } From 70489e503576c6f51471cd220968d89b4669c225 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 14 Jan 2018 12:54:27 -0800 Subject: [PATCH 23/86] Move navigation mode stuff into its own class --- src/AccessibilityManager.ts | 124 ++++++++++++++++++++++++------------ 1 file changed, 85 insertions(+), 39 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 5a27d34f5..c854cc2b1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -11,10 +11,6 @@ const MAX_ROWS_TO_READ = 20; const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-'; export class AccessibilityManager implements IDisposable { - private _activeItemId: string; - private _isNavigationModeActive: boolean = false; - private _navigationModeFocusedRow: number; - private _accessibilityTreeRoot: HTMLElement; private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; @@ -22,6 +18,7 @@ export class AccessibilityManager implements IDisposable { private _liveRegionLineCount: number = 0; private _renderRowsDebouncer: RenderDebouncer; + private _navigationMode: NavigationMode; private _disposables: IDisposable[] = []; @@ -37,7 +34,6 @@ export class AccessibilityManager implements IDisposable { private _charsToConsume: string[] = []; constructor(private _terminal: ITerminal) { - this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000)); this._accessibilityTreeRoot = document.createElement('div'); this._accessibilityTreeRoot.classList.add('xterm-accessibility'); this._rowContainer = document.createElement('div'); @@ -50,6 +46,7 @@ export class AccessibilityManager implements IDisposable { this._accessibilityTreeRoot.appendChild(this._rowContainer); this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._navigationMode = new NavigationMode(this._terminal, this._rowContainer, this._rowElements, this); this._liveRegion = document.createElement('div'); this._liveRegion.classList.add('live-region'); @@ -82,38 +79,17 @@ export class AccessibilityManager implements IDisposable { window.addEventListener('resize', () => this._refreshRowsDimensions()); this._rowContainer.addEventListener('keyup', e => { - switch (e.keyCode) { - case 27: // Escape - this.leaveNavigationMode(); - break; - // TODO: Jump up/down to next non-blank row - case 38: /*ArrowUp*/ - this._navigateToElement(this._navigationModeFocusedRow - 1); - break; - case 40: /*ArrowDown*/ - this._navigateToElement(this._navigationModeFocusedRow + 1); - break; - } - this._rowContainer.focus(); - e.preventDefault(); - e.stopPropagation(); - return true; - - // no handler - //return false; - }); - this._rowContainer.addEventListener('keydown', e => { - if (this._isNavigationModeActive) { - e.preventDefault(); - e.stopPropagation(); - return true; + if (this._navigationMode.isActive) { + return this._navigationMode.onKeyUp(e); + } + return false; + }); + this._rowContainer.addEventListener('keydown', e => { + if (this._navigationMode.isActive) { + return this._navigationMode.onKeyDown(e); } return false; }); - } - - public get isNavigationModeActive(): boolean { - return this._isNavigationModeActive; } private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { @@ -238,10 +214,29 @@ export class AccessibilityManager implements IDisposable { } } - public enterNavigationMode(): void { - this._isNavigationModeActive = true; + public announce(text: string): void { this._clearLiveRegion(); - this._liveRegion.textContent += 'Entered line navigation mode'; + this._liveRegion.textContent = text; + } +} + +class NavigationMode { + private _activeItemId: string; + private _isNavigationModeActive: boolean = false; + private _navigationModeFocusedRow: number; + + constructor( + private _terminal: ITerminal, + private _rowContainer: HTMLElement, + private _rowElements: HTMLElement[], + private _accessibilityManager: AccessibilityManager + ) { + this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000)); + } + + public enter(): void { + this._isNavigationModeActive = true; + this._accessibilityManager.announce('Entered line navigation mode'); this._rowContainer.tabIndex = 0; this._rowContainer.setAttribute('role', 'menu'); this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); @@ -249,9 +244,9 @@ export class AccessibilityManager implements IDisposable { this._navigateToElement(this._terminal.buffer.y); } - public leaveNavigationMode(): void { + public leave(): void { this._isNavigationModeActive = false; - this._liveRegion.textContent += 'Left line navigation mode'; + this._accessibilityManager.announce('Left line navigation mode'); this._rowContainer.removeAttribute('tabindex'); this._rowContainer.removeAttribute('aria-activedescendant'); this._rowContainer.removeAttribute('role'); @@ -262,6 +257,57 @@ export class AccessibilityManager implements IDisposable { this._terminal.textarea.focus(); } + public get isActive(): boolean { + return this._isNavigationModeActive; + } + + public onKeyDown(e: KeyboardEvent): boolean { + return this._onKey(e, e => { + console.log('keydown', e); + if (this._isNavigationModeActive) { + return true; + } + return false; + }); + } + + public onKeyUp(e: KeyboardEvent): boolean { + return this._onKey(e, e => { + console.log('keyup', e); + switch (e.keyCode) { + case 27: return this._onEscape(e); + case 38: return this._onArrowUp(e); + case 40: return this._onArrowDown(e); + } + return false; + }); + } + + private _onKey(e: KeyboardEvent, handler: (e: KeyboardEvent) => boolean): boolean { + if (handler && handler(e)) { + return true; + } + return false; + } + + private _onEscape(e: KeyboardEvent): boolean { + this.leave(); + return true; + } + + private _onArrowUp(e: KeyboardEvent): boolean { + // TODO: Jump up/down to next non-blank row + this._navigateToElement(this._navigationModeFocusedRow - 1); + this._rowContainer.focus(); + return true; + } + + private _onArrowDown(e: KeyboardEvent): boolean { + this._navigateToElement(this._navigationModeFocusedRow + 1); + this._rowContainer.focus(); + return true; + } + private _navigateToElement(row: number): void { if (row < 0) { if (this._terminal.buffer.ydisp > 0) { From 85c02f10bffee6334907020779c39ee838611afb Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 14 Jan 2018 13:18:00 -0800 Subject: [PATCH 24/86] Improve dispoable listener functions --- src/AccessibilityManager.ts | 44 +++++++++++++++---------------------- src/EventEmitter.ts | 21 +++++++++++++++++- src/Interfaces.ts | 1 + src/utils/Dom.ts | 26 ++++++++++++++++++++++ 4 files changed, 65 insertions(+), 27 deletions(-) create mode 100644 src/utils/Dom.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index c854cc2b1..76595c869 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -6,6 +6,7 @@ import { ITerminal, IBuffer, IDisposable } from './Interfaces'; import { isMac } from './utils/Browser'; import { RenderDebouncer } from './utils/RenderDebouncer'; +import { addDisposableListener } from './utils/Dom'; const MAX_ROWS_TO_READ = 20; const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-'; @@ -55,28 +56,22 @@ export class AccessibilityManager implements IDisposable { this._terminal.element.appendChild(this._accessibilityTreeRoot); - this._addTerminalEventListener('resize', data => this._onResize(data.cols, data.rows)); - this._addTerminalEventListener('refresh', data => this._refreshRows(data.start, data.end)); - this._addTerminalEventListener('scroll', data => this._refreshRows()); + this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)); + this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)); + this._terminal.addDisposableListener('scroll', data => this._refreshRows()); // Line feed is an issue as the prompt won't be read out after a command is run - this._addTerminalEventListener('a11y.char', (char) => this._onChar(char)); - this._addTerminalEventListener('linefeed', () => this._onChar('\n')); - // Ensure \t is covered, if not 2 words separated by only a tab will be read as 1 word - this._addTerminalEventListener('a11y.tab', spaceCount => { - for (let i = 0; i < spaceCount; i++) { - this._onChar(' '); - } - }); - this._addTerminalEventListener('charsizechanged', () => this._refreshRowsDimensions()); - this._addTerminalEventListener('key', keyChar => this._onKey(keyChar)); - this._addTerminalEventListener('blur', () => this._clearLiveRegion()); + this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)); + this._terminal.addDisposableListener('linefeed', () => this._onChar('\n')); + this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)); + this._terminal.addDisposableListener('charsizechanged', () => this._refreshRowsDimensions()); + this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar)); + this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()); // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always // guarenteed - this._addTerminalEventListener('dprchange', () => this._refreshRowsDimensions()); + this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions()); // TODO: Dispose of this listener when disposed - // TODO: Listen instead to when devicePixelRatio changed (depends on PR #1172) - window.addEventListener('resize', () => this._refreshRowsDimensions()); + addDisposableListener(window, 'resize', () => this._refreshRowsDimensions()); this._rowContainer.addEventListener('keyup', e => { if (this._navigationMode.isActive) { @@ -92,15 +87,6 @@ export class AccessibilityManager implements IDisposable { }); } - private _addTerminalEventListener(type: string, listener: (...args: any[]) => any): void { - this._terminal.on(type, listener); - this._disposables.push({ - dispose: () => { - this._terminal.off(type, listener); - } - }); - } - public dispose(): void { this._renderRowsDebouncer.dispose(); this._terminal.element.removeChild(this._accessibilityTreeRoot); @@ -133,6 +119,12 @@ export class AccessibilityManager implements IDisposable { return element; } + private _onTab(spaceCount: number): void { + for (let i = 0; i < spaceCount; i++) { + this._onChar(' '); + } + } + private _onChar(char: string): void { if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { // \n needs to be printed as a space, otherwise it will be collapsed to diff --git a/src/EventEmitter.ts b/src/EventEmitter.ts index 414eac895..7f090ac1a 100644 --- a/src/EventEmitter.ts +++ b/src/EventEmitter.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { IEventEmitter, IListenerType } from './Interfaces'; +import { IEventEmitter, IListenerType, IDisposable } from './Interfaces'; export class EventEmitter implements IEventEmitter { private _events: {[type: string]: IListenerType[]}; @@ -19,6 +19,25 @@ export class EventEmitter implements IEventEmitter { this._events[type].push(listener); } + /** + * Adds a disposabe listener to the EventEmitter, returning the disposable. + * @param type The event type. + * @param handler The handler for the listener. + */ + public addDisposableListener(type: string, handler: IListenerType): IDisposable { + this.on(type, handler); + return { + dispose: () => { + if (!handler) { + // Already disposed + return; + } + this.off(type, handler); + handler = null; + } + }; + } + public off(type: string, listener: IListenerType): void { if (!this._events[type]) { return; diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 423f8d114..780a6b4e6 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -244,6 +244,7 @@ export interface IEventEmitter { on(type: string, listener: IListenerType): void; off(type: string, listener: IListenerType): void; emit(type: string, data?: any): void; + addDisposableListener(type: string, handler: IListenerType): IDisposable; } export interface IListenerType { diff --git a/src/utils/Dom.ts b/src/utils/Dom.ts new file mode 100644 index 000000000..d32f37906 --- /dev/null +++ b/src/utils/Dom.ts @@ -0,0 +1,26 @@ +import { IDisposable } from "../Interfaces"; + +/** + * Adds a disposabe listener to a node in the DOM, returning the disposable. + * @param type The event type. + * @param handler The handler for the listener. + */ +export function addDisposableListener( + node: Element | Window | Document, + type: string, + handler: (e: any) => void, + useCapture?: boolean +): IDisposable { + node.addEventListener(type, handler, useCapture); + return { + dispose: () => { + if (!handler) { + // Already disposed + return; + } + node.removeEventListener(type, handler, useCapture); + node = null; + handler = null; + } + }; +} From 23bd60a733ffc05e396b081d97816f8f85db6b73 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 14 Jan 2018 13:28:43 -0800 Subject: [PATCH 25/86] Properly dispose of disposables --- src/AccessibilityManager.ts | 58 +++++++++++++++++++++---------------- 1 file changed, 33 insertions(+), 25 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 76595c869..ac1cdd37e 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -56,35 +56,23 @@ export class AccessibilityManager implements IDisposable { this._terminal.element.appendChild(this._accessibilityTreeRoot); - this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows)); - this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end)); - this._terminal.addDisposableListener('scroll', data => this._refreshRows()); + this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); + this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); + this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); // Line feed is an issue as the prompt won't be read out after a command is run - this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char)); - this._terminal.addDisposableListener('linefeed', () => this._onChar('\n')); - this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount)); - this._terminal.addDisposableListener('charsizechanged', () => this._refreshRowsDimensions()); - this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar)); - this._terminal.addDisposableListener('blur', () => this._clearLiveRegion()); + this._disposables.push(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); + this._disposables.push(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); + this._disposables.push(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); + this._disposables.push(this._terminal.addDisposableListener('charsizechanged', () => this._refreshRowsDimensions())); + this._disposables.push(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar))); + this._disposables.push(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always // guarenteed - this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions()); - // TODO: Dispose of this listener when disposed - addDisposableListener(window, 'resize', () => this._refreshRowsDimensions()); - - this._rowContainer.addEventListener('keyup', e => { - if (this._navigationMode.isActive) { - return this._navigationMode.onKeyUp(e); - } - return false; - }); - this._rowContainer.addEventListener('keydown', e => { - if (this._navigationMode.isActive) { - return this._navigationMode.onKeyDown(e); - } - return false; - }); + this._disposables.push(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions())); + // This shouldn't be needed on modern browsers but is present in case the + // media query that drives the dprchange event isn't supported + this._disposables.push(addDisposableListener(window, 'resize', () => this._refreshRowsDimensions())); } public dispose(): void { @@ -217,6 +205,8 @@ class NavigationMode { private _isNavigationModeActive: boolean = false; private _navigationModeFocusedRow: number; + private _disposables: IDisposable[] = []; + constructor( private _terminal: ITerminal, private _rowContainer: HTMLElement, @@ -224,6 +214,24 @@ class NavigationMode { private _accessibilityManager: AccessibilityManager ) { this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000)); + + this._disposables.push(addDisposableListener(this._rowContainer, 'keyup', e => { + if (this.isActive) { + return this.onKeyUp(e); + } + return false; + })); + this._disposables.push(addDisposableListener(this._rowContainer, 'keydown', e => { + if (this.isActive) { + return this.onKeyDown(e); + } + return false; + })); + } + + public dispose(): void { + this._disposables.forEach(d => d.dispose); + this._disposables = null; } public enter(): void { From 81b4d8fe9c17a713b76e76a66bce5119ca8913ba Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 14 Jan 2018 13:42:36 -0800 Subject: [PATCH 26/86] Dispose of NavigationMode when AccessibilityManager is --- src/AccessibilityManager.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index ac1cdd37e..bb6e08d41 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -56,6 +56,8 @@ export class AccessibilityManager implements IDisposable { this._terminal.element.appendChild(this._accessibilityTreeRoot); + this._disposables.push(this._renderRowsDebouncer); + this._disposables.push(this._navigationMode); this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); @@ -76,15 +78,14 @@ export class AccessibilityManager implements IDisposable { } public dispose(): void { - this._renderRowsDebouncer.dispose(); this._terminal.element.removeChild(this._accessibilityTreeRoot); + this._disposables.forEach(d => d.dispose()); + this._disposables = null; this._accessibilityTreeRoot = null; this._rowContainer = null; this._liveRegion = null; this._rowContainer = null; this._rowElements = null; - this._disposables.forEach(d => d.dispose()); - this._disposables = null; } private _onResize(cols: number, rows: number): void { @@ -115,9 +116,6 @@ export class AccessibilityManager implements IDisposable { private _onChar(char: string): void { if (this._liveRegionLineCount < MAX_ROWS_TO_READ + 1) { - // \n needs to be printed as a space, otherwise it will be collapsed to - // "" in the DOM and the last and first words of the rows will be read - // as a single word if (this._charsToConsume.length > 0) { // Have the screen reader ignore the char if it was just input const shiftedChar = this._charsToConsume.shift(); @@ -200,7 +198,7 @@ export class AccessibilityManager implements IDisposable { } } -class NavigationMode { +class NavigationMode implements IDisposable { private _activeItemId: string; private _isNavigationModeActive: boolean = false; private _navigationModeFocusedRow: number; @@ -230,7 +228,7 @@ class NavigationMode { } public dispose(): void { - this._disposables.forEach(d => d.dispose); + this._disposables.forEach(d => d.dispose()); this._disposables = null; } @@ -263,7 +261,6 @@ class NavigationMode { public onKeyDown(e: KeyboardEvent): boolean { return this._onKey(e, e => { - console.log('keydown', e); if (this._isNavigationModeActive) { return true; } @@ -273,7 +270,6 @@ class NavigationMode { public onKeyUp(e: KeyboardEvent): boolean { return this._onKey(e, e => { - console.log('keyup', e); switch (e.keyCode) { case 27: return this._onEscape(e); case 38: return this._onArrowUp(e); From e9b55930d89096e3173d67086ff6318e1d820e5c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 10:12:37 -0800 Subject: [PATCH 27/86] Fix test mocks --- src/AccessibilityManager.ts | 4 ++++ src/utils/TestUtils.test.ts | 11 ++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index bb6e08d41..d2fe7e1a1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -88,6 +88,10 @@ export class AccessibilityManager implements IDisposable { this._rowElements = null; } + public get isNavigationModeActive(): boolean { + return this._navigationMode.isActive; + } + private _onResize(cols: number, rows: number): void { // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index ee28bd112..89b03530b 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -3,7 +3,7 @@ * @license MIT */ -import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme, ILinkifier, IMouseHelper } from '../Interfaces'; +import { ITerminal, IBuffer, IBufferSet, IBrowser, ICharMeasure, ISelectionManager, ITerminalOptions, IListenerType, IInputHandlingTerminal, IViewport, ICircularList, ICompositionHelper, ITheme, ILinkifier, IMouseHelper, IDisposable } from '../Interfaces'; import { LineData } from '../Types'; import { Buffer } from '../Buffer'; import * as Browser from './Browser'; @@ -42,6 +42,9 @@ export class MockTerminal implements ITerminal { off(type: string, listener: IListenerType): void { throw new Error('Method not implemented.'); } + addDisposableListener(type: string, handler: IListenerType): IDisposable { + throw new Error('Method not implemented.'); + } scrollLines(disp: number, suppressScrollEvent: boolean): void { throw new Error('Method not implemented.'); } @@ -193,6 +196,9 @@ export class MockInputHandlingTerminal implements IInputHandlingTerminal { emit(type: string, data?: any): void { throw new Error('Method not implemented.'); } + addDisposableListener(type: string, handler: IListenerType): IDisposable { + throw new Error('Method not implemented.'); + } } export class MockBuffer implements IBuffer { @@ -229,6 +235,9 @@ export class MockRenderer implements IRenderer { emit(type: string, data?: any): void { throw new Error('Method not implemented.'); } + addDisposableListener(type: string, handler: IListenerType): IDisposable { + throw new Error('Method not implemented.'); + } dimensions: IRenderDimensions; setTheme(theme: ITheme): IColorSet { return {}; } onResize(cols: number, rows: number, didCharSizeChange: boolean): void {} From 34eb08952c8e78b20ea7109403f244d82c63331c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 10:26:09 -0800 Subject: [PATCH 28/86] Fix RenderDebouncer not refreshing whole viewport when asked --- src/utils/RenderDebouncer.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils/RenderDebouncer.ts b/src/utils/RenderDebouncer.ts index 5a9c93d6d..0d07b1a1e 100644 --- a/src/utils/RenderDebouncer.ts +++ b/src/utils/RenderDebouncer.ts @@ -24,8 +24,8 @@ export class RenderDebouncer implements IDisposable { public refresh(rowStart?: number, rowEnd?: number): void { rowStart = rowStart || 0; rowEnd = rowEnd || this._terminal.rows - 1; - this._rowStart = this._rowStart !== null ? Math.min(this._rowStart, rowStart) : rowStart; - this._rowEnd = this._rowEnd !== null ? Math.max(this._rowEnd, rowEnd) : rowEnd; + this._rowStart = this._rowStart !== undefined ? Math.min(this._rowStart, rowStart) : rowStart; + this._rowEnd = this._rowEnd !== undefined ? Math.max(this._rowEnd, rowEnd) : rowEnd; if (this._animationFrame) { return; From 222c42f7ea7e5155a3b55597a15b006235099ab4 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 10:29:40 -0800 Subject: [PATCH 29/86] Expose screen reader and navigation mode on demo/API --- demo/index.html | 8 ++++++++ demo/main.js | 17 +++++++++++++++-- src/AccessibilityManager.ts | 6 ++++++ src/Terminal.ts | 6 ++++++ typings/xterm.d.ts | 6 ++++++ 5 files changed, 41 insertions(+), 2 deletions(-) diff --git a/demo/index.html b/demo/index.html index 93399f9cd..e0c355d09 100644 --- a/demo/index.html +++ b/demo/index.html @@ -63,6 +63,14 @@ +
+

Accessibility

+

+ +

+

+ +

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

diff --git a/demo/main.js b/demo/main.js index 95b15a0db..df11a27fd 100644 --- a/demo/main.js +++ b/demo/main.js @@ -29,8 +29,10 @@ var terminalContainer = document.getElementById('terminal-container'), cursorStyle: document.querySelector('#option-cursor-style'), scrollback: document.querySelector('#option-scrollback'), tabstopwidth: document.querySelector('#option-tabstopwidth'), - bellStyle: document.querySelector('#option-bell-style') + bellStyle: document.querySelector('#option-bell-style'), + screenReaderMode: document.querySelector('#option-screen-reader-mode') }, + navigationModeElement = document.querySelector('#screen-reader-navigation-mode'), colsElement = document.getElementById('cols'), rowsElement = document.getElementById('rows'); @@ -78,6 +80,16 @@ optionElements.scrollback.addEventListener('change', function () { optionElements.tabstopwidth.addEventListener('change', function () { term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10)); }); +optionElements.screenReaderMode.addEventListener('change', function () { + term.setOption('screenReaderMode', optionElements.screenReaderMode.value); +}); +navigationModeElement.addEventListener('click', function () { + if (term.getOption('screenReaderMode')) { + term.enterNavigationMode(); + } else { + console.warn('screenReaderMode must be true to enter navigation mode'); + } +}); createTerminal(); @@ -89,7 +101,8 @@ function createTerminal() { term = new Terminal({ cursorBlink: optionElements.cursorBlink.checked, scrollback: parseInt(optionElements.scrollback.value, 10), - tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10) + tabStopWidth: parseInt(optionElements.tabstopwidth.value, 10), + screenReaderMode: optionElements.screenReaderMode.checked }); window.term = term; // Expose `term` to window for debugging purposes term.on('resize', function (size) { diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index d2fe7e1a1..930ae9f0d 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -47,6 +47,8 @@ export class AccessibilityManager implements IDisposable { this._accessibilityTreeRoot.appendChild(this._rowContainer); this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); + this._refreshRows(); + this._navigationMode = new NavigationMode(this._terminal, this._rowContainer, this._rowElements, this); this._liveRegion = document.createElement('div'); @@ -92,6 +94,10 @@ export class AccessibilityManager implements IDisposable { return this._navigationMode.isActive; } + public enterNavigationMode(): void { + this._navigationMode.enter(); + } + private _onResize(cols: number, rows: number): void { // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { diff --git a/src/Terminal.ts b/src/Terminal.ts index 619387e29..62dcab8d9 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1334,6 +1334,12 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } + public enterNavigationMode(): void { + if (this._accessibilityManager) { + this._accessibilityManager.enterNavigationMode(); + } + } + /** * Gets whether the terminal has an active selection. */ diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 5ebe578e4..856637344 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -332,6 +332,12 @@ declare module 'xterm' { */ deregisterLinkMatcher(matcherId: number): void; + /** + * Enters screen reader navigation mode. This will only work when + * the screenReaderMode option is true. + */ + enterNavigationMode(): void; + /** * Gets whether the terminal has an active selection. */ From 3fa4b4941fbbbda6178e65eddc0e944c49839b3e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 11:01:45 -0800 Subject: [PATCH 30/86] Support nav mode pgup/pgdown/end/home, check boundaries --- src/AccessibilityManager.ts | 63 +++++++++++++++++++++++-------------- src/Interfaces.ts | 1 + src/Terminal.ts | 22 +++++++++++++ src/utils/TestUtils.test.ts | 3 ++ 4 files changed, 66 insertions(+), 23 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 930ae9f0d..20da65e25 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -211,7 +211,7 @@ export class AccessibilityManager implements IDisposable { class NavigationMode implements IDisposable { private _activeItemId: string; private _isNavigationModeActive: boolean = false; - private _navigationModeFocusedRow: number; + private _absoluteFocusedRow: number; private _disposables: IDisposable[] = []; @@ -243,13 +243,14 @@ class NavigationMode implements IDisposable { } public enter(): void { + // TODO: Should entering navigation mode send ydisp to ybase? this._isNavigationModeActive = true; this._accessibilityManager.announce('Entered line navigation mode'); this._rowContainer.tabIndex = 0; this._rowContainer.setAttribute('role', 'menu'); this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); this._rowContainer.focus(); - this._navigateToElement(this._terminal.buffer.y); + this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); } public leave(): void { @@ -280,10 +281,16 @@ class NavigationMode implements IDisposable { public onKeyUp(e: KeyboardEvent): boolean { return this._onKey(e, e => { - switch (e.keyCode) { - case 27: return this._onEscape(e); - case 38: return this._onArrowUp(e); - case 40: return this._onArrowDown(e); + if (this._isNavigationModeActive) { + switch (e.keyCode) { + case 27: return this._onEscape(e); + case 33: return this._onPageUp(e); + case 34: return this._onPageDown(e); + case 35: return this._onEnd(e); + case 36: return this._onHome(e); + case 38: return this._onArrowUp(e); + case 40: return this._onArrowDown(e); + } } return false; }); @@ -302,35 +309,45 @@ class NavigationMode implements IDisposable { } private _onArrowUp(e: KeyboardEvent): boolean { - // TODO: Jump up/down to next non-blank row - this._navigateToElement(this._navigationModeFocusedRow - 1); - this._rowContainer.focus(); - return true; + return this._focusRow(this._absoluteFocusedRow - 1); } private _onArrowDown(e: KeyboardEvent): boolean { - this._navigateToElement(this._navigationModeFocusedRow + 1); + return this._focusRow(this._absoluteFocusedRow + 1); + } + + private _onPageUp(e: KeyboardEvent): boolean { + return this._focusRow(this._absoluteFocusedRow - this._terminal.rows); + } + + private _onPageDown(e: KeyboardEvent): boolean { + return this._focusRow(this._absoluteFocusedRow + this._terminal.rows); + } + + private _onHome(e: KeyboardEvent): boolean { + return this._focusRow(0); + } + + private _onEnd(e: KeyboardEvent): boolean { + return this._focusRow(this._terminal.buffer.lines.length - 1); + } + + private _focusRow(row: number): boolean { + this._navigateToElement(row); this._rowContainer.focus(); return true; } - private _navigateToElement(row: number): void { - if (row < 0) { - if (this._terminal.buffer.ydisp > 0) { - this._terminal.scrollLines(-1); - row = 0; - // TODO: This doesn't reliably read the element, maybe a new row needs to be inserted at the top? - // Exit early since the same element is focused - return; - } - } + private _navigateToElement(absoluteRow: number): void { + absoluteRow = this._terminal.scrollToRow(absoluteRow); + // TODO: Store this state const selected = document.querySelector('#' + this._activeItemId); if (selected) { selected.removeAttribute('id'); } - this._navigationModeFocusedRow = row; - const selectedElement = this._rowElements[row]; + this._absoluteFocusedRow = absoluteRow; + const selectedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; selectedElement.id = this._activeItemId; } } diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 780a6b4e6..4dc572d1e 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -56,6 +56,7 @@ export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElement */ handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; + scrollToRow(row: number): number; cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; reset(): void; diff --git a/src/Terminal.ts b/src/Terminal.ts index 62dcab8d9..92ae794ff 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1169,6 +1169,28 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } + /** + * Scroll the viewport to an absolute row in the buffer. + * @param absoluteRow The absolute row in the buffer to scroll to. + * @returns The actual absolute row that was scrolled to (including boundary checked). + */ + public scrollToRow(absoluteRow: number): number { + // Ensure value is valid + absoluteRow = Math.max(Math.min(absoluteRow, this.buffer.lines.length - 1), 0); + + // Move viewport as necessary + const relativeRow = absoluteRow - this.buffer.ydisp; + let scrollAmount = 0; + if (relativeRow < 0) { + scrollAmount = relativeRow; + } else if (relativeRow >= this.rows) { + scrollAmount = relativeRow - this.rows + 1; + } + this.scrollLines(scrollAmount); + + return absoluteRow; + } + /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 89b03530b..69adf3a20 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -48,6 +48,9 @@ export class MockTerminal implements ITerminal { scrollLines(disp: number, suppressScrollEvent: boolean): void { throw new Error('Method not implemented.'); } + scrollToRow(absoluteRow: number): number { + throw new Error('Method not implemented.'); + } cancel(ev: Event, force?: boolean): void { throw new Error('Method not implemented.'); } From f48da12c16c357913c47a93071da920977a07801 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 16 Jan 2018 11:24:19 -0800 Subject: [PATCH 31/86] Prevent event propagation when handled by nav mode --- src/AccessibilityManager.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 20da65e25..a2504d3bc 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -298,6 +298,8 @@ class NavigationMode implements IDisposable { private _onKey(e: KeyboardEvent, handler: (e: KeyboardEvent) => boolean): boolean { if (handler && handler(e)) { + e.preventDefault(); + e.stopPropagation(); return true; } return false; From 78d734117b6b14fcfe719d4cfff09f3a56001b81 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 17 Jan 2018 09:56:57 -0800 Subject: [PATCH 32/86] Support aria posinset and setsize --- src/AccessibilityManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a2504d3bc..fc34f41e6 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -190,7 +190,8 @@ export class AccessibilityManager implements IDisposable { for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); this._rowElements[i].textContent = lineData; - this._rowElements[i].setAttribute('aria-label', lineData); + this._rowElements[i].setAttribute('aria-posinset', (buffer.ydisp + i + 1).toString()); + this._rowElements[i].setAttribute('aria-setsize', (buffer.lines.length).toString()); } } From abfa58fca946b315664862b69f395f59f0c0957d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 17 Jan 2018 10:52:12 -0800 Subject: [PATCH 33/86] Keep track of nav mode focused element --- src/AccessibilityManager.ts | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index fc34f41e6..e36efbec9 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -213,6 +213,7 @@ class NavigationMode implements IDisposable { private _activeItemId: string; private _isNavigationModeActive: boolean = false; private _absoluteFocusedRow: number; + private _focusedElement: HTMLElement; private _disposables: IDisposable[] = []; @@ -260,9 +261,8 @@ class NavigationMode implements IDisposable { this._rowContainer.removeAttribute('tabindex'); this._rowContainer.removeAttribute('aria-activedescendant'); this._rowContainer.removeAttribute('role'); - const selected = document.querySelector('#' + this._activeItemId); - if (selected) { - selected.removeAttribute('id'); + if (this._focusedElement) { + this._focusedElement.removeAttribute('id'); } this._terminal.textarea.focus(); } @@ -344,13 +344,11 @@ class NavigationMode implements IDisposable { private _navigateToElement(absoluteRow: number): void { absoluteRow = this._terminal.scrollToRow(absoluteRow); - // TODO: Store this state - const selected = document.querySelector('#' + this._activeItemId); - if (selected) { - selected.removeAttribute('id'); + if (this._focusedElement) { + this._focusedElement.removeAttribute('id'); } this._absoluteFocusedRow = absoluteRow; - const selectedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; - selectedElement.id = this._activeItemId; + this._focusedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; + this._focusedElement.id = this._activeItemId; } } From 0f04f9fbb4951c1a81d210f61d0fd0323e32c1b7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 17 Jan 2018 14:10:03 -0800 Subject: [PATCH 34/86] Rotate rows to ensure an item will be read in nav mode when scrolling --- src/AccessibilityManager.ts | 19 +++++++++++++++++-- src/Interfaces.ts | 2 +- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index e36efbec9..a958d10bc 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -187,14 +187,24 @@ export class AccessibilityManager implements IDisposable { private _renderRows(start: number, end: number): void { const buffer: IBuffer = (this._terminal.buffer); + const setSize = (buffer.lines.length).toString(); for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); this._rowElements[i].textContent = lineData; - this._rowElements[i].setAttribute('aria-posinset', (buffer.ydisp + i + 1).toString()); - this._rowElements[i].setAttribute('aria-setsize', (buffer.lines.length).toString()); + const posInSet = (buffer.ydisp + i + 1).toString(); + this._rowElements[i].setAttribute('aria-posinset', posInSet); + this._rowElements[i].setAttribute('aria-setsize', setSize); } } + public rotateRows(): void { + this._rowContainer.removeChild(this._rowElements.shift()); + const newRowIndex = this._rowElements.length; + this._rowElements[newRowIndex] = this._createAccessibilityTreeNode(); + this._rowContainer.appendChild(this._rowElements[newRowIndex]); + this._refreshRowsDimensions(); + } + private _refreshRowsDimensions(): void { const buffer: IBuffer = (this._terminal.buffer); const dimensions = this._terminal.renderer.dimensions; @@ -342,6 +352,11 @@ class NavigationMode implements IDisposable { } private _navigateToElement(absoluteRow: number): void { + if (absoluteRow < this._terminal.buffer.ydisp || absoluteRow >= this._terminal.buffer.ydisp + this._terminal.rows) { + // Rotate rows to ensure the next focused item is read out correctly + this._accessibilityManager.rotateRows(); + } + absoluteRow = this._terminal.scrollToRow(absoluteRow); if (this._focusedElement) { diff --git a/src/Interfaces.ts b/src/Interfaces.ts index 4dc572d1e..a1623a67a 100644 --- a/src/Interfaces.ts +++ b/src/Interfaces.ts @@ -251,7 +251,7 @@ export interface IEventEmitter { export interface IListenerType { (data?: any): void; listener?: (data?: any) => void; -}; +} export interface ILinkMatcherOptions { /** From 39fcbe61c0b368e3b53739ab98d3abddcc38fbb6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Fri, 19 Jan 2018 10:52:03 -0800 Subject: [PATCH 35/86] Improve announcement on focus --- src/Terminal.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/Terminal.ts b/src/Terminal.ts index 92ae794ff..47fcbe772 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -620,6 +620,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea = document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); + // TODO: New API to set title? This could say "Terminal bash input", etc. + this.textarea.setAttribute('aria-label', 'Terminal input'); + this.textarea.setAttribute('aria-multiline', 'false'); this.textarea.setAttribute('autocorrect', 'off'); this.textarea.setAttribute('autocapitalize', 'off'); this.textarea.setAttribute('spellcheck', 'false'); From ec9f2702eb08a9fd7fe53c8b93ca7a278d000886 Mon Sep 17 00:00:00 2001 From: Saad Malik Date: Sun, 21 Jan 2018 16:40:38 -0800 Subject: [PATCH 36/86] Additional alt and control sequences * Add punctuation alt sequences * Add support for ctrl+alt sequences --- src/Terminal.test.ts | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/Terminal.ts | 40 ++++++++++++++++++++++++++++++++++++---- 2 files changed, 80 insertions(+), 4 deletions(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 946e9efb1..746cdeba1 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -578,6 +578,50 @@ describe('term.js addons', () => { assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 122 }).key, '\x1b[23;5~'); assert.equal(term.evaluateKeyEscapeSequence({ ctrlKey: true, keyCode: 123 }).key, '\x1b[24;5~'); }); + + // Characters using ctrl+alt sequences + it('should return proper sequence for ctrl+alt+a', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01'); + }); + + // Characters using alt sequences + it('should return proper sequences for alt+;', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:'); + }); + it('should return proper sequences for alt+=', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 187 }).key, '\x1b='); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 187 }).key, '\x1b+'); + }); + it('should return proper sequences for alt+,', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 188 }).key, '\x1b,'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 188 }).key, '\x1b<'); + }); + it('should return proper sequences for alt+-', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 189 }).key, '\x1b-'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 189 }).key, '\x1b_'); + }); + it('should return proper sequences for alt+.', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>'); + }); + it('should return proper sequences for alt+~', () => { + // tilde is a DEAD key + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b`'); + }); + it('should return proper sequences for alt+[', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b['); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{'); + }); + it('should return proper sequences for alt+]', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}'); + }); + it('should return proper sequences for alt+\\', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\''); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b|'); + }); }); describe('Third level shift', () => { diff --git a/src/Terminal.ts b/src/Terminal.ts index b56caf83f..4df277d53 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1713,14 +1713,46 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // ^] - Operating System Command (OSC) result.key = String.fromCharCode(29); } - } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.ctrlKey && !ev.metaKey) { + } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.metaKey) { // On macOS this is a third level shift when !macOptionIsMeta. Use instead. if (ev.keyCode >= 65 && ev.keyCode <= 90) { - result.key = C0.ESC + String.fromCharCode(ev.keyCode + 32); - } else if (ev.keyCode === 192) { - result.key = C0.ESC + '`'; + const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; + result.key = C0.ESC + String.fromCharCode(keyCode); } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { result.key = C0.ESC + (ev.keyCode - 48); + } else { + const t = (p,s) => !ev.shiftKey ? p : s; + switch (ev.keyCode) { + case 186: + result.key = C0.ESC + t(';', ':'); + break; + case 187: + result.key = C0.ESC + t('=', '+'); + break; + case 188: + result.key = C0.ESC + t(',', '<'); + break; + case 189: + result.key = C0.ESC + t('-', '_'); + break; + case 190: + result.key = C0.ESC + t('.', '>'); + break; + case 192: + // the tilde is a DEAD key + result.key = C0.ESC + '`'; + break; + case 219: + result.key = C0.ESC + t('[', '{'); + break; + case 221: + result.key = C0.ESC + t(']', '}'); + break; + case 222: + result.key = C0.ESC + t('\'', '|'); + break; + + } } } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a From d62e4e25b73e74ab524f2a8513c954bc02207a10 Mon Sep 17 00:00:00 2001 From: Saad Malik Date: Sun, 21 Jan 2018 16:56:58 -0800 Subject: [PATCH 37/86] Cleaup whitespace --- src/Terminal.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 4df277d53..4692f3456 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1721,7 +1721,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { result.key = C0.ESC + (ev.keyCode - 48); } else { - const t = (p,s) => !ev.shiftKey ? p : s; + const t = (p, s) => !ev.shiftKey ? p : s; switch (ev.keyCode) { case 186: result.key = C0.ESC + t(';', ':'); From c2d78e57d064e5d9a68973394b3b967f63ee6280 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 23 Jan 2018 09:49:43 -0800 Subject: [PATCH 38/86] Fix screenReadeMode disable in demo --- demo/main.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/demo/main.js b/demo/main.js index 651dbd0f2..179c6c648 100644 --- a/demo/main.js +++ b/demo/main.js @@ -85,7 +85,7 @@ optionElements.tabstopwidth.addEventListener('change', function () { term.setOption('tabStopWidth', parseInt(optionElements.tabstopwidth.value, 10)); }); optionElements.screenReaderMode.addEventListener('change', function () { - term.setOption('screenReaderMode', optionElements.screenReaderMode.value); + term.setOption('screenReaderMode', optionElements.screenReaderMode.checked); }); navigationModeElement.addEventListener('click', function () { if (term.getOption('screenReaderMode')) { From bce0cce74d3c2789ec79aa9023082b949ada2530 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 23 Jan 2018 09:50:47 -0800 Subject: [PATCH 39/86] Ensure a11y tree is located above the prompt --- src/AccessibilityManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a958d10bc..819541ae7 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -56,7 +56,7 @@ export class AccessibilityManager implements IDisposable { this._liveRegion.setAttribute('aria-live', 'assertive'); this._accessibilityTreeRoot.appendChild(this._liveRegion); - this._terminal.element.appendChild(this._accessibilityTreeRoot); + this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this._disposables.push(this._renderRowsDebouncer); this._disposables.push(this._navigationMode); From fba4f5f261ae480e0d9f90f852e47a3c1971dfc9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 23 Jan 2018 10:39:21 -0800 Subject: [PATCH 40/86] Ensure active item is removed before scroll --- src/AccessibilityManager.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 819541ae7..68681f4f1 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -357,11 +357,13 @@ class NavigationMode implements IDisposable { this._accessibilityManager.rotateRows(); } - absoluteRow = this._terminal.scrollToRow(absoluteRow); - + // Make sure there is no active element when scroll happens if (this._focusedElement) { this._focusedElement.removeAttribute('id'); } + + absoluteRow = this._terminal.scrollToRow(absoluteRow); + this._absoluteFocusedRow = absoluteRow; this._focusedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; this._focusedElement.id = this._activeItemId; From 45c6008b275d84a0e207768bbbc20120eb54f81f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 23 Jan 2018 11:11:29 -0800 Subject: [PATCH 41/86] Rotate terminal rows after removing focus --- src/AccessibilityManager.ts | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 68681f4f1..988279abb 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -261,8 +261,8 @@ class NavigationMode implements IDisposable { this._rowContainer.tabIndex = 0; this._rowContainer.setAttribute('role', 'menu'); this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); - this._rowContainer.focus(); this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); + this._rowContainer.focus(); } public leave(): void { @@ -352,19 +352,23 @@ class NavigationMode implements IDisposable { } private _navigateToElement(absoluteRow: number): void { - if (absoluteRow < this._terminal.buffer.ydisp || absoluteRow >= this._terminal.buffer.ydisp + this._terminal.rows) { - // Rotate rows to ensure the next focused item is read out correctly - this._accessibilityManager.rotateRows(); - } - - // Make sure there is no active element when scroll happens + // Make sure there is no active element when scroll and rotate happens if (this._focusedElement) { + this._rowContainer.removeAttribute('aria-activedescendant'); this._focusedElement.removeAttribute('id'); } - absoluteRow = this._terminal.scrollToRow(absoluteRow); + // Rotate rows to ensure the next focused item is read out correctly + if (absoluteRow < this._terminal.buffer.ydisp || absoluteRow >= this._terminal.buffer.ydisp + this._terminal.rows) { + this._accessibilityManager.rotateRows(); + } + // Scroll to row if it's outside of the viewport + absoluteRow = this._terminal.scrollToRow(absoluteRow); this._absoluteFocusedRow = absoluteRow; + + // Focus the new active element + this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); this._focusedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; this._focusedElement.id = this._activeItemId; } From c41b35ae9caf4824f8e4a8cb54722b115ba6d18c Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 24 Jan 2018 11:04:41 -0800 Subject: [PATCH 42/86] Add message about entering navigation mode --- src/AccessibilityManager.ts | 7 +++++++ src/xterm.css | 3 --- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 988279abb..281618f45 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -16,6 +16,7 @@ export class AccessibilityManager implements IDisposable { private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _liveRegion: HTMLElement; + private _moreRowsElement: HTMLElement; private _liveRegionLineCount: number = 0; private _renderRowsDebouncer: RenderDebouncer; @@ -37,6 +38,12 @@ export class AccessibilityManager implements IDisposable { constructor(private _terminal: ITerminal) { this._accessibilityTreeRoot = document.createElement('div'); this._accessibilityTreeRoot.classList.add('xterm-accessibility'); + + this._moreRowsElement = document.createElement('div'); + this._moreRowsElement.style.clip = 'clip(0 0 0 0)'; + this._moreRowsElement.textContent = 'In order to properly navigation the terminal buffer you need to enter navigation mode'; + this._accessibilityTreeRoot.appendChild(this._moreRowsElement); + this._rowContainer = document.createElement('div'); this._rowContainer.classList.add('xterm-accessibility-tree'); for (let i = 0; i < this._terminal.rows; i++) { diff --git a/src/xterm.css b/src/xterm.css index 583aa9071..fffc6b852 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -131,9 +131,6 @@ bottom: 0; right: 0; z-index: 100; -} - -.xterm .xterm-accessibility-tree { color: transparent; } From 3e734fab79b268537b26e25cde5e5ad4ef194c7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 24 Jan 2018 11:15:47 -0800 Subject: [PATCH 43/86] Support i18n --- src/AccessibilityManager.ts | 5 +++-- src/Strings.ts | 8 ++++++++ src/Terminal.ts | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/Strings.ts diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 281618f45..e1bc841a7 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -3,6 +3,7 @@ * @license MIT */ +import * as Strings from './Strings'; import { ITerminal, IBuffer, IDisposable } from './Interfaces'; import { isMac } from './utils/Browser'; import { RenderDebouncer } from './utils/RenderDebouncer'; @@ -41,7 +42,7 @@ export class AccessibilityManager implements IDisposable { this._moreRowsElement = document.createElement('div'); this._moreRowsElement.style.clip = 'clip(0 0 0 0)'; - this._moreRowsElement.textContent = 'In order to properly navigation the terminal buffer you need to enter navigation mode'; + this._moreRowsElement.textContent = Strings.navigationModeMoreRows; this._accessibilityTreeRoot.appendChild(this._moreRowsElement); this._rowContainer = document.createElement('div'); @@ -156,7 +157,7 @@ export class AccessibilityManager implements IDisposable { this._liveRegionLineCount++; if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { // TODO: Enable localization - this._liveRegion.textContent += 'Too much output to announce, navigate to rows manually to read'; + this._liveRegion.textContent += Strings.tooMuchOutput; } } diff --git a/src/Strings.ts b/src/Strings.ts new file mode 100644 index 000000000..17d8a4584 --- /dev/null +++ b/src/Strings.ts @@ -0,0 +1,8 @@ +/** + * Copyright (c) 2018 The xterm.js authors. All rights reserved. + * @license MIT + */ + +export let promptLabel = 'Terminal input'; +export let navigationModeMoreRows = 'In order to properly navigation the terminal buffer you need to enter navigation mode'; +export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 584565ebc..d293c256a 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -36,6 +36,7 @@ import { Linkifier } from './Linkifier'; import { SelectionManager } from './SelectionManager'; import { CharMeasure } from './utils/CharMeasure'; import * as Browser from './utils/Browser'; +import * as Strings from './Strings'; import { MouseHelper } from './utils/MouseHelper'; import { CHARSETS } from './Charsets'; import { CustomKeyEventHandler, LinkMatcherHandler, LinkMatcherValidationCallback, CharData, LineData } from './Types'; @@ -623,7 +624,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.textarea = document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); // TODO: New API to set title? This could say "Terminal bash input", etc. - this.textarea.setAttribute('aria-label', 'Terminal input'); + this.textarea.setAttribute('aria-label', Strings.promptLabel); this.textarea.setAttribute('aria-multiline', 'false'); this.textarea.setAttribute('autocorrect', 'off'); this.textarea.setAttribute('autocapitalize', 'off'); From 1cceda605299c3dd82593f16a39a8d4b8c9a2932 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 24 Jan 2018 16:34:39 -0800 Subject: [PATCH 44/86] Change navigation mode to work using focus instead of activedescendant --- src/AccessibilityManager.ts | 275 ++++++++++++++---------------------- src/xterm.css | 3 +- 2 files changed, 108 insertions(+), 170 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index e1bc841a7..fa3a094d3 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -12,6 +12,11 @@ import { addDisposableListener } from './utils/Dom'; const MAX_ROWS_TO_READ = 20; const ACTIVE_ITEM_ID_PREFIX = 'xterm-active-item-'; +enum BoundaryPosition { + Top, + Bottom +} + export class AccessibilityManager implements IDisposable { private _accessibilityTreeRoot: HTMLElement; private _rowContainer: HTMLElement; @@ -21,7 +26,10 @@ export class AccessibilityManager implements IDisposable { private _liveRegionLineCount: number = 0; private _renderRowsDebouncer: RenderDebouncer; - private _navigationMode: NavigationMode; + // private _navigationMode: NavigationMode; + + private _topBoundaryFocusListener: (e: FocusEvent) => void; + private _bottomBoundaryFocusListener: (e: FocusEvent) => void; private _disposables: IDisposable[] = []; @@ -41,6 +49,7 @@ export class AccessibilityManager implements IDisposable { this._accessibilityTreeRoot.classList.add('xterm-accessibility'); this._moreRowsElement = document.createElement('div'); + this._moreRowsElement.classList.add('xterm-message'); this._moreRowsElement.style.clip = 'clip(0 0 0 0)'; this._moreRowsElement.textContent = Strings.navigationModeMoreRows; this._accessibilityTreeRoot.appendChild(this._moreRowsElement); @@ -51,13 +60,19 @@ export class AccessibilityManager implements IDisposable { this._rowElements[i] = this._createAccessibilityTreeNode(); this._rowContainer.appendChild(this._rowElements[i]); } + + this._topBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Top); + this._bottomBoundaryFocusListener = e => this._onBoundaryFocus(e, BoundaryPosition.Bottom); + this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); + this._refreshRowsDimensions(); this._accessibilityTreeRoot.appendChild(this._rowContainer); this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); this._refreshRows(); - this._navigationMode = new NavigationMode(this._terminal, this._rowContainer, this._rowElements, this); + // this._navigationMode = new NavigationMode(this._terminal, this._rowContainer, this._rowElements, this); this._liveRegion = document.createElement('div'); this._liveRegion.classList.add('live-region'); @@ -67,7 +82,7 @@ export class AccessibilityManager implements IDisposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this._disposables.push(this._renderRowsDebouncer); - this._disposables.push(this._navigationMode); + // this._disposables.push(this._navigationMode); this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); @@ -98,12 +113,86 @@ export class AccessibilityManager implements IDisposable { this._rowElements = null; } + private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { + const boundaryElement = e.target; + const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; + + // Don't scroll if the buffer top has been reached + const posInSet = this._rowElements[0].getAttribute('aria-posinset'); + if (posInSet === '1') { + return; + } + console.log('posInSet', posInSet); + + // Don't scroll when the last focused item was not the second row (focus is going the other + // direction) + console.log('related', e.relatedTarget); + if (e.relatedTarget !== beforeBoundaryElement) { + console.log('cancel'); + return; + } + + boundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); + let oldLastElement: HTMLElement; + // TODO: oldLastElement.removeEventListener(...) + + if (position === BoundaryPosition.Top) { + oldLastElement = this._rowElements.pop(); + this._rowElements.unshift(this._createAccessibilityTreeNode()); + this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); + this._rowContainer.insertAdjacentElement('afterbegin', this._rowElements[0]); + } else { + oldLastElement = this._rowElements.shift(); + this._rowElements.push(this._createAccessibilityTreeNode()); + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._topBoundaryFocusListener); + this._rowContainer.appendChild(this._rowElements[this._rowElements.length - 1]); + } + this._rowContainer.removeChild(oldLastElement); + + + + + // TODO: Add bottom boundary listeners and remove in both cases + + + + + // Scroll up + this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1); + + // TODO: Only refresh single + this._refreshRowsDimensions(); + + // Focus the new active element + // this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); + // this._focusedElement = this._rowElements[1]; + // this._focusedElement.id = this._activeItemId; + + // Focus new boundary before element + this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2].focus(); + + // Prevent the standard behavior + e.preventDefault(); + e.stopImmediatePropagation(); + } + public get isNavigationModeActive(): boolean { - return this._navigationMode.isActive; + // TODO: Remove this function + return true; + // return this._navigationMode.isActive; } public enterNavigationMode(): void { - this._navigationMode.enter(); + // this._navigationMode.enter(); + + // this._isNavigationModeActive = true; + this.announce('Entered line navigation mode'); + // this._rowContainer.tabIndex = 0; + // this._rowContainer.setAttribute('role', 'list'); + // this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); + // this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); + // this._rowContainer.focus(); + this._rowElements[this._rowElements.length - 1].focus(); } private _onResize(cols: number, rows: number): void { @@ -117,12 +206,15 @@ export class AccessibilityManager implements IDisposable { this._rowContainer.removeChild(this._rowElements.pop()); } + // TODO: Fix up boundary listeners + this._refreshRowsDimensions(); } - private _createAccessibilityTreeNode(): HTMLElement { + public _createAccessibilityTreeNode(): HTMLElement { const element = document.createElement('div'); - element.setAttribute('role', 'menuitem'); + element.setAttribute('role', 'listitem'); + element.tabIndex = -1; return element; } @@ -156,7 +248,6 @@ export class AccessibilityManager implements IDisposable { if (char === '\n') { this._liveRegionLineCount++; if (this._liveRegionLineCount === MAX_ROWS_TO_READ + 1) { - // TODO: Enable localization this._liveRegion.textContent += Strings.tooMuchOutput; } } @@ -198,19 +289,20 @@ export class AccessibilityManager implements IDisposable { const setSize = (buffer.lines.length).toString(); for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); - this._rowElements[i].textContent = lineData; + this._rowElements[i].textContent = lineData.length === 0 ? 'Blank line' : lineData; const posInSet = (buffer.ydisp + i + 1).toString(); this._rowElements[i].setAttribute('aria-posinset', posInSet); this._rowElements[i].setAttribute('aria-setsize', setSize); } + // TODO: Clean up } public rotateRows(): void { - this._rowContainer.removeChild(this._rowElements.shift()); - const newRowIndex = this._rowElements.length; - this._rowElements[newRowIndex] = this._createAccessibilityTreeNode(); - this._rowContainer.appendChild(this._rowElements[newRowIndex]); - this._refreshRowsDimensions(); + // this._rowContainer.removeChild(this._rowElements.shift()); + // const newRowIndex = this._rowElements.length; + // this._rowElements[newRowIndex] = this._createAccessibilityTreeNode(); + // this._rowContainer.appendChild(this._rowElements[newRowIndex]); + // this._refreshRowsDimensions(); } private _refreshRowsDimensions(): void { @@ -226,158 +318,3 @@ export class AccessibilityManager implements IDisposable { this._liveRegion.textContent = text; } } - -class NavigationMode implements IDisposable { - private _activeItemId: string; - private _isNavigationModeActive: boolean = false; - private _absoluteFocusedRow: number; - private _focusedElement: HTMLElement; - - private _disposables: IDisposable[] = []; - - constructor( - private _terminal: ITerminal, - private _rowContainer: HTMLElement, - private _rowElements: HTMLElement[], - private _accessibilityManager: AccessibilityManager - ) { - this._activeItemId = ACTIVE_ITEM_ID_PREFIX + Math.floor((Math.random() * 100000)); - - this._disposables.push(addDisposableListener(this._rowContainer, 'keyup', e => { - if (this.isActive) { - return this.onKeyUp(e); - } - return false; - })); - this._disposables.push(addDisposableListener(this._rowContainer, 'keydown', e => { - if (this.isActive) { - return this.onKeyDown(e); - } - return false; - })); - } - - public dispose(): void { - this._disposables.forEach(d => d.dispose()); - this._disposables = null; - } - - public enter(): void { - // TODO: Should entering navigation mode send ydisp to ybase? - this._isNavigationModeActive = true; - this._accessibilityManager.announce('Entered line navigation mode'); - this._rowContainer.tabIndex = 0; - this._rowContainer.setAttribute('role', 'menu'); - this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); - this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); - this._rowContainer.focus(); - } - - public leave(): void { - this._isNavigationModeActive = false; - this._accessibilityManager.announce('Left line navigation mode'); - this._rowContainer.removeAttribute('tabindex'); - this._rowContainer.removeAttribute('aria-activedescendant'); - this._rowContainer.removeAttribute('role'); - if (this._focusedElement) { - this._focusedElement.removeAttribute('id'); - } - this._terminal.textarea.focus(); - } - - public get isActive(): boolean { - return this._isNavigationModeActive; - } - - public onKeyDown(e: KeyboardEvent): boolean { - return this._onKey(e, e => { - if (this._isNavigationModeActive) { - return true; - } - return false; - }); - } - - public onKeyUp(e: KeyboardEvent): boolean { - return this._onKey(e, e => { - if (this._isNavigationModeActive) { - switch (e.keyCode) { - case 27: return this._onEscape(e); - case 33: return this._onPageUp(e); - case 34: return this._onPageDown(e); - case 35: return this._onEnd(e); - case 36: return this._onHome(e); - case 38: return this._onArrowUp(e); - case 40: return this._onArrowDown(e); - } - } - return false; - }); - } - - private _onKey(e: KeyboardEvent, handler: (e: KeyboardEvent) => boolean): boolean { - if (handler && handler(e)) { - e.preventDefault(); - e.stopPropagation(); - return true; - } - return false; - } - - private _onEscape(e: KeyboardEvent): boolean { - this.leave(); - return true; - } - - private _onArrowUp(e: KeyboardEvent): boolean { - return this._focusRow(this._absoluteFocusedRow - 1); - } - - private _onArrowDown(e: KeyboardEvent): boolean { - return this._focusRow(this._absoluteFocusedRow + 1); - } - - private _onPageUp(e: KeyboardEvent): boolean { - return this._focusRow(this._absoluteFocusedRow - this._terminal.rows); - } - - private _onPageDown(e: KeyboardEvent): boolean { - return this._focusRow(this._absoluteFocusedRow + this._terminal.rows); - } - - private _onHome(e: KeyboardEvent): boolean { - return this._focusRow(0); - } - - private _onEnd(e: KeyboardEvent): boolean { - return this._focusRow(this._terminal.buffer.lines.length - 1); - } - - private _focusRow(row: number): boolean { - this._navigateToElement(row); - this._rowContainer.focus(); - return true; - } - - private _navigateToElement(absoluteRow: number): void { - // Make sure there is no active element when scroll and rotate happens - if (this._focusedElement) { - this._rowContainer.removeAttribute('aria-activedescendant'); - this._focusedElement.removeAttribute('id'); - } - - // Rotate rows to ensure the next focused item is read out correctly - if (absoluteRow < this._terminal.buffer.ydisp || absoluteRow >= this._terminal.buffer.ydisp + this._terminal.rows) { - this._accessibilityManager.rotateRows(); - } - - // Scroll to row if it's outside of the viewport - absoluteRow = this._terminal.scrollToRow(absoluteRow); - this._absoluteFocusedRow = absoluteRow; - - // Focus the new active element - this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); - this._focusedElement = this._rowElements[absoluteRow - this._terminal.buffer.ydisp]; - this._focusedElement.id = this._activeItemId; - } -} diff --git a/src/xterm.css b/src/xterm.css index fffc6b852..1057dadc8 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -124,7 +124,8 @@ cursor: text; } -.xterm .xterm-accessibility { +.xterm .xterm-accessibility, +.xterm .xterm-message { position: absolute; left: 0; top: 0; From 4c6f18c36f925fe4aeae5e9188d14b24cdd35212 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 25 Jan 2018 09:19:40 -0800 Subject: [PATCH 45/86] Revert "Add support for modifiers for PageUp / PageDown keys." This reverts commit 83921c0b26874216ee9c28ebab8d523acf5ff63e. Fixes #1245 --- src/Terminal.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 3746cb585..6a61bb4e8 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1600,8 +1600,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // page up if (ev.shiftKey) { result.scrollLines = -(this.rows - 1); - } else if (modifiers) { - result.key = C0.ESC + '[5;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[5~'; } @@ -1610,8 +1608,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // page down if (ev.shiftKey) { result.scrollLines = this.rows - 1; - } else if (modifiers) { - result.key = C0.ESC + '[6;' + (modifiers + 1) + '~'; } else { result.key = C0.ESC + '[6~'; } From 9c62a817d0b8a6c6623a24c45cd383bc496df8d6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 25 Jan 2018 12:01:51 -0800 Subject: [PATCH 46/86] Fix build after merge --- src/Interfaces.ts | 378 ---------------------------------------------- src/Types.ts | 2 + 2 files changed, 2 insertions(+), 378 deletions(-) delete mode 100644 src/Interfaces.ts diff --git a/src/Interfaces.ts b/src/Interfaces.ts deleted file mode 100644 index 2524d2349..000000000 --- a/src/Interfaces.ts +++ /dev/null @@ -1,378 +0,0 @@ -/** - * Copyright (c) 2017 The xterm.js authors. All rights reserved. - * @license MIT - */ - -import { ICharset, ILinkMatcherOptions } from './Interfaces'; -import { LinkMatcherHandler, LinkMatcherValidationCallback, LineData } from './Types'; -import { IColorSet, IRenderer } from './renderer/Interfaces'; -import { IMouseZoneManager } from './input/Interfaces'; - -export interface IBrowser { - isNode: boolean; - userAgent: string; - platform: string; - isFirefox: boolean; - isMSIE: boolean; - isMac: boolean; - isIpad: boolean; - isIphone: boolean; - isMSWindows: boolean; -} - -export interface IBufferAccessor { - buffer: IBuffer; -} - -export interface IElementAccessor { - element: HTMLElement; -} - -export interface ILinkifierAccessor { - linkifier: ILinkifier; -} - -export interface ITerminal extends ILinkifierAccessor, IBufferAccessor, IElementAccessor, IEventEmitter { - selectionManager: ISelectionManager; - charMeasure: ICharMeasure; - textarea: HTMLTextAreaElement; - renderer: IRenderer; - rows: number; - cols: number; - browser: IBrowser; - writeBuffer: string[]; - cursorHidden: boolean; - cursorState: number; - defAttr: number; - options: ITerminalOptions; - buffers: IBufferSet; - isFocused: boolean; - mouseHelper: IMouseHelper; - bracketedPasteMode: boolean; - - /** - * Emit the 'data' event and populate the given data. - * @param data The data to populate in the event. - */ - handler(data: string): void; - scrollLines(disp: number, suppressScrollEvent?: boolean): void; - cancel(ev: Event, force?: boolean): boolean | void; - log(text: string): void; - reset(): void; - showCursor(): void; - blankLine(cur?: boolean, isWrapped?: boolean, cols?: number): LineData; - refresh(start: number, end: number): void; -} - -/** - * This interface encapsulates everything needed from the Terminal by the - * InputHandler. This cleanly separates the large amount of methods needed by - * InputHandler cleanly from the ITerminal interface. - */ -export interface IInputHandlingTerminal extends IEventEmitter { - element: HTMLElement; - options: ITerminalOptions; - cols: number; - rows: number; - charset: ICharset; - gcharset: number; - glevel: number; - charsets: ICharset[]; - applicationKeypad: boolean; - applicationCursor: boolean; - originMode: boolean; - insertMode: boolean; - wraparoundMode: boolean; - bracketedPasteMode: boolean; - defAttr: number; - curAttr: number; - prefix: string; - savedCols: number; - x10Mouse: boolean; - vt200Mouse: boolean; - normalMouse: boolean; - mouseEvents: boolean; - sendFocus: boolean; - utfMouse: boolean; - sgrMouse: boolean; - urxvtMouse: boolean; - cursorHidden: boolean; - - buffers: IBufferSet; - buffer: IBuffer; - viewport: IViewport; - selectionManager: ISelectionManager; - - bell(): void; - focus(): void; - convertEol: boolean; - updateRange(y: number): void; - scroll(isWrapped?: boolean): void; - setgLevel(g: number): void; - eraseAttr(): number; - eraseRight(x: number, y: number): void; - eraseLine(y: number): void; - eraseLeft(x: number, y: number): void; - blankLine(cur?: boolean, isWrapped?: boolean): LineData; - is(term: string): boolean; - send(data: string): void; - setgCharset(g: number, charset: ICharset): void; - resize(x: number, y: number): void; - log(text: string, data?: any): void; - reset(): void; - showCursor(): void; - refresh(start: number, end: number): void; - matchColor(r1: number, g1: number, b1: number): number; - error(text: string, data?: any): void; - setOption(key: string, value: any): void; -} - -export interface ITerminalOptions { - bellSound?: string; - bellStyle?: string; - cancelEvents?: boolean; - cols?: number; - convertEol?: boolean; - cursorBlink?: boolean; - cursorStyle?: string; - debug?: boolean; - disableStdin?: boolean; - enableBold?: boolean; - fontSize?: number; - fontFamily?: string; - handler?: (data: string) => void; - letterSpacing?: number; - lineHeight?: number; - rows?: number; - screenKeys?: boolean; - scrollback?: number; - tabStopWidth?: number; - termName?: string; - theme?: ITheme; - useFlowControl?: boolean; - rightClickSelectsWord?: boolean; -} - -export interface IBuffer { - lines: ICircularList; - ydisp: number; - ybase: number; - y: number; - x: number; - tabs: any; - scrollBottom: number; - scrollTop: number; - savedY: number; - savedX: number; - isCursorInViewport: boolean; - translateBufferLineToString(lineIndex: number, trimRight: boolean, startCol?: number, endCol?: number): string; - nextStop(x?: number): number; - prevStop(x?: number): number; -} - -export interface IBufferSet { - alt: IBuffer; - normal: IBuffer; - active: IBuffer; - - activateNormalBuffer(): void; - activateAltBuffer(): void; -} - -export interface IMouseHelper { - getCoords(event: {pageX: number, pageY: number}, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number, isSelection?: boolean): [number, number]; - getRawByteCoords(event: MouseEvent, element: HTMLElement, charMeasure: ICharMeasure, lineHeight: number, colCount: number, rowCount: number): { x: number, y: number }; -} - -export interface IViewport { - syncScrollArea(): void; - onWheel(ev: WheelEvent): void; - onTouchStart(ev: TouchEvent): void; - onTouchMove(ev: TouchEvent): void; - onThemeChanged(colors: IColorSet): void; -} - -export interface ISelectionManager { - selectionText: string; - selectionStart: [number, number]; - selectionEnd: [number, number]; - - disable(): void; - enable(): void; - setBuffer(buffer: IBuffer): void; - setSelection(row: number, col: number, length: number): void; - isClickInSelection(event: MouseEvent): boolean; - selectWordAtCursor(event: MouseEvent): void; -} - -export interface ICompositionHelper { - compositionstart(): void; - compositionupdate(ev: CompositionEvent): void; - compositionend(): void; - updateCompositionElements(dontRecurse?: boolean): void; - keydown(ev: KeyboardEvent): boolean; -} - -export interface ICharMeasure { - width: number; - height: number; - measure(options: ITerminalOptions): void; -} - -export interface ILinkifier extends IEventEmitter { - attachToDom(mouseZoneManager: IMouseZoneManager): void; - linkifyRows(start: number, end: number): void; - setHypertextLinkHandler(handler: LinkMatcherHandler): void; - setHypertextValidationCallback(callback: LinkMatcherValidationCallback): void; - registerLinkMatcher(regex: RegExp, handler: LinkMatcherHandler, options?: ILinkMatcherOptions): number; - deregisterLinkMatcher(matcherId: number): boolean; -} - -export interface ICircularList extends IEventEmitter { - length: number; - maxLength: number; - forEach: (callbackfn: (value: T, index: number) => void) => void; - - get(index: number): T; - set(index: number, value: T): void; - push(value: T): void; - pop(): T; - splice(start: number, deleteCount: number, ...items: T[]): void; - trimStart(count: number): void; - shiftElements(start: number, count: number, offset: number): void; -} - -export interface IEventEmitter { - on(type: string, listener: IListenerType): void; - off(type: string, listener: IListenerType): void; - emit(type: string, data?: any): void; -} - -export interface IListenerType { - (data?: any): void; - listener?: (data?: any) => void; -} - -export interface ILinkMatcherOptions { - /** - * The index of the link from the regex.match(text) call. This defaults to 0 - * (for regular expressions without capture groups). - */ - matchIndex?: number; - /** - * A callback that validates an individual link, returning true if valid and - * false if invalid. - */ - validationCallback?: LinkMatcherValidationCallback; - /** - * A callback that fires when the mouse hovers over a link. - */ - tooltipCallback?: LinkMatcherHandler; - /** - * A callback that fires when the mouse leaves a link that was hovered. - */ - leaveCallback?: () => void; - /** - * The priority of the link matcher, this defines the order in which the link - * matcher is evaluated relative to others, from highest to lowest. The - * default value is 0. - */ - priority?: number; -} - -/** - * Handles actions generated by the parser. - */ -export interface IInputHandler { - addChar(char: string, code: number): void; - - /** C0 BEL */ bell(): void; - /** C0 LF */ lineFeed(): void; - /** C0 CR */ carriageReturn(): void; - /** C0 BS */ backspace(): void; - /** C0 HT */ tab(): void; - /** C0 SO */ shiftOut(): void; - /** C0 SI */ shiftIn(): void; - - /** CSI @ */ insertChars(params?: number[]): void; - /** CSI A */ cursorUp(params?: number[]): void; - /** CSI B */ cursorDown(params?: number[]): void; - /** CSI C */ cursorForward(params?: number[]): void; - /** CSI D */ cursorBackward(params?: number[]): void; - /** CSI E */ cursorNextLine(params?: number[]): void; - /** CSI F */ cursorPrecedingLine(params?: number[]): void; - /** CSI G */ cursorCharAbsolute(params?: number[]): void; - /** CSI H */ cursorPosition(params?: number[]): void; - /** CSI I */ cursorForwardTab(params?: number[]): void; - /** CSI J */ eraseInDisplay(params?: number[]): void; - /** CSI K */ eraseInLine(params?: number[]): void; - /** CSI L */ insertLines(params?: number[]): void; - /** CSI M */ deleteLines(params?: number[]): void; - /** CSI P */ deleteChars(params?: number[]): void; - /** CSI S */ scrollUp(params?: number[]): void; - /** CSI T */ scrollDown(params?: number[]): void; - /** CSI X */ eraseChars(params?: number[]): void; - /** CSI Z */ cursorBackwardTab(params?: number[]): void; - /** CSI ` */ charPosAbsolute(params?: number[]): void; - /** CSI a */ HPositionRelative(params?: number[]): void; - /** CSI b */ repeatPrecedingCharacter(params?: number[]): void; - /** CSI c */ sendDeviceAttributes(params?: number[]): void; - /** CSI d */ linePosAbsolute(params?: number[]): void; - /** CSI e */ VPositionRelative(params?: number[]): void; - /** CSI f */ HVPosition(params?: number[]): void; - /** CSI g */ tabClear(params?: number[]): void; - /** CSI h */ setMode(params?: number[]): void; - /** CSI l */ resetMode(params?: number[]): void; - /** CSI m */ charAttributes(params?: number[]): void; - /** CSI n */ deviceStatus(params?: number[]): void; - /** CSI p */ softReset(params?: number[]): void; - /** CSI q */ setCursorStyle(params?: number[]): void; - /** CSI r */ setScrollRegion(params?: number[]): void; - /** CSI s */ saveCursor(params?: number[]): void; - /** CSI u */ restoreCursor(params?: number[]): void; -} - -export interface ITheme { - foreground?: string; - background?: string; - cursor?: string; - cursorAccent?: string; - selection?: string; - black?: string; - red?: string; - green?: string; - yellow?: string; - blue?: string; - magenta?: string; - cyan?: string; - white?: string; - brightBlack?: string; - brightRed?: string; - brightGreen?: string; - brightYellow?: string; - brightBlue?: string; - brightMagenta?: string; - brightCyan?: string; - brightWhite?: string; -} - -export interface ILinkMatcher { - id: number; - regex: RegExp; - handler: LinkMatcherHandler; - hoverTooltipCallback?: LinkMatcherHandler; - hoverLeaveCallback?: () => void; - matchIndex?: number; - validationCallback?: LinkMatcherValidationCallback; - priority?: number; -} - -export interface ICharset { - [key: string]: string; -} - -export interface ILinkHoverEvent { - x: number; - y: number; - length: number; -} diff --git a/src/Types.ts b/src/Types.ts index f3e9bb941..f78e52a5e 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -283,6 +283,8 @@ export interface ISelectionManager { disable(): void; enable(): void; setSelection(row: number, col: number, length: number): void; + isClickInSelection(event: MouseEvent): boolean; + selectWordAtCursor(event: MouseEvent): void; } export interface ILinkifier extends IEventEmitter { From e6c6e0766a7d5fd79f808b65814653c0ab709a21 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Thu, 25 Jan 2018 21:00:10 +0000 Subject: [PATCH 47/86] Fix usage of allowWhitespaceOnlySelection variable --- src/SelectionManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index a4a0b80c6..00d22fb6a 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -267,7 +267,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectWordAtCursor(event: MouseEvent): void { const coords = this._getMouseBufferCoords(event); if (coords) { - const wordPosition = this._getWordAt(coords, true); + const wordPosition = this._getWordAt(coords, false); if (wordPosition) { this._model.selectionStart = [wordPosition.start, coords[1]]; this._model.selectionStartLength = wordPosition.length; From 0104e4214e7c09331ba45087d8665a77faeb99ec Mon Sep 17 00:00:00 2001 From: Saad Malik Date: Fri, 26 Jan 2018 18:33:37 -0800 Subject: [PATCH 48/86] Reimplement using map * Reimplemented reg + shift alt digits and characters using a map * Add test cases for digits --- src/Terminal.test.ts | 51 +++++++++++++++++++++++++++++++-- src/Terminal.ts | 68 +++++++++++++++++++++----------------------- 2 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 746cdeba1..301ab020e 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -584,7 +584,49 @@ describe('term.js addons', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, ctrlKey: true, keyCode: 65 }).key, '\x1b\x01'); }); - // Characters using alt sequences + // Characters using alt sequences (numbers) + it('should return proper sequences for alt+0', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 48 }).key, '\x1b0'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 48 }).key, '\x1b)'); + }); + it('should return proper sequences for alt+1', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 49 }).key, '\x1b1'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 49 }).key, '\x1b!'); + }); + it('should return proper sequences for alt+2', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 50 }).key, '\x1b2'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 50 }).key, '\x1b@'); + }); + it('should return proper sequences for alt+3', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 51 }).key, '\x1b3'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 51 }).key, '\x1b#'); + }); + it('should return proper sequences for alt+4', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 52 }).key, '\x1b4'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 52 }).key, '\x1b$'); + }); + it('should return proper sequences for alt+5', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 53 }).key, '\x1b5'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 53 }).key, '\x1b%'); + }); + it('should return proper sequences for alt+6', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 54 }).key, '\x1b6'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 54 }).key, '\x1b^'); + }); + it('should return proper sequences for alt+7', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 55 }).key, '\x1b7'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 55 }).key, '\x1b&'); + }); + it('should return proper sequences for alt+8', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 56 }).key, '\x1b8'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 56 }).key, '\x1b*'); + }); + it('should return proper sequences for alt+9', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 57 }).key, '\x1b9'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 57 }).key, '\x1b('); + }); + + // Characters using alt sequences (special chars) it('should return proper sequences for alt+;', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 186 }).key, '\x1b;'); assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 186 }).key, '\x1b:'); @@ -605,10 +647,13 @@ describe('term.js addons', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 190 }).key, '\x1b.'); assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 190 }).key, '\x1b>'); }); + it('should return proper sequences for alt+/', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 191 }).key, '\x1b/'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 191 }).key, '\x1b?'); + }); it('should return proper sequences for alt+~', () => { - // tilde is a DEAD key assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 192 }).key, '\x1b`'); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b`'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 192 }).key, '\x1b~'); }); it('should return proper sequences for alt+[', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b['); diff --git a/src/Terminal.ts b/src/Terminal.ts index f737adbbe..ddb647dab 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -46,6 +46,33 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; import { ITheme } from 'xterm'; +// reg + shift key mappings for digits and special chars +const KEYCODE_KEY_MAPPINGS = { + // digits 0-9 + 48: ['0', ')'], + 49: ['1', '!'], + 50: ['2', '@'], + 51: ['3', '#'], + 52: ['4', '$'], + 53: ['5', '%'], + 54: ['6', '^'], + 55: ['7', '&'], + 56: ['8', '*'], + 57: ['9', '('], + + // special chars + 186: [';', ':'], + 187: ['=', '+'], + 188: [',', '<'], + 189: ['-', '_'], + 190: ['.', '>'], + 191: ['/', '?'], + 192: ['`', '~'], + 219: ['[', '{'], + 221: [']', '}'], + 222: ['\'', '|'] +}; + // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -1727,44 +1754,13 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } else if ((!this.browser.isMac || this.options.macOptionIsMeta) && ev.altKey && !ev.metaKey) { // On macOS this is a third level shift when !macOptionIsMeta. Use instead. - if (ev.keyCode >= 65 && ev.keyCode <= 90) { + const keyMapping = KEYCODE_KEY_MAPPINGS[ev.keyCode]; + const key = keyMapping && keyMapping[!ev.shiftKey ? 0 : 1]; + if (key) { + result.key = C0.ESC + key; + } else if (ev.keyCode >= 65 && ev.keyCode <= 90) { const keyCode = ev.ctrlKey ? ev.keyCode - 64 : ev.keyCode + 32; result.key = C0.ESC + String.fromCharCode(keyCode); - } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - result.key = C0.ESC + (ev.keyCode - 48); - } else { - const t = (p, s) => !ev.shiftKey ? p : s; - switch (ev.keyCode) { - case 186: - result.key = C0.ESC + t(';', ':'); - break; - case 187: - result.key = C0.ESC + t('=', '+'); - break; - case 188: - result.key = C0.ESC + t(',', '<'); - break; - case 189: - result.key = C0.ESC + t('-', '_'); - break; - case 190: - result.key = C0.ESC + t('.', '>'); - break; - case 192: - // the tilde is a DEAD key - result.key = C0.ESC + '`'; - break; - case 219: - result.key = C0.ESC + t('[', '{'); - break; - case 221: - result.key = C0.ESC + t(']', '}'); - break; - case 222: - result.key = C0.ESC + t('\'', '|'); - break; - - } } } else if (this.browser.isMac && !ev.altKey && !ev.ctrlKey && ev.metaKey) { if (ev.keyCode === 65) { // cmd + a From 5c061195c0bfb8cd6c413c0871752a31f4fd720a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 14:45:22 -0800 Subject: [PATCH 49/86] Get navigation right/down working with nav mode --- src/AccessibilityManager.ts | 61 +++++++++++++++++++------------------ 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 676a39ed6..474952958 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -73,8 +73,6 @@ export class AccessibilityManager implements IDisposable { this._renderRowsDebouncer = new RenderDebouncer(this._terminal, this._renderRows.bind(this)); this._refreshRows(); - // this._navigationMode = new NavigationMode(this._terminal, this._rowContainer, this._rowElements, this); - this._liveRegion = document.createElement('div'); this._liveRegion.classList.add('live-region'); this._liveRegion.setAttribute('aria-live', 'assertive'); @@ -83,7 +81,6 @@ export class AccessibilityManager implements IDisposable { this._terminal.element.insertAdjacentElement('afterbegin', this._accessibilityTreeRoot); this._disposables.push(this._renderRowsDebouncer); - // this._disposables.push(this._navigationMode); this._disposables.push(this._terminal.addDisposableListener('resize', data => this._onResize(data.cols, data.rows))); this._disposables.push(this._terminal.addDisposableListener('refresh', data => this._refreshRows(data.start, data.end))); this._disposables.push(this._terminal.addDisposableListener('scroll', data => this._refreshRows())); @@ -118,45 +115,54 @@ export class AccessibilityManager implements IDisposable { const boundaryElement = e.target; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; - // Don't scroll if the buffer top has been reached - const posInSet = this._rowElements[0].getAttribute('aria-posinset'); - if (posInSet === '1') { + // Don't scroll if the buffer top has reached the end in that direction + const posInSet = boundaryElement.getAttribute('aria-posinset'); + const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`; + if (posInSet === lastRowPos) { return; } - console.log('posInSet', posInSet); // Don't scroll when the last focused item was not the second row (focus is going the other // direction) - console.log('related', e.relatedTarget); if (e.relatedTarget !== beforeBoundaryElement) { console.log('cancel'); return; } - boundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); - let oldLastElement: HTMLElement; - // TODO: oldLastElement.removeEventListener(...) - + // TODO: Refactor to reduce duplication, define top and bottom boundary elements + let otherBoundaryElement: HTMLElement; if (position === BoundaryPosition.Top) { - oldLastElement = this._rowElements.pop(); + // Remove old other boundary element from array + otherBoundaryElement = this._rowElements.pop(); + + // Remove listeners from old boundary elements + boundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); + otherBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener); + + // Add new element to array/DOM this._rowElements.unshift(this._createAccessibilityTreeNode()); - this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); this._rowContainer.insertAdjacentElement('afterbegin', this._rowElements[0]); + + // Add listeners to new boundary elements + this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); } else { - oldLastElement = this._rowElements.shift(); + // Remove old other boundary element from array + otherBoundaryElement = this._rowElements.shift(); + + // Remove listeners from old boundary elements + otherBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); + boundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener); + + // Add new element to array/DOM this._rowElements.push(this._createAccessibilityTreeNode()); - this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._topBoundaryFocusListener); this._rowContainer.appendChild(this._rowElements[this._rowElements.length - 1]); + + // Add listeners to new boundary elements + this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); } - this._rowContainer.removeChild(oldLastElement); - - - - - // TODO: Add bottom boundary listeners and remove in both cases - - - + this._rowContainer.removeChild(otherBoundaryElement); // Scroll up this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1); @@ -164,11 +170,6 @@ export class AccessibilityManager implements IDisposable { // TODO: Only refresh single this._refreshRowsDimensions(); - // Focus the new active element - // this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); - // this._focusedElement = this._rowElements[1]; - // this._focusedElement.id = this._activeItemId; - // Focus new boundary before element this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2].focus(); From 963db4d2fd46850818238dc470beb182607667bc Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 14:48:45 -0800 Subject: [PATCH 50/86] Remove navigation mode/more rows element --- demo/index.html | 3 --- demo/main.js | 8 -------- src/AccessibilityManager.ts | 27 --------------------------- src/Strings.ts | 1 - src/Terminal.ts | 10 ---------- src/utils/TestUtils.test.ts | 3 --- typings/xterm.d.ts | 6 ------ 7 files changed, 58 deletions(-) diff --git a/demo/index.html b/demo/index.html index 9c95d06c3..b051f4ddf 100644 --- a/demo/index.html +++ b/demo/index.html @@ -71,9 +71,6 @@

-

- -

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

diff --git a/demo/main.js b/demo/main.js index 179c6c648..e31c93b21 100644 --- a/demo/main.js +++ b/demo/main.js @@ -33,7 +33,6 @@ var terminalContainer = document.getElementById('terminal-container'), bellStyle: document.querySelector('#option-bell-style'), screenReaderMode: document.querySelector('#option-screen-reader-mode') }, - navigationModeElement = document.querySelector('#screen-reader-navigation-mode'), colsElement = document.getElementById('cols'), rowsElement = document.getElementById('rows'); @@ -87,13 +86,6 @@ optionElements.tabstopwidth.addEventListener('change', function () { optionElements.screenReaderMode.addEventListener('change', function () { term.setOption('screenReaderMode', optionElements.screenReaderMode.checked); }); -navigationModeElement.addEventListener('click', function () { - if (term.getOption('screenReaderMode')) { - term.enterNavigationMode(); - } else { - console.warn('screenReaderMode must be true to enter navigation mode'); - } -}); createTerminal(); diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 474952958..a5fe33672 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -23,11 +23,9 @@ export class AccessibilityManager implements IDisposable { private _rowContainer: HTMLElement; private _rowElements: HTMLElement[] = []; private _liveRegion: HTMLElement; - private _moreRowsElement: HTMLElement; private _liveRegionLineCount: number = 0; private _renderRowsDebouncer: RenderDebouncer; - // private _navigationMode: NavigationMode; private _topBoundaryFocusListener: (e: FocusEvent) => void; private _bottomBoundaryFocusListener: (e: FocusEvent) => void; @@ -49,12 +47,6 @@ export class AccessibilityManager implements IDisposable { this._accessibilityTreeRoot = document.createElement('div'); this._accessibilityTreeRoot.classList.add('xterm-accessibility'); - this._moreRowsElement = document.createElement('div'); - this._moreRowsElement.classList.add('xterm-message'); - this._moreRowsElement.style.clip = 'clip(0 0 0 0)'; - this._moreRowsElement.textContent = Strings.navigationModeMoreRows; - this._accessibilityTreeRoot.appendChild(this._moreRowsElement); - this._rowContainer = document.createElement('div'); this._rowContainer.classList.add('xterm-accessibility-tree'); for (let i = 0; i < this._terminal.rows; i++) { @@ -178,25 +170,6 @@ export class AccessibilityManager implements IDisposable { e.stopImmediatePropagation(); } - public get isNavigationModeActive(): boolean { - // TODO: Remove this function - return true; - // return this._navigationMode.isActive; - } - - public enterNavigationMode(): void { - // this._navigationMode.enter(); - - // this._isNavigationModeActive = true; - this.announce('Entered line navigation mode'); - // this._rowContainer.tabIndex = 0; - // this._rowContainer.setAttribute('role', 'list'); - // this._rowContainer.setAttribute('aria-activedescendant', this._activeItemId); - // this._navigateToElement(this._terminal.buffer.ydisp + this._terminal.buffer.y); - // this._rowContainer.focus(); - this._rowElements[this._rowElements.length - 1].focus(); - } - private _onResize(cols: number, rows: number): void { // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { diff --git a/src/Strings.ts b/src/Strings.ts index 17d8a4584..8e7823333 100644 --- a/src/Strings.ts +++ b/src/Strings.ts @@ -4,5 +4,4 @@ */ export let promptLabel = 'Terminal input'; -export let navigationModeMoreRows = 'In order to properly navigation the terminal buffer you need to enter navigation mode'; export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read'; diff --git a/src/Terminal.ts b/src/Terminal.ts index 6d3f066a3..e9116a221 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1374,12 +1374,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT } } - public enterNavigationMode(): void { - if (this._accessibilityManager) { - this._accessibilityManager.enterNavigationMode(); - } - } - /** * Gets whether the terminal has an active selection. */ @@ -1420,10 +1414,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT * @param {KeyboardEvent} ev The keydown event to be handled. */ protected _keyDown(ev: KeyboardEvent): boolean { - if (this._accessibilityManager && this._accessibilityManager.isNavigationModeActive) { - return; - } - if (this.customKeyEventHandler && this.customKeyEventHandler(ev) === false) { return false; } diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index 91dabc26d..dd9126760 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -10,9 +10,6 @@ import * as Browser from '../shared/utils/Browser'; import { ITheme, IDisposable } from 'xterm'; export class MockTerminal implements ITerminal { - enterNavigationMode(): void { - throw new Error('Method not implemented.'); - } getOption(key: any): any { throw new Error('Method not implemented.'); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index ce4684e51..c48c814bb 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -370,12 +370,6 @@ declare module 'xterm' { */ deregisterLinkMatcher(matcherId: number): void; - /** - * Enters screen reader navigation mode. This will only work when - * the screenReaderMode option is true. - */ - enterNavigationMode(): void; - /** * Gets whether the terminal has an active selection. */ From 65850c1c209d0ad6b6b1e015087db8d381ac7e27 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 14:53:46 -0800 Subject: [PATCH 51/86] Reduce duplication --- src/AccessibilityManager.ts | 52 ++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 29 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index a5fe33672..969a98370 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -105,7 +105,7 @@ export class AccessibilityManager implements IDisposable { private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { const boundaryElement = e.target; - const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; + const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; // Don't scroll if the buffer top has reached the end in that direction const posInSet = boundaryElement.getAttribute('aria-posinset'); @@ -116,50 +116,44 @@ export class AccessibilityManager implements IDisposable { // Don't scroll when the last focused item was not the second row (focus is going the other // direction) - if (e.relatedTarget !== beforeBoundaryElement) { - console.log('cancel'); + if (e.relatedTarget !== beforeBoundaryElement) { return; } - // TODO: Refactor to reduce duplication, define top and bottom boundary elements - let otherBoundaryElement: HTMLElement; + // Remove old boundary element from array + let topBoundaryElement: HTMLElement; + let bottomBoundaryElement: HTMLElement; if (position === BoundaryPosition.Top) { - // Remove old other boundary element from array - otherBoundaryElement = this._rowElements.pop(); + topBoundaryElement = boundaryElement; + bottomBoundaryElement = this._rowElements.pop(); + this._rowContainer.removeChild(bottomBoundaryElement); + } else { + topBoundaryElement = this._rowElements.shift(); + bottomBoundaryElement = boundaryElement; + this._rowContainer.removeChild(topBoundaryElement); + } - // Remove listeners from old boundary elements - boundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); - otherBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener); + // Remove listeners from old boundary elements + topBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); + bottomBoundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener); - // Add new element to array/DOM + // Add new element to array/DOM + if (position === BoundaryPosition.Top) { this._rowElements.unshift(this._createAccessibilityTreeNode()); this._rowContainer.insertAdjacentElement('afterbegin', this._rowElements[0]); - - // Add listeners to new boundary elements - this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); - this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); } else { - // Remove old other boundary element from array - otherBoundaryElement = this._rowElements.shift(); - - // Remove listeners from old boundary elements - otherBoundaryElement.removeEventListener('focus', this._topBoundaryFocusListener); - boundaryElement.removeEventListener('focus', this._bottomBoundaryFocusListener); - - // Add new element to array/DOM this._rowElements.push(this._createAccessibilityTreeNode()); this._rowContainer.appendChild(this._rowElements[this._rowElements.length - 1]); - - // Add listeners to new boundary elements - this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); - this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); } - this._rowContainer.removeChild(otherBoundaryElement); + + // Add listeners to new boundary elements + this._rowElements[0].addEventListener('focus', this._topBoundaryFocusListener); + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); // Scroll up this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1); - // TODO: Only refresh single + // TODO: Only refresh only a single row this._refreshRowsDimensions(); // Focus new boundary before element From 6439aca1d847113f56aeb2ace8b83eee14d27513 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 15:00:19 -0800 Subject: [PATCH 52/86] Refresh dimensions of only a single row --- src/AccessibilityManager.ts | 30 +++++++++++++----------------- 1 file changed, 13 insertions(+), 17 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 969a98370..fdcf48876 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -139,11 +139,15 @@ export class AccessibilityManager implements IDisposable { // Add new element to array/DOM if (position === BoundaryPosition.Top) { - this._rowElements.unshift(this._createAccessibilityTreeNode()); - this._rowContainer.insertAdjacentElement('afterbegin', this._rowElements[0]); + const newElement = this._createAccessibilityTreeNode(); + this._rowElements.unshift(newElement); + this._refreshRowDimensions(newElement); + this._rowContainer.insertAdjacentElement('afterbegin', newElement); } else { - this._rowElements.push(this._createAccessibilityTreeNode()); - this._rowContainer.appendChild(this._rowElements[this._rowElements.length - 1]); + const newElement = this._createAccessibilityTreeNode(); + this._rowElements.push(newElement); + this._refreshRowDimensions(newElement); + this._rowContainer.appendChild(newElement); } // Add listeners to new boundary elements @@ -153,9 +157,6 @@ export class AccessibilityManager implements IDisposable { // Scroll up this._terminal.scrollLines(position === BoundaryPosition.Top ? -1 : 1); - // TODO: Only refresh only a single row - this._refreshRowsDimensions(); - // Focus new boundary before element this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2].focus(); @@ -266,22 +267,17 @@ export class AccessibilityManager implements IDisposable { // TODO: Clean up } - public rotateRows(): void { - // this._rowContainer.removeChild(this._rowElements.shift()); - // const newRowIndex = this._rowElements.length; - // this._rowElements[newRowIndex] = this._createAccessibilityTreeNode(); - // this._rowContainer.appendChild(this._rowElements[newRowIndex]); - // this._refreshRowsDimensions(); - } - private _refreshRowsDimensions(): void { const buffer: IBuffer = (this._terminal.buffer); - const dimensions = this._terminal.renderer.dimensions; for (let i = 0; i < this._terminal.rows; i++) { - this._rowElements[i].style.height = `${dimensions.actualCellHeight}px`; + this._refreshRowDimensions(this._rowElements[i]); } } + private _refreshRowDimensions(element: HTMLElement): void { + element.style.height = `${this._terminal.renderer.dimensions.actualCellHeight}px`; + } + public announce(text: string): void { this._clearLiveRegion(); this._liveRegion.textContent = text; From 5506af53224f0814080011a4b5056b4f6b839f81 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 16:47:23 -0800 Subject: [PATCH 53/86] Clean up a11y manager --- src/AccessibilityManager.ts | 47 ++++++++++++++++++------------------- src/Strings.ts | 1 + 2 files changed, 24 insertions(+), 24 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index fdcf48876..286c1f987 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -141,12 +141,10 @@ export class AccessibilityManager implements IDisposable { if (position === BoundaryPosition.Top) { const newElement = this._createAccessibilityTreeNode(); this._rowElements.unshift(newElement); - this._refreshRowDimensions(newElement); this._rowContainer.insertAdjacentElement('afterbegin', newElement); } else { const newElement = this._createAccessibilityTreeNode(); this._rowElements.push(newElement); - this._refreshRowDimensions(newElement); this._rowContainer.appendChild(newElement); } @@ -166,6 +164,9 @@ export class AccessibilityManager implements IDisposable { } private _onResize(cols: number, rows: number): void { + // Remove bottom boundary listener + this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); + // Grow rows as required for (let i = this._rowContainer.children.length; i < this._terminal.rows; i++) { this._rowElements[i] = this._createAccessibilityTreeNode(); @@ -176,7 +177,8 @@ export class AccessibilityManager implements IDisposable { this._rowContainer.removeChild(this._rowElements.pop()); } - // TODO: Fix up boundary listeners + // Add bottom boundary listener + this._rowElements[this._rowElements.length - 1].addEventListener('focus', this._bottomBoundaryFocusListener); this._refreshRowsDimensions(); } @@ -185,6 +187,7 @@ export class AccessibilityManager implements IDisposable { const element = document.createElement('div'); element.setAttribute('role', 'listitem'); element.tabIndex = -1; + this._refreshRowDimensions(element); return element; } @@ -200,19 +203,10 @@ export class AccessibilityManager implements IDisposable { // Have the screen reader ignore the char if it was just input const shiftedChar = this._charsToConsume.shift(); if (shiftedChar !== char) { - if (char === ' ') { - // Always use nbsp for spaces in order to preserve the space between characters in - // voiceover's caption window - this._liveRegion.innerHTML += ' '; - } else { - this._liveRegion.textContent += char; - } + this._announceCharacter(char); } } else { - if (char === ' ') { - this._liveRegion.innerHTML += ' '; - } else - this._liveRegion.textContent += char; + this._announceCharacter(char); } if (char === '\n') { @@ -255,20 +249,20 @@ export class AccessibilityManager implements IDisposable { } private _renderRows(start: number, end: number): void { - const buffer: IBuffer = (this._terminal.buffer); - const setSize = (buffer.lines.length).toString(); + const buffer: IBuffer = this._terminal.buffer; + const setSize = buffer.lines.length.toString(); for (let i = start; i <= end; i++) { const lineData = buffer.translateBufferLineToString(buffer.ydisp + i, true); - this._rowElements[i].textContent = lineData.length === 0 ? 'Blank line' : lineData; const posInSet = (buffer.ydisp + i + 1).toString(); - this._rowElements[i].setAttribute('aria-posinset', posInSet); - this._rowElements[i].setAttribute('aria-setsize', setSize); + const element = this._rowElements[i]; + element.textContent = lineData.length === 0 ? Strings.blankLine : lineData; + element.setAttribute('aria-posinset', posInSet); + element.setAttribute('aria-setsize', setSize); } - // TODO: Clean up } private _refreshRowsDimensions(): void { - const buffer: IBuffer = (this._terminal.buffer); + const buffer: IBuffer = this._terminal.buffer; for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); } @@ -278,8 +272,13 @@ export class AccessibilityManager implements IDisposable { element.style.height = `${this._terminal.renderer.dimensions.actualCellHeight}px`; } - public announce(text: string): void { - this._clearLiveRegion(); - this._liveRegion.textContent = text; + private _announceCharacter(char: string): void { + if (char === ' ') { + // Always use nbsp for spaces in order to preserve the space between characters in + // voiceover's caption window + this._liveRegion.innerHTML += ' '; + } else { + this._liveRegion.textContent += char; + } } } diff --git a/src/Strings.ts b/src/Strings.ts index 8e7823333..86f0ebe13 100644 --- a/src/Strings.ts +++ b/src/Strings.ts @@ -3,5 +3,6 @@ * @license MIT */ +export let blankLine = 'Blank line'; export let promptLabel = 'Terminal input'; export let tooMuchOutput = 'Too much output to announce, navigate to rows manually to read'; From 0bdf1e82656c4ec508f5072d7536d5db7152ef8e Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 17:51:43 -0800 Subject: [PATCH 54/86] Remove unused function --- src/Terminal.ts | 22 ---------------------- src/Types.ts | 1 - 2 files changed, 23 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index e9116a221..4eafc7a04 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1187,28 +1187,6 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } - /** - * Scroll the viewport to an absolute row in the buffer. - * @param absoluteRow The absolute row in the buffer to scroll to. - * @returns The actual absolute row that was scrolled to (including boundary checked). - */ - public scrollToRow(absoluteRow: number): number { - // Ensure value is valid - absoluteRow = Math.max(Math.min(absoluteRow, this.buffer.lines.length - 1), 0); - - // Move viewport as necessary - const relativeRow = absoluteRow - this.buffer.ydisp; - let scrollAmount = 0; - if (relativeRow < 0) { - scrollAmount = relativeRow; - } else if (relativeRow >= this.rows) { - scrollAmount = relativeRow - this.rows + 1; - } - this.scrollLines(scrollAmount); - - return absoluteRow; - } - /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). diff --git a/src/Types.ts b/src/Types.ts index 0b877930a..2500d73cb 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -197,7 +197,6 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce */ handler(data: string): void; scrollLines(disp: number, suppressScrollEvent?: boolean): void; - scrollToRow(row: number): number; cancel(ev: Event, force?: boolean): boolean | void; log(text: string): void; showCursor(): void; From b13826441cdeb4cb539a022808d1d8932b2e8283 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 18:14:52 -0800 Subject: [PATCH 55/86] Hook up scroll APIs to scroll and focus when navigating --- src/AccessibilityManager.ts | 74 ++++++++++++++++++++++++++++++++++--- src/Terminal.ts | 20 ++++++++-- 2 files changed, 85 insertions(+), 9 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 286c1f987..338f5a80e 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -103,10 +103,20 @@ export class AccessibilityManager implements IDisposable { this._rowElements = null; } + public get isNavigatingRows(): boolean { + return this._rowElements.indexOf(document.activeElement) >= 0; + } + private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { const boundaryElement = e.target; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; + // Don't scroll when the last focused item was not the second row (focus is going the other + // direction) + if (e.relatedTarget !== beforeBoundaryElement) { + return; + } + // Don't scroll if the buffer top has reached the end in that direction const posInSet = boundaryElement.getAttribute('aria-posinset'); const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`; @@ -114,12 +124,6 @@ export class AccessibilityManager implements IDisposable { return; } - // Don't scroll when the last focused item was not the second row (focus is going the other - // direction) - if (e.relatedTarget !== beforeBoundaryElement) { - return; - } - // Remove old boundary element from array let topBoundaryElement: HTMLElement; let bottomBoundaryElement: HTMLElement; @@ -163,6 +167,64 @@ export class AccessibilityManager implements IDisposable { e.stopImmediatePropagation(); } + /** + * Moves the focus of the terminal relatively by a number of rows. + * @param amount The amount of rows to scroll + */ + public moveRowFocus(amount: number): void { + console.log('moveRowFocus', amount); + // Do nothing if not navigating rows or the amount to navigate is 0 + if (!this.isNavigatingRows || amount === 0) { + return; + } + + // Find and validate the new absolute row position + const buffer = this._terminal.buffer; + const oldRelativeRow = this._rowElements.indexOf(document.activeElement); + const oldAbsoluteRow = buffer.ydisp + oldRelativeRow; + const newAbsoluteRow = Math.max(Math.min(oldAbsoluteRow + amount, buffer.lines.length - 1), 0); + if (oldAbsoluteRow === newAbsoluteRow) { + return; + } + + // Find the new relative row, this cannot be on a boundary element unless the focused row is the + // top-most or bottom-most row in the buffer. + let newRelativeRow: number; + if (newAbsoluteRow === 0) { + newRelativeRow = 0; + } else if (newAbsoluteRow === buffer.lines.length - 1) { + newRelativeRow = this._terminal.rows - 1; + } else { + newRelativeRow = Math.max(Math.min(oldRelativeRow + amount, this._terminal.rows - 2), 1); + } + + // Find the new viewport position + let newYDisp: number; + if (newAbsoluteRow === 0) { + // Start of buffer + newYDisp = 0; + } else if (newAbsoluteRow === buffer.lines.length - 1) { + // End of buffer + newYDisp = buffer.lines.length - 1 - this._terminal.rows; + } else if (newAbsoluteRow > buffer.ydisp && newAbsoluteRow < buffer.ydisp + this._terminal.rows - 1) { + // No scrolling necessary + newYDisp = buffer.ydisp; + } else if (newAbsoluteRow < oldAbsoluteRow) { + // Scrolling up needed + newYDisp = newAbsoluteRow - 1; + } else { // if (newAbsoluteRow > oldAbsoluteRow) + // Scrolling down needed + newYDisp = newAbsoluteRow + this._terminal.rows - 2; + } + + // Perform scroll + const scrollAmount = newYDisp - buffer.ydisp; + this._terminal.scrollLines(scrollAmount); + + // Focus new row + this._rowElements[newRelativeRow].focus(); + } + private _onResize(cols: number, rows: number): void { // Remove bottom boundary listener this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); diff --git a/src/Terminal.ts b/src/Terminal.ts index 4eafc7a04..d0389048c 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1187,26 +1187,40 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } + /** + * Scrolls the viewport by a number of rows when not navigating rows. When + * navigating rows, move focus relatively by a number of rows. + * @param amount The number of lines to scroll. + */ + private _accessibilityAwareScrollLines(amount: number): void { + console.log('_accessibilityAwareScrollLines'); + if (this.options.screenReaderMode && this._accessibilityManager.isNavigatingRows) { + this._accessibilityManager.moveRowFocus(amount); + } else { + this.scrollLines(amount); + } + } + /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). */ public scrollPages(pageCount: number): void { - this.scrollLines(pageCount * (this.rows - 1)); + this._accessibilityAwareScrollLines(pageCount * (this.rows - 1)); } /** * Scrolls the display of the terminal to the top. */ public scrollToTop(): void { - this.scrollLines(-this.buffer.ydisp); + this._accessibilityAwareScrollLines(-this.buffer.ydisp); } /** * Scrolls the display of the terminal to the bottom. */ public scrollToBottom(): void { - this.scrollLines(this.buffer.ybase - this.buffer.ydisp); + this._accessibilityAwareScrollLines(this.buffer.ybase - this.buffer.ydisp); } /** From 7c8b64aaf12c52ab6f2c67a0d518dc52e5226e66 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 18:14:58 -0800 Subject: [PATCH 56/86] Revert "Hook up scroll APIs to scroll and focus when navigating" This reverts commit b13826441cdeb4cb539a022808d1d8932b2e8283. --- src/AccessibilityManager.ts | 74 +++---------------------------------- src/Terminal.ts | 20 ++-------- 2 files changed, 9 insertions(+), 85 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 338f5a80e..286c1f987 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -103,20 +103,10 @@ export class AccessibilityManager implements IDisposable { this._rowElements = null; } - public get isNavigatingRows(): boolean { - return this._rowElements.indexOf(document.activeElement) >= 0; - } - private _onBoundaryFocus(e: FocusEvent, position: BoundaryPosition): void { const boundaryElement = e.target; const beforeBoundaryElement = this._rowElements[position === BoundaryPosition.Top ? 1 : this._rowElements.length - 2]; - // Don't scroll when the last focused item was not the second row (focus is going the other - // direction) - if (e.relatedTarget !== beforeBoundaryElement) { - return; - } - // Don't scroll if the buffer top has reached the end in that direction const posInSet = boundaryElement.getAttribute('aria-posinset'); const lastRowPos = position === BoundaryPosition.Top ? '1' : `${this._terminal.buffer.lines.length}`; @@ -124,6 +114,12 @@ export class AccessibilityManager implements IDisposable { return; } + // Don't scroll when the last focused item was not the second row (focus is going the other + // direction) + if (e.relatedTarget !== beforeBoundaryElement) { + return; + } + // Remove old boundary element from array let topBoundaryElement: HTMLElement; let bottomBoundaryElement: HTMLElement; @@ -167,64 +163,6 @@ export class AccessibilityManager implements IDisposable { e.stopImmediatePropagation(); } - /** - * Moves the focus of the terminal relatively by a number of rows. - * @param amount The amount of rows to scroll - */ - public moveRowFocus(amount: number): void { - console.log('moveRowFocus', amount); - // Do nothing if not navigating rows or the amount to navigate is 0 - if (!this.isNavigatingRows || amount === 0) { - return; - } - - // Find and validate the new absolute row position - const buffer = this._terminal.buffer; - const oldRelativeRow = this._rowElements.indexOf(document.activeElement); - const oldAbsoluteRow = buffer.ydisp + oldRelativeRow; - const newAbsoluteRow = Math.max(Math.min(oldAbsoluteRow + amount, buffer.lines.length - 1), 0); - if (oldAbsoluteRow === newAbsoluteRow) { - return; - } - - // Find the new relative row, this cannot be on a boundary element unless the focused row is the - // top-most or bottom-most row in the buffer. - let newRelativeRow: number; - if (newAbsoluteRow === 0) { - newRelativeRow = 0; - } else if (newAbsoluteRow === buffer.lines.length - 1) { - newRelativeRow = this._terminal.rows - 1; - } else { - newRelativeRow = Math.max(Math.min(oldRelativeRow + amount, this._terminal.rows - 2), 1); - } - - // Find the new viewport position - let newYDisp: number; - if (newAbsoluteRow === 0) { - // Start of buffer - newYDisp = 0; - } else if (newAbsoluteRow === buffer.lines.length - 1) { - // End of buffer - newYDisp = buffer.lines.length - 1 - this._terminal.rows; - } else if (newAbsoluteRow > buffer.ydisp && newAbsoluteRow < buffer.ydisp + this._terminal.rows - 1) { - // No scrolling necessary - newYDisp = buffer.ydisp; - } else if (newAbsoluteRow < oldAbsoluteRow) { - // Scrolling up needed - newYDisp = newAbsoluteRow - 1; - } else { // if (newAbsoluteRow > oldAbsoluteRow) - // Scrolling down needed - newYDisp = newAbsoluteRow + this._terminal.rows - 2; - } - - // Perform scroll - const scrollAmount = newYDisp - buffer.ydisp; - this._terminal.scrollLines(scrollAmount); - - // Focus new row - this._rowElements[newRelativeRow].focus(); - } - private _onResize(cols: number, rows: number): void { // Remove bottom boundary listener this._rowElements[this._rowElements.length - 1].removeEventListener('focus', this._bottomBoundaryFocusListener); diff --git a/src/Terminal.ts b/src/Terminal.ts index d0389048c..4eafc7a04 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1187,40 +1187,26 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.refresh(0, this.rows - 1); } - /** - * Scrolls the viewport by a number of rows when not navigating rows. When - * navigating rows, move focus relatively by a number of rows. - * @param amount The number of lines to scroll. - */ - private _accessibilityAwareScrollLines(amount: number): void { - console.log('_accessibilityAwareScrollLines'); - if (this.options.screenReaderMode && this._accessibilityManager.isNavigatingRows) { - this._accessibilityManager.moveRowFocus(amount); - } else { - this.scrollLines(amount); - } - } - /** * Scroll the display of the terminal by a number of pages. * @param {number} pageCount The number of pages to scroll (negative scrolls up). */ public scrollPages(pageCount: number): void { - this._accessibilityAwareScrollLines(pageCount * (this.rows - 1)); + this.scrollLines(pageCount * (this.rows - 1)); } /** * Scrolls the display of the terminal to the top. */ public scrollToTop(): void { - this._accessibilityAwareScrollLines(-this.buffer.ydisp); + this.scrollLines(-this.buffer.ydisp); } /** * Scrolls the display of the terminal to the bottom. */ public scrollToBottom(): void { - this._accessibilityAwareScrollLines(this.buffer.ybase - this.buffer.ydisp); + this.scrollLines(this.buffer.ybase - this.buffer.ydisp); } /** From ec156f62e1cda51682ab0ffbd253f420435410ea Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sat, 27 Jan 2018 18:38:28 -0800 Subject: [PATCH 57/86] Only update a11y manager dimensions after a renderer resize --- src/AccessibilityManager.ts | 5 ++++- src/renderer/Renderer.ts | 2 -- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 286c1f987..cb9155165 100644 --- a/src/AccessibilityManager.ts +++ b/src/AccessibilityManager.ts @@ -80,13 +80,13 @@ export class AccessibilityManager implements IDisposable { this._disposables.push(this._terminal.addDisposableListener('a11y.char', (char) => this._onChar(char))); this._disposables.push(this._terminal.addDisposableListener('linefeed', () => this._onChar('\n'))); this._disposables.push(this._terminal.addDisposableListener('a11y.tab', spaceCount => this._onTab(spaceCount))); - this._disposables.push(this._terminal.addDisposableListener('charsizechanged', () => this._refreshRowsDimensions())); this._disposables.push(this._terminal.addDisposableListener('key', keyChar => this._onKey(keyChar))); this._disposables.push(this._terminal.addDisposableListener('blur', () => this._clearLiveRegion())); // TODO: Maybe renderer should fire an event on terminal when the characters change and that // should be listened to instead? That would mean that the order of events are always // guarenteed this._disposables.push(this._terminal.addDisposableListener('dprchange', () => this._refreshRowsDimensions())); + this._disposables.push(this._terminal.renderer.addDisposableListener('resize', () => this._refreshRowsDimensions())); // This shouldn't be needed on modern browsers but is present in case the // media query that drives the dprchange event isn't supported this._disposables.push(addDisposableListener(window, 'resize', () => this._refreshRowsDimensions())); @@ -262,6 +262,9 @@ export class AccessibilityManager implements IDisposable { } private _refreshRowsDimensions(): void { + if (!this._terminal.renderer.dimensions.actualCellHeight) { + return; + } const buffer: IBuffer = this._terminal.buffer; for (let i = 0; i < this._terminal.rows; i++) { this._refreshRowDimensions(this._rowElements[i]); diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 80bc9f44a..f281f9a9f 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -242,7 +242,5 @@ export class Renderer extends EventEmitter implements IRenderer { // differ. this.dimensions.actualCellHeight = this.dimensions.canvasHeight / this._terminal.rows; this.dimensions.actualCellWidth = this.dimensions.canvasWidth / this._terminal.cols; - } - } From a0f390ef5429a7d10029c0c3f2216a1a128e7644 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Jan 2018 13:10:11 -0800 Subject: [PATCH 58/86] Expose strings through the API --- src/Terminal.ts | 6 +++++- src/utils/TestUtils.test.ts | 1 + typings/xterm.d.ts | 13 ++++++++++++- 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 4eafc7a04..48d00b2b5 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -47,7 +47,7 @@ import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; import { ScreenDprMonitor } from './utils/ScreenDprMonitor'; -import { ITheme } from 'xterm'; +import { ITheme, ILocalizableStrings } from 'xterm'; // Let it work inside Node.js for automated testing purposes. const document = (typeof window !== 'undefined') ? window.document : null; @@ -313,6 +313,10 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT return this.buffers.active; } + public static get strings(): ILocalizableStrings { + return Strings; + } + /** * back_color_erase feature for xterm. */ diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index dd9126760..0c79e98f8 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -10,6 +10,7 @@ import * as Browser from '../shared/utils/Browser'; import { ITheme, IDisposable } from 'xterm'; export class MockTerminal implements ITerminal { + static string: any; getOption(key: any): any { throw new Error('Method not implemented.'); } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index c48c814bb..ea39ffcdd 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -207,12 +207,18 @@ declare module 'xterm' { } /** - * An object that can be disposed via a dipose function. + * An object that can be disposed via a dispose function. */ export interface IDisposable { dispose(): void; } + export interface ILocalizableStrings { + blankLine: string; + promptLabel: string; + tooMuchOutput: string; + } + /** * The class that represents an xterm.js terminal. */ @@ -237,6 +243,11 @@ declare module 'xterm' { */ cols: number; + /** + * Natural language strings that can be localized. + */ + static strings: ILocalizableStrings; + /** * Creates a new `Terminal` object. * From 9ade1f0a5d8586fcbc88f8e66fe6cbfc1512fead Mon Sep 17 00:00:00 2001 From: Saad Malik Date: Sun, 28 Jan 2018 17:11:13 -0800 Subject: [PATCH 59/86] Add mapping for backward slash * Add mapping for backward slash * Fix mapping for single/double quote --- src/Terminal.test.ts | 8 ++++++-- src/Terminal.ts | 3 ++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/Terminal.test.ts b/src/Terminal.test.ts index 301ab020e..12467d84f 100644 --- a/src/Terminal.test.ts +++ b/src/Terminal.test.ts @@ -659,13 +659,17 @@ describe('term.js addons', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 219 }).key, '\x1b['); assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 219 }).key, '\x1b{'); }); + it('should return proper sequences for alt+\\', () => { + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 220 }).key, '\x1b\\'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 220 }).key, '\x1b|'); + }); it('should return proper sequences for alt+]', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 221 }).key, '\x1b]'); assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 221 }).key, '\x1b}'); }); - it('should return proper sequences for alt+\\', () => { + it('should return proper sequences for alt+\'', () => { assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: false, keyCode: 222 }).key, '\x1b\''); - assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b|'); + assert.equal(term.evaluateKeyEscapeSequence({ altKey: true, shiftKey: true, keyCode: 222 }).key, '\x1b"'); }); }); diff --git a/src/Terminal.ts b/src/Terminal.ts index cdbda41ff..88dc2b6ba 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -69,8 +69,9 @@ const KEYCODE_KEY_MAPPINGS = { 191: ['/', '?'], 192: ['`', '~'], 219: ['[', '{'], + 220: ['\\', '|'], 221: [']', '}'], - 222: ['\'', '|'] + 222: ['\'', '"'] }; // Let it work inside Node.js for automated testing purposes. From bbac1801e5328900328667e129f6fb8428aa93fe Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Sun, 28 Jan 2018 17:33:51 -0800 Subject: [PATCH 60/86] Small clean up --- src/SelectionManager.ts | 4 +++- src/handlers/Clipboard.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 00d22fb6a..0d425a914 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -257,7 +257,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager return false; } - return (start[1] < coords[1] && end[1] > coords[1]) || (start[1] === coords[1] && coords[0] > start[0]) || (end[1] === coords[1] && coords[0] < end[0]); + return (start[1] < coords[1] && end[1] > coords[1]) || + (start[1] === coords[1] && coords[0] > start[0]) || + (end[1] === coords[1] && coords[0] < end[0]); } /** diff --git a/src/handlers/Clipboard.ts b/src/handlers/Clipboard.ts index 807516c78..7ac97714d 100644 --- a/src/handlers/Clipboard.ts +++ b/src/handlers/Clipboard.ts @@ -121,8 +121,9 @@ export function moveTextAreaUnderMouseCursor(ev: MouseEvent, textarea: HTMLTextA export function rightClickHandler(ev: MouseEvent, textarea: HTMLTextAreaElement, selectionManager: ISelectionManager, shouldSelectWord: boolean): void { moveTextAreaUnderMouseCursor(ev, textarea); - if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) + if (shouldSelectWord && !selectionManager.isClickInSelection(ev)) { selectionManager.selectWordAtCursor(ev); + } // Get textarea ready to copy from the context menu textarea.value = selectionManager.selectionText; From 983af8fac218a082741368e591b57108bdfa5802 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Wed, 31 Jan 2018 00:01:46 +0000 Subject: [PATCH 61/86] Use _selectWordA --- src/SelectionManager.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 0d425a914..c01d7b6d5 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -269,13 +269,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager public selectWordAtCursor(event: MouseEvent): void { const coords = this._getMouseBufferCoords(event); if (coords) { - const wordPosition = this._getWordAt(coords, false); - if (wordPosition) { - this._model.selectionStart = [wordPosition.start, coords[1]]; - this._model.selectionStartLength = wordPosition.length; - this._model.selectionEnd = [this._model.areSelectionValuesReversed() ? wordPosition.start : (wordPosition.start + wordPosition.length), coords[1]]; - this.refresh(true); - } + this._selectWordAt(coords, false); + this.refresh(true); } } From e8496295333b91372b463c1aa0497f93b6e0fcf3 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 31 Jan 2018 11:25:13 -0800 Subject: [PATCH 62/86] typescript@2.7 Tuples using a fixed length by default will be helpful for CharData --- package-lock.json | 3526 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 3523 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index f7c01276d..03bbd1155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "xterm", - "version": "3.0.0", + "version": "3.1.0-master", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -77,6 +77,15 @@ "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=", "dev": true }, + "acorn-dynamic-import": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/acorn-dynamic-import/-/acorn-dynamic-import-2.0.2.tgz", + "integrity": "sha1-x1K9IQvvZ5UBtsbLf8hPj0cVjMQ=", + "dev": true, + "requires": { + "acorn": "4.0.13" + } + }, "acorn-globals": { "version": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", @@ -92,6 +101,35 @@ } } }, + "ajv": { + "version": "5.5.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", + "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", + "dev": true, + "requires": { + "co": "4.6.0", + "fast-deep-equal": "1.0.0", + "fast-json-stable-stringify": "2.0.0", + "json-schema-traverse": "0.3.1" + } + }, + "ajv-keywords": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", + "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", + "dev": true + }, + "align-text": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", + "dev": true, + "requires": { + "kind-of": "3.2.2", + "longest": "1.0.1", + "repeat-string": "1.6.1" + } + }, "amdefine": { "version": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", @@ -108,6 +146,16 @@ "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, + "anymatch": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", + "dev": true, + "requires": { + "micromatch": "2.3.11", + "normalize-path": "2.1.1" + } + }, "argparse": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", @@ -117,6 +165,21 @@ "sprintf-js": "1.0.3" } }, + "arr-diff": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", + "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", + "dev": true, + "requires": { + "arr-flatten": "1.1.0" + } + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "dev": true + }, "array-differ": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", @@ -164,6 +227,12 @@ "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, + "array-unique": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", + "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", + "dev": true + }, "asn1": { "version": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", @@ -203,6 +272,21 @@ "acorn": "4.0.13" } }, + "async": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", + "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "dev": true, + "requires": { + "lodash": "4.17.4" + } + }, + "async-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.1.tgz", + "integrity": "sha1-GdOGodntxufByF04iu28xW0zYC0=", + "dev": true + }, "asynckit": { "version": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", @@ -319,6 +403,18 @@ "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", "dev": true }, + "big.js": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-3.2.0.tgz", + "integrity": "sha512-+hN/Zh2D08Mx65pZ/4g5bsmNiZUuChDiQfTUQ7qJr4/kuopCr88xZsAXv6mBoZEsUI4OuGHlX59qE94K2mMW8Q==", + "dev": true + }, + "binary-extensions": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-1.11.0.tgz", + "integrity": "sha1-RqoXUftqL5PuXmibsQh9SxTGwgU=", + "dev": true + }, "bn.js": { "version": "4.11.7", "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.7.tgz", @@ -343,6 +439,17 @@ "concat-map": "0.0.1" } }, + "braces": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", + "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", + "dev": true, + "requires": { + "expand-range": "1.8.2", + "preserve": "0.2.0", + "repeat-element": "1.1.2" + } + }, "brorand": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", @@ -601,11 +708,27 @@ "integrity": "sha1-0JxLUoAKpMB44t2BqGmqyQ0uVOc=", "dev": true }, + "camelcase": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=", + "dev": true + }, "caseless": { "version": "https://registry.npmjs.org/caseless/-/caseless-0.11.0.tgz", "integrity": "sha1-cVuW6phBWTzDMGeSP17GDr2k99c=", "dev": true }, + "center-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", + "dev": true, + "requires": { + "align-text": "0.1.4", + "lazy-cache": "1.0.4" + } + }, "chai": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/chai/-/chai-3.5.0.tgz", @@ -660,6 +783,23 @@ "supports-color": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz" } }, + "chokidar": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-1.7.0.tgz", + "integrity": "sha1-eY5ol3gVHIB2tLNg5e3SjNortGg=", + "dev": true, + "requires": { + "anymatch": "1.3.2", + "async-each": "1.0.1", + "fsevents": "1.1.3", + "glob-parent": "2.0.0", + "inherits": "2.0.3", + "is-binary-path": "1.0.1", + "is-glob": "2.0.1", + "path-is-absolute": "1.0.1", + "readdirp": "2.1.0" + } + }, "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", @@ -670,6 +810,17 @@ "safe-buffer": "5.1.1" } }, + "cliui": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", + "dev": true, + "requires": { + "center-align": "0.1.3", + "right-align": "0.1.3", + "wordwrap": "0.0.2" + } + }, "clone": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", @@ -699,6 +850,18 @@ "through2": "2.0.3" } }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", @@ -876,6 +1039,17 @@ "sha.js": "2.4.8" } }, + "cross-spawn": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "dev": true, + "requires": { + "lru-cache": "4.1.1", + "shebang-command": "1.2.0", + "which": "1.3.0" + } + }, "cryptiles": { "version": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", @@ -915,6 +1089,15 @@ "cssom": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz" } }, + "d": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", + "integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", + "dev": true, + "requires": { + "es5-ext": "0.10.38" + } + }, "dashdash": { "version": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", @@ -942,6 +1125,12 @@ "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=", "dev": true }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "dev": true + }, "deep-is": { "version": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", @@ -1070,6 +1259,120 @@ "minimalistic-crypto-utils": "1.0.1" } }, + "emojis-list": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-2.1.0.tgz", + "integrity": "sha1-TapNnbAPmBmIDHn6RXrlsJof04k=", + "dev": true + }, + "enhanced-resolve": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-3.4.1.tgz", + "integrity": "sha1-BCHjOf1xQZs9oT0Smzl5BAIwR24=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "memory-fs": "0.4.1", + "object-assign": "4.1.1", + "tapable": "0.2.8" + }, + "dependencies": { + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, + "errno": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.6.tgz", + "integrity": "sha512-IsORQDpaaSwcDP4ZZnHxgE85werpo34VYn1Ud3mq+eUsF593faR8oCZNXrROVkpFu2TsbrNhHin0aUrTsQ9vNw==", + "dev": true, + "requires": { + "prr": "1.0.1" + } + }, + "error-ex": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", + "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", + "dev": true, + "requires": { + "is-arrayish": "0.2.1" + } + }, + "es5-ext": { + "version": "0.10.38", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.38.tgz", + "integrity": "sha512-jCMyePo7AXbUESwbl8Qi01VSH2piY9s/a3rSU/5w/MlTIx8HPL1xn2InGN8ejt/xulcJgnTO7vqNtOAxzYd2Kg==", + "dev": true, + "requires": { + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-symbol": "3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-set": "0.1.5", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1", + "event-emitter": "0.3.5" + } + }, + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38" + } + }, + "es6-weak-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", + "integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38", + "es6-iterator": "2.0.3", + "es6-symbol": "3.1.1" + } + }, "escape-string-regexp": { "version": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", @@ -1098,11 +1401,55 @@ } } }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "0.1.5", + "es6-weak-map": "2.0.2", + "esrecurse": "4.2.0", + "estraverse": "4.2.0" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + } + } + }, "esprima": { "version": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha1-luO3DVd59q1JzQMmc9HDEnZ7pYE=", "dev": true }, + "esrecurse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.0.tgz", + "integrity": "sha1-+pVo2Y04I/mkHZHpAtyrnqblsWM=", + "dev": true, + "requires": { + "estraverse": "4.2.0", + "object-assign": "4.1.1" + }, + "dependencies": { + "estraverse": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", + "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + } + } + }, "estraverse": { "version": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", "integrity": "sha1-r2fy3JIlgkFZUJJgkaQAXSnJu0Q=", @@ -1113,6 +1460,16 @@ "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", "dev": true }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1.0.0", + "es5-ext": "0.10.38" + } + }, "events": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz", @@ -1128,12 +1485,45 @@ "create-hash": "1.1.3" } }, + "execa": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", + "dev": true, + "requires": { + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" + } + }, "exit-on-epipe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", "dev": true }, + "expand-brackets": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", + "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", + "dev": true, + "requires": { + "is-posix-bracket": "0.1.1" + } + }, + "expand-range": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", + "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", + "dev": true, + "requires": { + "fill-range": "2.2.3" + } + }, "express": { "version": "4.13.4", "resolved": "https://registry.npmjs.org/express/-/express-4.13.4.tgz", @@ -1513,6 +1903,15 @@ "integrity": "sha1-WkdDU7nzNT3dgXbf03uRyDpG8dQ=", "dev": true }, + "extglob": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", + "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, "extsprintf": { "version": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", @@ -1585,11 +1984,66 @@ } } }, + "fast-deep-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz", + "integrity": "sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=", + "dev": true + }, + "fast-json-stable-stringify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", + "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", + "dev": true + }, "fast-levenshtein": { "version": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "filename-regex": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", + "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", + "dev": true + }, + "fill-range": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", + "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", + "dev": true, + "requires": { + "is-number": "2.1.0", + "isobject": "2.1.0", + "randomatic": "1.1.7", + "repeat-element": "1.1.2", + "repeat-string": "1.6.1" + } + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "dev": true + }, + "for-own": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", + "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", + "dev": true, + "requires": { + "for-in": "1.0.2" + } + }, "forever-agent": { "version": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", @@ -1642,6 +2096,910 @@ } } }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", @@ -1661,6 +3019,18 @@ "is-property": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz" } }, + "get-caller-file": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", + "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, "getpass": { "version": "https://registry.npmjs.org/getpass/-/getpass-0.1.6.tgz", "integrity": "sha1-KD/9n8ElaECHUxHBtg6MQBhxEOY=", @@ -1766,6 +3136,25 @@ } } }, + "glob-base": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", + "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", + "dev": true, + "requires": { + "glob-parent": "2.0.0", + "is-glob": "2.0.1" + } + }, + "glob-parent": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", + "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", + "dev": true, + "requires": { + "is-glob": "2.0.1" + } + }, "glogg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", @@ -1775,6 +3164,12 @@ "sparkles": "1.0.0" } }, + "graceful-fs": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", + "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", + "dev": true + }, "graceful-readlink": { "version": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", @@ -8613,6 +10008,12 @@ "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", "dev": true }, + "hosted-git-info": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.5.0.tgz", + "integrity": "sha512-pNgbURSuab90KbTqvRPsseaTxOJCZBD0a7t+haSN33piP9cCM4l0CqdzAif2hUqm716UovKB2ROmiabGAKVXyg==", + "dev": true + }, "html-encoding-sniffer": { "version": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz", "integrity": "sha1-eb96eF6klf5mFl5zQVPzY/9UN9o=", @@ -8699,12 +10100,93 @@ } } }, + "interpret": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", + "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", + "dev": true + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-binary-path": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", + "dev": true, + "requires": { + "binary-extensions": "1.11.0" + } + }, "is-buffer": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", "dev": true }, + "is-builtin-module": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", + "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", + "dev": true, + "requires": { + "builtin-modules": "1.1.1" + } + }, + "is-dotfile": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", + "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", + "dev": true + }, + "is-equal-shallow": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", + "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", + "dev": true, + "requires": { + "is-primitive": "2.0.0" + } + }, + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "dev": true + }, + "is-extglob": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", + "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-glob": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", + "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", + "dev": true, + "requires": { + "is-extglob": "1.0.0" + } + }, "is-my-json-valid": { "version": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.15.0.tgz", "integrity": "sha1-k27do8o8IR/ZjzstPgjaQ/eykVs=", @@ -8716,6 +10198,15 @@ "xtend": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" } }, + "is-number": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", + "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + } + }, "is-plain-object": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.3.tgz", @@ -8733,11 +10224,29 @@ } } }, + "is-posix-bracket": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", + "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", + "dev": true + }, + "is-primitive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", + "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", + "dev": true + }, "is-property": { "version": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", "dev": true }, + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + }, "is-typedarray": { "version": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", @@ -8749,6 +10258,29 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "dev": true, + "requires": { + "isarray": "1.0.0" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + } + } + }, "isstream": { "version": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", @@ -9019,11 +10551,23 @@ } } }, + "json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==", + "dev": true + }, "json-schema": { "version": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, + "json-schema-traverse": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", + "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", + "dev": true + }, "json-stable-stringify": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-0.0.1.tgz", @@ -9038,6 +10582,12 @@ "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, + "json5": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", + "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", + "dev": true + }, "jsonify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", @@ -9065,6 +10615,15 @@ "verror": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz" } }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + }, "labeled-stream-splicer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/labeled-stream-splicer/-/labeled-stream-splicer-2.0.0.tgz", @@ -9076,6 +10635,21 @@ "stream-splicer": "2.0.0" } }, + "lazy-cache": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "dev": true, + "requires": { + "invert-kv": "1.0.0" + } + }, "levn": { "version": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", @@ -9094,6 +10668,51 @@ "astw": "2.2.0" } }, + "load-json-file": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", + "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "parse-json": "2.2.0", + "pify": "2.3.0", + "strip-bom": "3.0.0" + } + }, + "loader-runner": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-2.3.0.tgz", + "integrity": "sha1-9IKuqC1UPgeSFwDVpG7yb9rGuKI=", + "dev": true + }, + "loader-utils": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", + "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=", + "dev": true, + "requires": { + "big.js": "3.2.0", + "emojis-list": "2.1.0", + "json5": "0.5.1" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "2.0.0", + "path-exists": "3.0.0" + } + }, + "lodash": { + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=", + "dev": true + }, "lodash._basecopy": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", @@ -9148,6 +10767,12 @@ "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", "dev": true }, + "lodash.clone": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clone/-/lodash.clone-4.5.0.tgz", + "integrity": "sha1-GVhwRQ9aExkkeN9Lw9I9LeoZB7Y=", + "dev": true + }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -9192,6 +10817,12 @@ "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", "dev": true }, + "lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=", + "dev": true + }, "lodash.sortby": { "version": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", @@ -9224,6 +10855,73 @@ "lodash.escape": "3.2.0" } }, + "longest": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=", + "dev": true + }, + "lru-cache": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==", + "dev": true, + "requires": { + "pseudomap": "1.0.2", + "yallist": "2.1.2" + } + }, + "mem": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", + "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", + "dev": true, + "requires": { + "mimic-fn": "1.1.0" + } + }, + "memory-fs": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", + "dev": true, + "requires": { + "errno": "0.1.6", + "readable-stream": "2.3.3" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, "merge-stream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", @@ -9289,6 +10987,27 @@ } } }, + "micromatch": { + "version": "2.3.11", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", + "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", + "dev": true, + "requires": { + "arr-diff": "2.0.0", + "array-unique": "0.2.1", + "braces": "1.8.5", + "expand-brackets": "0.1.5", + "extglob": "0.3.2", + "filename-regex": "2.0.1", + "is-extglob": "1.0.0", + "is-glob": "2.0.1", + "kind-of": "3.2.2", + "normalize-path": "2.1.1", + "object.omit": "2.0.1", + "parse-glob": "3.0.4", + "regex-cache": "0.4.4" + } + }, "miller-rabin": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.0.tgz", @@ -9312,6 +11031,12 @@ "mime-db": "https://registry.npmjs.org/mime-db/-/mime-db-1.26.0.tgz" } }, + "mimic-fn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.1.0.tgz", + "integrity": "sha1-5md4PZLonb00KBi1IwudYqZyrRg=", + "dev": true + }, "minimalistic-assert": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.0.tgz", @@ -9339,6 +11064,23 @@ "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + }, + "dependencies": { + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + } + } + }, "mocha": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/mocha/-/mocha-3.4.2.tgz", @@ -9691,6 +11433,111 @@ "integrity": "sha1-7XFfP+neArV6XmJS2QqWZ14fCFo=", "dev": true }, + "node-libs-browser": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/node-libs-browser/-/node-libs-browser-2.1.0.tgz", + "integrity": "sha512-5AzFzdoIMb89hBGMZglEegffzgRg+ZFoUmisQ8HI4j1KDdpx13J0taNp2y9xPbur6W61gepGDDotGBVQ7mfUCg==", + "dev": true, + "requires": { + "assert": "1.4.1", + "browserify-zlib": "0.2.0", + "buffer": "4.9.1", + "console-browserify": "1.1.0", + "constants-browserify": "1.0.0", + "crypto-browserify": "3.11.1", + "domain-browser": "1.1.7", + "events": "1.1.1", + "https-browserify": "1.0.0", + "os-browserify": "0.3.0", + "path-browserify": "0.0.0", + "process": "0.11.10", + "punycode": "1.4.1", + "querystring-es3": "0.2.1", + "readable-stream": "2.3.3", + "stream-browserify": "2.0.1", + "stream-http": "2.7.2", + "string_decoder": "1.0.3", + "timers-browserify": "2.0.6", + "tty-browserify": "0.0.0", + "url": "0.11.0", + "util": "0.10.3", + "vm-browserify": "0.0.4" + }, + "dependencies": { + "browserify-zlib": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", + "dev": true, + "requires": { + "pako": "1.0.6" + } + }, + "https-browserify": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=", + "dev": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "os-browserify": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc=", + "dev": true + }, + "pako": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.6.tgz", + "integrity": "sha512-lQe48YPsMJAig+yngZ87Lus+NF+3mtu7DVOBu6b/gHO1YpKwIj5AWjZ/TOS7i46HD/UixzWb1zeWDZfGZ3iYcg==", + "dev": true + }, + "punycode": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + }, + "timers-browserify": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.6.tgz", + "integrity": "sha512-HQ3nbYRAowdVd0ckGFvmJPPCOH/CHleFN/Y0YQCX1DVaB7t+KFvisuyN09fuP8Jtp1CpfSh8O8bMkHbdbPe6Pw==", + "dev": true, + "requires": { + "setimmediate": "1.0.5" + } + } + } + }, "node-pty": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/node-pty/-/node-pty-0.7.3.tgz", @@ -9833,12 +11680,919 @@ "requires": { "anymatch": "1.3.0", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", "is-glob": "2.0.1", "path-is-absolute": "1.0.1", "readdirp": "2.1.0" + }, + "dependencies": { + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + } } }, "concat-map": { @@ -10854,6 +13608,42 @@ } } }, + "normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "dev": true, + "requires": { + "hosted-git-info": "2.5.0", + "is-builtin-module": "1.0.0", + "semver": "5.5.0", + "validate-npm-package-license": "3.0.1" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "dev": true, + "requires": { + "remove-trailing-separator": "1.0.2" + } + }, + "npm-run-path": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", + "dev": true, + "requires": { + "path-key": "2.0.1" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "oauth-sign": { "version": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", @@ -10900,6 +13690,16 @@ } } }, + "object.omit": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", + "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", + "dev": true, + "requires": { + "for-own": "0.1.5", + "is-extendable": "0.1.1" + } + }, "object.pick": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.2.0.tgz", @@ -10952,6 +13752,47 @@ "integrity": "sha1-ScoCk+CxlZCl9d4Qx/JlphfY/lQ=", "dev": true }, + "os-locale": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", + "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", + "dev": true, + "requires": { + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=", + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", + "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "dev": true, + "requires": { + "p-try": "1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "1.2.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", @@ -10980,12 +13821,51 @@ "pbkdf2": "3.0.12" } }, + "parse-glob": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", + "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", + "dev": true, + "requires": { + "glob-base": "0.3.0", + "is-dotfile": "1.0.3", + "is-extglob": "1.0.0", + "is-glob": "2.0.1" + } + }, + "parse-json": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", + "dev": true, + "requires": { + "error-ex": "1.3.1" + } + }, "path-browserify": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-0.0.0.tgz", "integrity": "sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo=", "dev": true }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", + "dev": true + }, "path-parse": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", @@ -10998,6 +13878,15 @@ "integrity": "sha1-6GQhf3TDaFDwhSt43Hv31KVyG/I=", "dev": true }, + "path-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", + "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", + "dev": true, + "requires": { + "pify": "2.3.0" + } + }, "pbkdf2": { "version": "3.0.12", "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.0.12.tgz", @@ -11011,6 +13900,12 @@ "sha.js": "2.4.8" } }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "dev": true + }, "pinkie": { "version": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", @@ -11035,6 +13930,12 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "preserve": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", + "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", + "dev": true + }, "printj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.0.tgz", @@ -11053,6 +13954,18 @@ "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", "dev": true }, + "prr": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, "public-encrypt": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.0.tgz", @@ -11083,6 +13996,47 @@ "integrity": "sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM=", "dev": true }, + "randomatic": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dev": true, + "requires": { + "is-number": "3.0.0", + "kind-of": "4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dev": true, + "requires": { + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "dev": true, + "requires": { + "is-buffer": "1.1.5" + } + } + } + }, "randombytes": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", @@ -11133,6 +14087,27 @@ } } }, + "read-pkg": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", + "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", + "dev": true, + "requires": { + "load-json-file": "2.0.0", + "normalize-package-data": "2.4.0", + "path-type": "2.0.0" + } + }, + "read-pkg-up": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", + "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", + "dev": true, + "requires": { + "find-up": "2.1.0", + "read-pkg": "2.0.0" + } + }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", @@ -11145,12 +14120,77 @@ "string_decoder": "0.10.31" } }, + "readdirp": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.1.0.tgz", + "integrity": "sha1-TtCtBg3zBzMAxIRANz9y0cxkLXg=", + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "minimatch": "3.0.4", + "readable-stream": "2.3.3", + "set-immediate-shim": "1.0.1" + }, + "dependencies": { + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "readable-stream": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.3.tgz", + "integrity": "sha512-m+qzzcn7KUxEmd1gMbchF+Y2eIUbieUaxkWtptyHywrX0rE8QEYqPC07Vuy4Wm32/xE16NcdBctb8S0Xe/5IeQ==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" + } + }, + "string_decoder": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.3.tgz", + "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } + } + } + }, + "regex-cache": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", + "dev": true, + "requires": { + "is-equal-shallow": "0.1.3" + } + }, "remove-trailing-separator": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz", "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", "dev": true }, + "repeat-element": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", + "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "dev": true + }, "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -11224,6 +14264,18 @@ "tough-cookie": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz" } }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", + "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", + "dev": true + }, "resolve": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.4.0.tgz", @@ -11233,6 +14285,15 @@ "path-parse": "1.0.5" } }, + "right-align": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", + "dev": true, + "requires": { + "align-text": "0.1.4" + } + }, "ripemd160": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", @@ -11260,6 +14321,24 @@ "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", "dev": true }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "set-immediate-shim": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", + "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", + "dev": true + }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=", + "dev": true + }, "sha.js": { "version": "2.4.8", "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.8.tgz", @@ -11279,6 +14358,21 @@ "sha.js": "2.4.8" } }, + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "dev": true, + "requires": { + "shebang-regex": "1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", + "dev": true + }, "shell-quote": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.6.1.tgz", @@ -11291,6 +14385,12 @@ "jsonify": "0.0.0" } }, + "signal-exit": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", + "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", + "dev": true + }, "sntp": { "version": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", @@ -11390,6 +14490,12 @@ } } }, + "source-list-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.0.tgz", + "integrity": "sha512-I2UmuJSRr/T8jisiROLU3A3ltr+swpniSmNPI4Ml3ZCX6tVnDsuZzK7F2hl5jTqbZBWCEKlj5HRQiPExXLgE8A==", + "dev": true + }, "source-map": { "version": "0.5.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.6.tgz", @@ -11402,6 +14508,27 @@ "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", "dev": true }, + "spdx-correct": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", + "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", + "dev": true, + "requires": { + "spdx-license-ids": "1.2.2" + } + }, + "spdx-expression-parse": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", + "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", + "dev": true + }, + "spdx-license-ids": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", + "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", + "dev": true + }, "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", @@ -11623,6 +14750,39 @@ } } }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "3.0.0" + } + } + } + }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", @@ -11642,6 +14802,18 @@ "ansi-regex": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-eof": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", + "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", + "dev": true + }, "subarg": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/subarg/-/subarg-1.0.0.tgz", @@ -11670,6 +14842,12 @@ "acorn": "4.0.13" } }, + "tapable": { + "version": "0.2.8", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-0.2.8.tgz", + "integrity": "sha1-mTcqXJmb8t8WCvwNdL7U9HlIzSI=", + "dev": true + }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", @@ -11868,11 +15046,54 @@ "dev": true }, "typescript": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.4.2.tgz", - "integrity": "sha1-+DlfhdRZJ2BnyYiqQYN6j4KHCEQ=", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.1.tgz", + "integrity": "sha512-bqB1yS6o9TNA9ZC/MJxM0FZzPnZdtHj0xWK/IZ5khzVqdpGul/R/EIiHRgFXlwTD7PSIaYVnGKq1QgMCu2mnqw==", "dev": true }, + "uglify-js": { + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", + "dev": true, + "requires": { + "source-map": "0.5.6", + "uglify-to-browserify": "1.0.2", + "yargs": "3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.10.0.tgz", + "integrity": "sha1-9+572FfdfB0tOMDnTvvWgdFDH9E=", + "dev": true, + "requires": { + "camelcase": "1.2.1", + "cliui": "2.1.0", + "decamelize": "1.2.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz", + "integrity": "sha1-bgkk1r2mta/jSeOabWMoUKD4grc=", + "dev": true, + "optional": true + }, + "uglifyjs-webpack-plugin": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-0.4.6.tgz", + "integrity": "sha1-uVH0q7a9YX5m9j64kUmOORdj4wk=", + "dev": true, + "requires": { + "source-map": "0.5.6", + "uglify-js": "2.8.29", + "webpack-sources": "1.1.0" + } + }, "umd": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/umd/-/umd-3.0.1.tgz", @@ -11920,6 +15141,16 @@ "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", "dev": true }, + "validate-npm-package-license": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", + "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", + "dev": true, + "requires": { + "spdx-correct": "1.0.2", + "spdx-expression-parse": "1.0.4" + } + }, "verror": { "version": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", @@ -12107,11 +15338,136 @@ "indexof": "0.0.1" } }, + "watchpack": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-1.4.0.tgz", + "integrity": "sha1-ShRyvLuVK9Cpu0A2gB+VTfs5+qw=", + "dev": true, + "requires": { + "async": "2.6.0", + "chokidar": "1.7.0", + "graceful-fs": "4.1.11" + } + }, "webidl-conversions": { "version": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.1.tgz", "integrity": "sha1-gBWherg+fhsxFjhIas6B2mziBqA=", "dev": true }, + "webpack": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-3.10.0.tgz", + "integrity": "sha512-fxxKXoicjdXNUMY7LIdY89tkJJJ0m1Oo8PQutZ5rLgWbV5QVKI15Cn7+/IHnRTd3vfKfiwBx6SBqlorAuNA8LA==", + "dev": true, + "requires": { + "acorn": "5.3.0", + "acorn-dynamic-import": "2.0.2", + "ajv": "5.5.2", + "ajv-keywords": "2.1.1", + "async": "2.6.0", + "enhanced-resolve": "3.4.1", + "escope": "3.6.0", + "interpret": "1.1.0", + "json-loader": "0.5.7", + "json5": "0.5.1", + "loader-runner": "2.3.0", + "loader-utils": "1.1.0", + "memory-fs": "0.4.1", + "mkdirp": "0.5.1", + "node-libs-browser": "2.1.0", + "source-map": "0.5.6", + "supports-color": "4.5.0", + "tapable": "0.2.8", + "uglifyjs-webpack-plugin": "0.4.6", + "watchpack": "1.4.0", + "webpack-sources": "1.1.0", + "yargs": "8.0.2" + }, + "dependencies": { + "acorn": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.3.0.tgz", + "integrity": "sha512-Yej+zOJ1Dm/IMZzzj78OntP/r3zHEaKcyNoU2lAaxPtrseM6rF0xwqoz5Q5ysAiED9hTjI2hgtvLXitlCN1/Ug==", + "dev": true + }, + "supports-color": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.5.0.tgz", + "integrity": "sha1-vnoN5ITexcXN34s9WRJQRJEvY1s=", + "dev": true, + "requires": { + "has-flag": "2.0.0" + } + } + } + }, + "webpack-sources": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-1.1.0.tgz", + "integrity": "sha512-aqYp18kPphgoO5c/+NaUvEeACtZjMESmDChuD3NBciVpah3XpMEU9VAAtIaB1BsfJWWTSdv8Vv1m3T0aRk2dUw==", + "dev": true, + "requires": { + "source-list-map": "2.0.0", + "source-map": "0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "webpack-stream": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/webpack-stream/-/webpack-stream-4.0.0.tgz", + "integrity": "sha1-82c92QfW2bHqe/UfzR24W1/Z4PI=", + "dev": true, + "requires": { + "gulp-util": "3.0.8", + "lodash.clone": "4.5.0", + "lodash.some": "4.6.0", + "memory-fs": "0.4.1", + "through": "2.3.8", + "vinyl": "2.1.0", + "webpack": "3.10.0" + }, + "dependencies": { + "clone": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.1.tgz", + "integrity": "sha1-0hfR6WERjjrJpLi7oyhVU79kfNs=", + "dev": true + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "dev": true + }, + "replace-ext": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", + "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", + "dev": true + }, + "vinyl": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", + "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "dev": true, + "requires": { + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.0.0", + "remove-trailing-separator": "1.0.2", + "replace-ext": "1.0.0" + } + } + } + }, "whatwg-encoding": { "version": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz", "integrity": "sha1-PGxFGhmO567FWx7GHQkgxngBpfQ=", @@ -12120,6 +15476,71 @@ "iconv-lite": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.13.tgz" } }, + "which": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "dev": true, + "requires": { + "isexe": "2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.0.tgz", + "integrity": "sha1-VDjNLqk7IC76Ohn+iIeu58lPnJ0=", + "dev": true + }, + "wordwrap": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.2.tgz", + "integrity": "sha1-t5Zpu0LstAn4PVg8rVLKF+qhZD8=", + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, "xml-name-validator": { "version": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-2.0.1.tgz", "integrity": "sha1-TYuPHszTQZqjYgYb7O9RXh5VljU=", @@ -12130,6 +15551,103 @@ "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", "dev": true }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", + "dev": true + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + }, + "yargs": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-8.0.2.tgz", + "integrity": "sha1-YpmpBVsc78lp/355wdkY3Osiw2A=", + "dev": true, + "requires": { + "camelcase": "4.1.0", + "cliui": "3.2.0", + "decamelize": "1.2.0", + "get-caller-file": "1.0.2", + "os-locale": "2.1.0", + "read-pkg-up": "2.0.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "dev": true, + "requires": { + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wrap-ansi": "2.1.0" + }, + "dependencies": { + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + } + } + }, + "yargs-parser": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-7.0.0.tgz", + "integrity": "sha1-jQrELxbqVd69MyyvTEA4s+P139k=", + "dev": true, + "requires": { + "camelcase": "4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", + "dev": true + } + } + }, "zmodem.js": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/zmodem.js/-/zmodem.js-0.1.6.tgz", diff --git a/package.json b/package.json index ba988d613..3e14ff3f9 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "nodemon": "1.10.2", "sorcery": "^0.10.0", "tslint": "^5.9.1", - "typescript": "~2.4.0", + "typescript": "~2.7.1", "vinyl-buffer": "^1.0.0", "vinyl-source-stream": "^1.1.0", "webpack": "^3.10.0", From 2ac5a8da4970d82fbe67bdb9b4998e956ecd7943 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 31 Jan 2018 17:42:33 -0800 Subject: [PATCH 63/86] Allow support of modifiers with links Fixes #1021 --- src/Linkifier.ts | 7 +++++++ src/Types.ts | 8 ++++++++ src/input/MouseZoneManager.ts | 15 ++++++++------- src/input/Types.ts | 7 ++++--- typings/xterm.d.ts | 12 ++++++++++-- 5 files changed, 37 insertions(+), 12 deletions(-) diff --git a/src/Linkifier.ts b/src/Linkifier.ts index da901a6ed..a9617952d 100644 --- a/src/Linkifier.ts +++ b/src/Linkifier.ts @@ -150,6 +150,7 @@ export class Linkifier extends EventEmitter implements ILinkifier { validationCallback: options.validationCallback, hoverTooltipCallback: options.tooltipCallback, hoverLeaveCallback: options.leaveCallback, + willLinkActivate: options.willLinkActivate, priority: options.priority || 0 }; this._addLinkMatcherToList(matcher); @@ -290,6 +291,12 @@ export class Linkifier extends EventEmitter implements ILinkifier { if (matcher.hoverLeaveCallback) { matcher.hoverLeaveCallback(); } + }, + e => { + if (matcher.willLinkActivate) { + return matcher.willLinkActivate(e, uri); + } + return true; } )); } diff --git a/src/Types.ts b/src/Types.ts index da3cc9d2a..9dd7dda2d 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -161,6 +161,7 @@ export interface ILinkMatcher { matchIndex?: number; validationCallback?: LinkMatcherValidationCallback; priority?: number; + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } export interface ICharset { @@ -321,6 +322,13 @@ export interface ILinkMatcherOptions { * default value is 0. */ priority?: number; + /** + * A callback that fires when the mousedown and click events occur that + * determines whether a link will be activated upon click. This enables + * only activating a link when a certain modifier is held down, if not the + * mouse event will continue propagation (eg. double click to select word). + */ + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } export interface IBrowser { diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 98377f36c..79c02b5f2 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -151,10 +151,10 @@ export class MouseZoneManager implements IMouseZoneManager { // components from handling the mouse event. const zone = this._findZoneEventAt(e); if (zone) { - // TODO: When link modifier support is added, the event should only be - // cancelled when the modifier is held (see #1021) - e.preventDefault(); - e.stopImmediatePropagation(); + if (zone.willLinkActivate(e)) { + e.preventDefault(); + e.stopImmediatePropagation(); + } } } @@ -189,9 +189,10 @@ export class MouseZone implements IMouseZone { public x2: number, public y: number, public clickCallback: (e: MouseEvent) => any, - public hoverCallback?: (e: MouseEvent) => any, - public tooltipCallback?: (e: MouseEvent) => any, - public leaveCallback?: () => void + public hoverCallback: (e: MouseEvent) => any, + public tooltipCallback: (e: MouseEvent) => any, + public leaveCallback: () => void, + public willLinkActivate: (e: MouseEvent) => boolean ) { } } diff --git a/src/input/Types.ts b/src/input/Types.ts index 21514a53c..f13984646 100644 --- a/src/input/Types.ts +++ b/src/input/Types.ts @@ -13,7 +13,8 @@ export interface IMouseZone { x2: number; y: number; clickCallback: (e: MouseEvent) => any; - hoverCallback?: (e: MouseEvent) => any; - tooltipCallback?: (e: MouseEvent) => any; - leaveCallback?: () => any; + hoverCallback: (e: MouseEvent) => any | undefined; + tooltipCallback: (e: MouseEvent) => any | undefined; + leaveCallback: () => any | undefined; + willLinkActivate: (e: MouseEvent) => boolean; } diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 0b35edf11..2f6b4cd85 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -174,8 +174,8 @@ declare module 'xterm' { matchIndex?: number; /** - * A callback that validates an individual link, returning true if valid and - * false if invalid. + * A callback that validates whether to create an individual link, pass + * whether the link is valid to the callback. */ validationCallback?: (uri: string, callback: (isValid: boolean) => void) => void; @@ -196,6 +196,14 @@ declare module 'xterm' { * default value is 0. */ priority?: number; + + /** + * A callback that fires when the mousedown and click events occur that + * determines whether a link will be activated upon click. This enables + * only activating a link when a certain modifier is held down, if not the + * mouse event will continue propagation (eg. double click to select word). + */ + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } export interface IEventEmitter { From 610597530867e10fb91baf85d65cadf6c38c214a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 1 Feb 2018 10:24:17 -0800 Subject: [PATCH 64/86] Ensure viewport is attached before syncing scrollbar Fixes #1265 --- src/Viewport.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/Viewport.ts b/src/Viewport.ts index 87ecd69ec..59dc304d4 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -93,6 +93,12 @@ export class Viewport implements IViewport { * @param ev The scroll event. */ private onScroll(ev: Event): void { + // Don't attempt to scroll if the element is not visible, otherwise scrollTop will be corrupt + // which causes the terminal to scroll the buffer to the top + if (!this.viewportElement.offsetParent) { + return; + } + const newRow = Math.round(this.viewportElement.scrollTop / this.currentRowHeight); const diff = newRow - this.terminal.buffer.ydisp; this.terminal.scrollLines(diff, true); From 04ccb638b44b2b8fd5f4dbba1e8138cc8b500db7 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 1 Feb 2018 11:28:56 -0800 Subject: [PATCH 65/86] Fix lint --- src/Types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Types.ts b/src/Types.ts index 9dd7dda2d..c84337079 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -328,7 +328,7 @@ export interface ILinkMatcherOptions { * only activating a link when a certain modifier is held down, if not the * mouse event will continue propagation (eg. double click to select word). */ - willLinkActivate?: (event: MouseEvent, uri: string) => boolean; + willLinkActivate?: (event: MouseEvent, uri: string) => boolean; } export interface IBrowser { From 5550bfd90b9b9a96f8344a0d3cd89400a228b61d Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Thu, 1 Feb 2018 21:37:06 +0000 Subject: [PATCH 66/86] Fix is click in selection --- src/SelectionManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index c01d7b6d5..a639a75e4 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -258,8 +258,8 @@ export class SelectionManager extends EventEmitter implements ISelectionManager } return (start[1] < coords[1] && end[1] > coords[1]) || - (start[1] === coords[1] && coords[0] > start[0]) || - (end[1] === coords[1] && coords[0] < end[0]); + (start[1] === end[1] && start[1] === coords[1] && coords[0] > start[0] && coords[0] < end[0]) || + (start[1] < end[1] && end[1] === coords[1] && coords[0] < end[0]); } /** From 3c6cab1491c84e99b0c9de92d20ea0272c99141a Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 1 Feb 2018 17:20:33 -0800 Subject: [PATCH 67/86] Clear selectionEnd when selecting word at cursor --- src/SelectionManager.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index a639a75e4..f571b74cd 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -270,6 +270,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager const coords = this._getMouseBufferCoords(event); if (coords) { this._selectWordAt(coords, false); + this._model.selectionEnd = null; this.refresh(true); } } From cab097c09c60dfc342eaac2b8d66a3235f2edd96 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Thu, 1 Feb 2018 17:27:49 -0800 Subject: [PATCH 68/86] Clean up condition for readability --- src/SelectionManager.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 33853036b..11856a922 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -266,9 +266,9 @@ export class SelectionManager extends EventEmitter implements ISelectionManager return false; } - return (start[1] < coords[1] && end[1] > coords[1]) || - (start[1] === end[1] && start[1] === coords[1] && coords[0] > start[0] && coords[0] < end[0]) || - (start[1] < end[1] && end[1] === coords[1] && coords[0] < end[0]); + 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]); } /** From c302bc9f1762140d28adc9359a4345cbea0f1367 Mon Sep 17 00:00:00 2001 From: Krasimir Tsonev Date: Mon, 5 Feb 2018 09:32:34 +0200 Subject: [PATCH 69/86] Update README.md In `Getting Started` section we read `npm install` only. Shouldn't be `npm install xterm`? --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a5d34632b..740ff0aad 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ It enables applications to provide fully featured terminals to their users and c First you need to install the module, we ship exclusively through [npm](https://www.npmjs.com/) so you need that installed and then add xterm.js as a dependency by running: ``` -npm install +npm install xterm ``` To start using xterm.js on your browser, add the `xterm.js` and `xterm.css` to the head of your html page. Then create a `
` onto which xterm can attach itself. From d0f45fee25d67cc0f703caf52d28dc33b2e583b6 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Mon, 5 Feb 2018 14:30:57 +0200 Subject: [PATCH 70/86] Fix #1232: Add `webpack:watch` gulp task --- Procfile | 1 + gulpfile.js | 4 ++++ package.json | 4 ++-- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/Procfile b/Procfile index 063b78f46..64562488d 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ web: npm start +webpack: npm run webpack:watch diff --git a/gulpfile.js b/gulpfile.js index 7a65d7f28..de64ee919 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -192,6 +192,10 @@ gulp.task('webpack', ['build'], function() { .pipe(gulp.dest('demo/dist/')); }); +gulp.task('webpack:watch', ['webpack'], () => { + gulp.watch('./src/*', ['webpack']); +}); + /** * Submit coverage results to coveralls.io */ diff --git a/package.json b/package.json index 3e14ff3f9..c16409dc3 100644 --- a/package.json +++ b/package.json @@ -75,7 +75,6 @@ "zmodem.js": "^0.1.5" }, "scripts": { - "prestart": "gulp webpack", "start": "node demo/app", "prestart-zmodem": "npm run build", "start-zmodem": "node build/addons/zmodem/demo/app", @@ -86,7 +85,8 @@ "build": "gulp build", "prepublish": "npm run build", "coveralls": "gulp coveralls", - "webpack": "gulp webpack" + "webpack": "gulp webpack", + "webpack:watch": "gulp webpack:watch" }, "dependencies": {} } From 182426dd0fa79fa212cdad95d05400f33e62ad91 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 6 Feb 2018 10:28:34 +0200 Subject: [PATCH 71/86] =?UTF-8?q?Rename=20gulp=20task:=20`webpack:watch`?= =?UTF-8?q?=20=E2=86=92=20watch`=20and=20also=20monitor=20subdirectories?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Procfile | 2 +- gulpfile.js | 4 ++-- package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Procfile b/Procfile index 64562488d..44bf043c3 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: npm start -webpack: npm run webpack:watch +webpack: npm run watch diff --git a/gulpfile.js b/gulpfile.js index de64ee919..4353e46d1 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -192,8 +192,8 @@ gulp.task('webpack', ['build'], function() { .pipe(gulp.dest('demo/dist/')); }); -gulp.task('webpack:watch', ['webpack'], () => { - gulp.watch('./src/*', ['webpack']); +gulp.task('watch', ['webpack'], () => { + gulp.watch(['./src/*', './src/**/*'], ['webpack']); }); /** diff --git a/package.json b/package.json index c16409dc3..1a718fbc2 100644 --- a/package.json +++ b/package.json @@ -86,7 +86,7 @@ "prepublish": "npm run build", "coveralls": "gulp coveralls", "webpack": "gulp webpack", - "webpack:watch": "gulp webpack:watch" + "watch": "gulp watch" }, "dependencies": {} } From f02903a6df9e380a9131ae95046a793f1578b954 Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Tue, 6 Feb 2018 11:16:57 +0200 Subject: [PATCH 72/86] Update README.md and Docker files --- Dockerfile | 2 +- README.md | 47 +++++++++++++++++++++++++++++++++++++++------- demo/index.html | 2 +- docker-compose.yml | 7 +++++++ 4 files changed, 49 insertions(+), 9 deletions(-) diff --git a/Dockerfile b/Dockerfile index 22556f431..1c72e6790 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:6 +FROM node:8 MAINTAINER Paris Kasidiaris # Set the working directory diff --git a/README.md b/README.md index 740ff0aad..cc1c89503 100644 --- a/README.md +++ b/README.md @@ -131,27 +131,60 @@ Do you use xterm.js in your application as well? Please [open a Pull Request](ht ## Demo -### Linux or macOS +Xterm.js ships with a barebones demo implementation, designed for the development and evaluation of the library only. Exposing the demo to the public as is would introduce security risks for the host. -First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run these commands: +Below you can find instructions on how to run the demo on different platforms. + +### SourceLair + +SourceLair will run the demo and builder in parallel automatically. Just make sure to choose the "Node.js" project type, when cloning the xterm.js repo (or just use this shortcut; https://lair.io/xtermjs/xtermjs). + +Then open your project's [Public URL](https://help.sourcelair.com/projects/the-public-url/) to access the demo. + +### Docker + +First, make sure you have Docker Engine 1.13.0 (or newer) and Docker Compose 1.10.0 (or newer). To run the demo and builder in parallel, run the following command in your terminal: + +``` +docker-compose up +``` + +Then open http://0.0.0.0:3000 in a web browser to access the demo. If you prefer a different port than `3000` to access the xterm.js demo, then set the `XTERMJS_PORT` environment variable to the desired port. + +### Foreman (or other Procfile runner) + +First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal: ``` npm install -npm start +foreman start # Replace foreman with "honcho", "forego" etc. depending on your runner ``` -Then open http://0.0.0.0:3000 in a web browser. +Then open http://0.0.0.0:3000 in a web browser to access the demo. + +### Linux or macOS + +First, be sure that a C++ compiler such as GCC-C++ or Clang is installed, then run the following commands in your terminal: + +``` +npm install +npm start # Run this in its own terminal +npm run watch # Run this in its own terminal +``` + +Then open http://0.0.0.0:3000 in a web browser to access the demo. ### Windows -First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run these commands. +First, ensure [node-gyp](https://github.com/nodejs/node-gyp) is installed and configured correctly, then run the following commands in your terminal: ``` npm install -npm start +npm start # Run this in its own terminal +npm run watch # Run this in its own terminal ``` -Then open http://127.0.0.1:3000 in a web browser. +Then open http://127.0.0.1:3000 in a web browser to access the demo. *Note: Do not use ConEmu, as it seems to break the demo for some reason.* diff --git a/demo/index.html b/demo/index.html index da348f5e6..b6d0ff61e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -67,7 +67,7 @@ -

Attention: The demo is a barebones implementation and is designed for xterm.js evaluation purposes only. Exposing the demo to the public as is would introduce security risks for the host.

+

Attention: The demo is a barebones implementation and is designed for the development and evaluation of xterm.js only. Exposing the demo to the public as is would introduce security risks for the host.

diff --git a/docker-compose.yml b/docker-compose.yml index 03e88fbc1..effd975c9 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,3 +7,10 @@ services: - ./:/usr/src/app ports: - ${XTERMJS_PORT:3000}:3000 + command: ["npm", "start"] + + watch: + build: . + volumes: + - ./:/usr/src/app + command: ["npm", "run", "watch"] From 35ea593806ab675ccf2b3c3cbbb5c8788f3f760d Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 6 Feb 2018 02:03:09 -0800 Subject: [PATCH 73/86] Update readme copyright statement --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 740ff0aad..4501aad46 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,6 @@ To contribute either code, documentation or issues to xterm.js please read the [ If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that all code is your original work. +Copyright (c) 2017-2018, [The xterm.js authors](https://github.com/xtermjs/xterm.js/graphs/contributors) (MIT License) Copyright (c) 2014-2017, SourceLair, Private Company ([www.sourcelair.com](https://www.sourcelair.com/home)) (MIT License) - Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) From dc759a93bc7753fcd733a06fbb3a67220120876b Mon Sep 17 00:00:00 2001 From: Thomas Zilz Date: Tue, 6 Feb 2018 12:31:17 +0100 Subject: [PATCH 74/86] Support setting padding on the .xterm element (#1208) * Support padding on the .xterm element --- demo/index.html | 4 ++++ demo/main.js | 17 +++++++++++------ src/SelectionManager.ts | 4 ++-- src/Terminal.ts | 18 +++++++++++------- src/Types.ts | 3 +++ src/Viewport.ts | 16 +++++++++------- src/addons/fit/fit.ts | 15 ++++++++++----- src/input/MouseZoneManager.ts | 2 +- src/renderer/Renderer.ts | 12 ++++++++---- src/utils/TestUtils.test.ts | 3 +++ src/xterm.css | 11 ++++++++++- 11 files changed, 72 insertions(+), 33 deletions(-) diff --git a/demo/index.html b/demo/index.html index b6d0ff61e..a62cff0e3 100644 --- a/demo/index.html +++ b/demo/index.html @@ -64,6 +64,10 @@ +
+ + +
diff --git a/demo/main.js b/demo/main.js index 1faf77d8a..264a205d3 100644 --- a/demo/main.js +++ b/demo/main.js @@ -33,23 +33,27 @@ var terminalContainer = document.getElementById('terminal-container'), bellStyle: document.querySelector('#option-bell-style') }, colsElement = document.getElementById('cols'), - rowsElement = document.getElementById('rows'); + rowsElement = document.getElementById('rows'), + paddingElement = document.getElementById('padding'); function setTerminalSize() { var cols = parseInt(colsElement.value, 10); var rows = parseInt(rowsElement.value, 10); - var viewportElement = document.querySelector('.xterm-viewport'); - var scrollBarWidth = viewportElement.offsetWidth - viewportElement.clientWidth; - var width = (cols * term.renderer.dimensions.actualCellWidth + 20 /*room for scrollbar*/).toString() + 'px'; + var width = (cols * term.renderer.dimensions.actualCellWidth + term.viewport.scrollBarWidth).toString() + 'px'; var height = (rows * term.renderer.dimensions.actualCellHeight).toString() + 'px'; - terminalContainer.style.width = width; terminalContainer.style.height = height; - term.resize(cols, rows); + term.fit(); +} + +function setPadding() { + term.element.style.padding = parseInt(paddingElement.value, 10).toString() + 'px'; + term.fit(); } colsElement.addEventListener('change', setTerminalSize); rowsElement.addEventListener('change', setTerminalSize); +paddingElement.addEventListener('change', setPadding); actionElements.findNext.addEventListener('keypress', function (e) { if (e.key === "Enter") { @@ -119,6 +123,7 @@ function createTerminal() { setTimeout(function () { colsElement.value = term.cols; rowsElement.value = term.rows; + paddingElement.value = 0; // Set terminal size again to set the specific dimensions on the demo setTerminalSize(); diff --git a/src/SelectionManager.ts b/src/SelectionManager.ts index 11856a922..dc1d0682a 100644 --- a/src/SelectionManager.ts +++ b/src/SelectionManager.ts @@ -309,7 +309,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseBufferCoords(event: MouseEvent): [number, number] { - const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.element, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); + const coords = this._terminal.mouseHelper.getCoords(event, this._terminal.screenElement, this._charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows, true); if (!coords) { return null; } @@ -329,7 +329,7 @@ export class SelectionManager extends EventEmitter implements ISelectionManager * @param event The mouse event. */ private _getMouseEventScrollAmount(event: MouseEvent): number { - let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.element)[1]; + let offset = MouseHelper.getCoordsRelativeToElement(event, this._terminal.screenElement)[1]; const terminalHeight = this._terminal.rows * Math.ceil(this._charMeasure.height * this._terminal.options.lineHeight); if (offset >= 0 && offset <= terminalHeight) { return 0; diff --git a/src/Terminal.ts b/src/Terminal.ts index c55dd924d..1bc9cfbb0 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -96,6 +96,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { export class Terminal extends EventEmitter implements ITerminal, IInputHandlingTerminal { public textarea: HTMLTextAreaElement; public element: HTMLElement; + public screenElement: HTMLElement; /** * The HTMLElement that the terminal is created in, set by Terminal.open. @@ -609,15 +610,18 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.viewportScrollArea.classList.add('xterm-scroll-area'); this.viewportElement.appendChild(this.viewportScrollArea); - this._mouseZoneManager = new MouseZoneManager(this); - this.on('scroll', () => this._mouseZoneManager.clearAll()); - this.linkifier.attachToDom(this._mouseZoneManager); - + this.screenElement = document.createElement('div'); + this.screenElement.classList.add('xterm-screen'); // Create the container that will hold helpers like the textarea for // capturing DOM Events. Then produce the helpers. this.helperContainer = document.createElement('div'); this.helperContainer.classList.add('xterm-helpers'); - fragment.appendChild(this.helperContainer); + this.screenElement.appendChild(this.helperContainer); + fragment.appendChild(this.screenElement); + + this._mouseZoneManager = new MouseZoneManager(this); + this.on('scroll', () => this._mouseZoneManager.clearAll()); + this.linkifier.attachToDom(this._mouseZoneManager); this.textarea = document.createElement('textarea'); this.textarea.classList.add('xterm-helper-textarea'); @@ -735,7 +739,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT button = getButton(ev); // get mouse coordinates - pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; sendEvent(button, pos); @@ -761,7 +765,7 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT // ^[[M 3<^[[M@4<^[[M@5<^[[M@6<^[[M@7<^[[M#7< function sendMove(ev: MouseEvent): void { let button = pressed; - let pos = self.mouseHelper.getRawByteCoords(ev, self.element, self.charMeasure, self.options.lineHeight, self.cols, self.rows); + let pos = self.mouseHelper.getRawByteCoords(ev, self.screenElement, self.charMeasure, self.options.lineHeight, self.cols, self.rows); if (!pos) return; // buttons marked as motions diff --git a/src/Types.ts b/src/Types.ts index 599299e3d..3ea9fce69 100644 --- a/src/Types.ts +++ b/src/Types.ts @@ -85,6 +85,7 @@ export interface IInputHandlingTerminal extends IEventEmitter { } export interface IViewport { + scrollBarWidth: number; syncScrollArea(): void; onWheel(ev: WheelEvent): void; onTouchStart(ev: TouchEvent): void; @@ -175,6 +176,7 @@ export interface ILinkHoverEvent { } export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAccessor, ILinkifierAccessor { + screenElement: HTMLElement; selectionManager: ISelectionManager; charMeasure: ICharMeasure; renderer: IRenderer; @@ -188,6 +190,7 @@ export interface ITerminal extends PublicTerminal, IElementAccessor, IBufferAcce buffers: IBufferSet; isFocused: boolean; mouseHelper: IMouseHelper; + viewport: IViewport; bracketedPasteMode: boolean; applicationCursor: boolean; diff --git a/src/Viewport.ts b/src/Viewport.ts index 59dc304d4..35a2aa689 100644 --- a/src/Viewport.ts +++ b/src/Viewport.ts @@ -7,11 +7,14 @@ import { IColorSet } from './renderer/Types'; import { ITerminal, IViewport } from './Types'; import { CharMeasure } from './utils/CharMeasure'; +const FALLBACK_SCROLL_BAR_WIDTH = 15; + /** * Represents the viewport of a terminal, the visible area within the larger buffer of output. * Logic for the virtual scroll bar is included in this object. */ export class Viewport implements IViewport { + public scrollBarWidth: number = 0; private currentRowHeight: number = 0; private lastRecordedBufferLength: number = 0; private lastRecordedViewportHeight: number = 0; @@ -31,6 +34,10 @@ export class Viewport implements IViewport { private scrollArea: HTMLElement, private charMeasure: CharMeasure ) { + // Measure the width of the scrollbar. If it is 0 we can assume it's an OSX overlay scrollbar. + // Unfortunately the overlay scrollbar would be hidden underneath the screen element in that case, + // therefore we account for a standard amount to make it visible + this.scrollBarWidth = (this.viewportElement.offsetWidth - this.scrollArea.offsetWidth) || FALLBACK_SCROLL_BAR_WIDTH; this.viewportElement.addEventListener('scroll', this.onScroll.bind(this)); // Perform this async to ensure the CharMeasure is ready. @@ -48,13 +55,8 @@ export class Viewport implements IViewport { private refresh(): void { if (this.charMeasure.height > 0) { this.currentRowHeight = this.terminal.renderer.dimensions.scaledCellHeight / window.devicePixelRatio; - - if (this.lastRecordedViewportHeight !== this.terminal.renderer.dimensions.canvasHeight) { - this.lastRecordedViewportHeight = this.terminal.renderer.dimensions.canvasHeight; - this.viewportElement.style.height = this.lastRecordedViewportHeight + 'px'; - } - - const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength); + this.lastRecordedViewportHeight = this.viewportElement.offsetHeight; + const newBufferHeight = Math.round(this.currentRowHeight * this.lastRecordedBufferLength) + (this.lastRecordedViewportHeight - this.terminal.renderer.dimensions.canvasHeight); if (this.lastRecordedBufferHeight !== newBufferHeight) { this.lastRecordedBufferHeight = newBufferHeight; this.scrollArea.style.height = this.lastRecordedBufferHeight + 'px'; diff --git a/src/addons/fit/fit.ts b/src/addons/fit/fit.ts index 75e854d97..f6f593dcf 100644 --- a/src/addons/fit/fit.ts +++ b/src/addons/fit/fit.ts @@ -28,17 +28,22 @@ export function proposeGeometry(term: Terminal): IGeometry { } const parentElementStyle = window.getComputedStyle(term.element.parentElement); const parentElementHeight = parseInt(parentElementStyle.getPropertyValue('height')); - const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width')) - 17); + const parentElementWidth = Math.max(0, parseInt(parentElementStyle.getPropertyValue('width'))); const elementStyle = window.getComputedStyle(term.element); - const elementPaddingVer = parseInt(elementStyle.getPropertyValue('padding-top')) + parseInt(elementStyle.getPropertyValue('padding-bottom')); - const elementPaddingHor = parseInt(elementStyle.getPropertyValue('padding-right')) + parseInt(elementStyle.getPropertyValue('padding-left')); + const elementPadding = { + top: parseInt(elementStyle.getPropertyValue('padding-top')), + bottom: parseInt(elementStyle.getPropertyValue('padding-bottom')), + right: parseInt(elementStyle.getPropertyValue('padding-right')), + left: parseInt(elementStyle.getPropertyValue('padding-left')) + }; + const elementPaddingVer = elementPadding.top + elementPadding.bottom; + const elementPaddingHor = elementPadding.right + elementPadding.left; const availableHeight = parentElementHeight - elementPaddingVer; - const availableWidth = parentElementWidth - elementPaddingHor; + const availableWidth = parentElementWidth - elementPaddingHor - (term).viewport.scrollBarWidth; const geometry = { cols: Math.floor(availableWidth / (term).renderer.dimensions.actualCellWidth), rows: Math.floor(availableHeight / (term).renderer.dimensions.actualCellHeight) }; - return geometry; } diff --git a/src/input/MouseZoneManager.ts b/src/input/MouseZoneManager.ts index 79c02b5f2..3ab86e7c1 100644 --- a/src/input/MouseZoneManager.ts +++ b/src/input/MouseZoneManager.ts @@ -169,7 +169,7 @@ export class MouseZoneManager implements IMouseZoneManager { } private _findZoneEventAt(e: MouseEvent): IMouseZone { - const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.element, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); + const coords = this._terminal.mouseHelper.getCoords(e, this._terminal.screenElement, this._terminal.charMeasure, this._terminal.options.lineHeight, this._terminal.cols, this._terminal.rows); if (!coords) { return null; } diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 11c247332..3792a9dab 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -38,10 +38,10 @@ export class Renderer extends EventEmitter implements IRenderer { } this._renderLayers = [ - new TextRenderLayer(this._terminal.element, 0, this.colorManager.colors, this._terminal.options.allowTransparency), - new SelectionRenderLayer(this._terminal.element, 1, this.colorManager.colors), - new LinkRenderLayer(this._terminal.element, 2, this.colorManager.colors, this._terminal), - new CursorRenderLayer(this._terminal.element, 3, this.colorManager.colors) + new TextRenderLayer(this._terminal.screenElement, 0, this.colorManager.colors, this._terminal.options.allowTransparency), + new SelectionRenderLayer(this._terminal.screenElement, 1, this.colorManager.colors), + new LinkRenderLayer(this._terminal.screenElement, 2, this.colorManager.colors, this._terminal), + new CursorRenderLayer(this._terminal.screenElement, 3, this.colorManager.colors) ]; this.dimensions = { scaledCharWidth: null, @@ -120,6 +120,10 @@ export class Renderer extends EventEmitter implements IRenderer { this._terminal.refresh(0, this._terminal.rows - 1); } + // Resize the screen + this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth + this._terminal.viewport.scrollBarWidth}px`; + this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; + this.emit('resize', { width: this.dimensions.canvasWidth, height: this.dimensions.canvasHeight diff --git a/src/utils/TestUtils.test.ts b/src/utils/TestUtils.test.ts index d53c8c58b..d48ad12ca 100644 --- a/src/utils/TestUtils.test.ts +++ b/src/utils/TestUtils.test.ts @@ -80,6 +80,7 @@ export class MockTerminal implements ITerminal { isFocused: boolean; options: ITerminalOptions = {}; element: HTMLElement; + screenElement: HTMLElement; rowContainer: HTMLElement; selectionContainer: HTMLElement; selectionManager: ISelectionManager; @@ -96,6 +97,7 @@ export class MockTerminal implements ITerminal { scrollback: number; buffers: IBufferSet; buffer: IBuffer; + viewport: IViewport; applicationCursor: boolean; handler(data: string): void { throw new Error('Method not implemented.'); @@ -308,6 +310,7 @@ export class MockRenderer implements IRenderer { } export class MockViewport implements IViewport { + scrollBarWidth: number = 0; onThemeChanged(colors: IColorSet): void { throw new Error('Method not implemented.'); } diff --git a/src/xterm.css b/src/xterm.css index b3ee50f0f..3edaf3ff5 100644 --- a/src/xterm.css +++ b/src/xterm.css @@ -96,9 +96,18 @@ background-color: #000; overflow-y: scroll; cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; } -.xterm canvas { +.xterm .xterm-screen { + position: relative; +} + +.xterm .xterm-screen canvas { position: absolute; left: 0; top: 0; From 760837e5cb46e9aab6dd9d25c9c72104c8dbc559 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Tue, 6 Feb 2018 07:25:54 -0800 Subject: [PATCH 75/86] Mark enableBold setting as deprecated --- typings/xterm.d.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 1d14243ef..cd59b81dd 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -55,6 +55,8 @@ declare module 'xterm' { /** * Whether to enable the rendering of bold text. + * + * @deprecated Use fontWeight and fontWeightBold instead. */ enableBold?: boolean; From 1dbe7b3a8cb17014e7f48127228c8329df84d10b Mon Sep 17 00:00:00 2001 From: Paris Kasidiaris Date: Fri, 9 Feb 2018 08:42:39 +0000 Subject: [PATCH 76/86] Bump version to 3.1.0 Signed-off-by: Paris Kasidiaris --- AUTHORS | 13 +++++++++++++ package.json | 2 +- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index 1bd4c87d3..47328bc95 100644 --- a/AUTHORS +++ b/AUTHORS @@ -1,12 +1,15 @@ List of xterm.js contributors. Updated before every release. +Adrian Labbé Aleksandr Andrienko Aleksandr Andriienko Alessandro Nadalin Alexander Olsson Alexey Kontsevoy +Andres Mejia Anish Athalye Anthony Lapenna +Antonin Stefanutti Antonis Kalipetis Anton Skshidlevsky Anton Yurovskykh @@ -19,6 +22,7 @@ Bill Church Bob Reid bottleofwater Brian Mock +Bruno Ribeiro Bruno Ribeito Carson Anderson CHaBou @@ -26,6 +30,7 @@ Christian Budde Christensen Christof Marti Christopher Jeffrey coderaiser +Damien Tournoud Dan Brown Daniel Griffen Daniel Imms @@ -46,20 +51,25 @@ imoses InDieTasten irokas Jakob Gillich +Jan Kuri Jean Bruenn Jeremy Danyow +Jianhui Zhao Joao Moreno Joao Moreno Johannes Zellner Jon Masters Jörg Breitbart +Justin Luk Justin Mecham Kirill Merkushev +Krasimir Tsonev Luca Lucian Buzzo Lukas Drgon Maël Nison Marc Dumais +Markus F.X.J. Oberhumer Martin Chloride Martin Koppehel Martin Wang @@ -75,10 +85,12 @@ Paris Kasidiaris Peter Baumgarten Rick Baker runarberg +Saad Malik Samuel Williams Saswat Das Saul Costa Shuanglei Tao +sitzmar Steven Silvester stuicey t-amqi @@ -90,3 +102,4 @@ Tyler Jewell Vincent Woo yutaka YuviPanda +ZHAO Xudong diff --git a/package.json b/package.json index 1a718fbc2..079333de1 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "xterm", "description": "Full xterm terminal, in your browser", - "version": "3.1.0-master", + "version": "3.1.0", "ignore": [ "demo", "test", From b5ba9ae771bbe901431826fb8e0b8663add12433 Mon Sep 17 00:00:00 2001 From: Oleksandr Andriienko Date: Mon, 12 Feb 2018 12:30:26 +0200 Subject: [PATCH 77/86] Fix xterm.js in headless mode yields TypeError on resize Signed-off-by: Oleksandr Andriienko --- src/Terminal.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/Terminal.ts b/src/Terminal.ts index 1bc9cfbb0..9f8bc4337 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -1889,7 +1889,9 @@ export class Terminal extends EventEmitter implements ITerminal, IInputHandlingT this.rows = y; this.buffers.setupTabStops(this.cols); - this.charMeasure.measure(this.options); + if (this.charMeasure) { + this.charMeasure.measure(this.options); + } this.refresh(0, this.rows - 1); this.emit('resize', {cols: x, rows: y}); From 64ed8309a1cfecc87561351b406e0aad04e2170c Mon Sep 17 00:00:00 2001 From: Matthew James Date: Mon, 12 Feb 2018 20:26:09 +0000 Subject: [PATCH 78/86] Added FreeMAN to list of real-world uses --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index f1bb422a5..cdaf0d71a 100644 --- a/README.md +++ b/README.md @@ -110,7 +110,7 @@ Xterm.js is used in several world-class applications to provide great terminal e - [**Selenoid UI**](https://github.com/aerokube/selenoid-ui): Simple UI for the scallable golang implementation of Selenium Hub named Selenoid. We use XTerm for streaming logs over websockets from docker containers. - [**Portainer**](https://portainer.io): Simple management UI for Docker. - [**SSHy**](https://github.com/stuicey/SSHy): HTML5 Based SSHv2 Web Client with E2E encryption utilising `xterm.js`, SJCL & websockets. -- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible +- [**JupyterLab**](https://github.com/jupyterlab/jupyterlab): An extensible computational environment for Jupyter, supporting interactive data science and scientific computing across all programming languages. - [**Theia**](https://github.com/theia-ide/theia): Theia is a cloud & desktop IDE framework implemented in TypeScript. - [**Opshell**](https://github.com/ricktbaker/opshell) Ops Helper tool to make life easier working with AWS instances across multiple organizations. @@ -126,6 +126,7 @@ computational environment for Jupyter, supporting interactive data science and s - [**Pisth**](https://github.com/ColdGrub1384/Pisth): An SFTP and SSH client for iOS - [**abstruse**](https://github.com/bleenco/abstruse): Abstruse CI is a continuous integration platform based on Node.JS and Docker. - [**Microsoft SQL Operations Studio**](https://github.com/Microsoft/sqlopsstudio): A data management tool that enables working with SQL Server, Azure SQL DB and SQL DW from Windows, macOS and Linux +- [**FreeMAN**](https://github.com/matthew-matvei/freeman): A free, cross-platform file manager for power users Do you use xterm.js in your application as well? Please [open a Pull Request](https://github.com/sourcelair/xterm.js/pulls) to include it here. We would love to have it in our list. From 183fd2fe79273ffbbf58fba534f0d05946ac7331 Mon Sep 17 00:00:00 2001 From: Daniel Griffen Date: Fri, 16 Feb 2018 10:17:48 -0800 Subject: [PATCH 79/86] fix terminal width calculation --- src/renderer/Renderer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/Renderer.ts b/src/renderer/Renderer.ts index 2408f8088..fa1e34e62 100644 --- a/src/renderer/Renderer.ts +++ b/src/renderer/Renderer.ts @@ -121,7 +121,7 @@ export class Renderer extends EventEmitter implements IRenderer { } // Resize the screen - this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth + this._terminal.viewport.scrollBarWidth}px`; + this._terminal.screenElement.style.width = `${this.dimensions.canvasWidth}px`; this._terminal.screenElement.style.height = `${this.dimensions.canvasHeight}px`; this.emit('resize', { From db8ba17147c1ae3bd0bb7438f5af629f94502c29 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 21 Feb 2018 14:39:43 -0800 Subject: [PATCH 80/86] Check audio context ctor before constructing --- src/SoundManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index dc61acbdd..dbd4f02d4 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -21,8 +21,9 @@ export class SoundManager implements ISoundManager { } public playBellSound(): void { - if (!this._audioContext) { - this._audioContext = new (window.AudioContext || window.webkitAudioContext)(); + const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; + if (!this._audioContext && audioContextCtor) { + this._audioContext = new audioContextCtor(); } if (this._audioContext) { @@ -33,8 +34,7 @@ export class SoundManager implements ISoundManager { bellAudioSource.connect(context.destination); bellAudioSource.start(0); }); - } - else { + } else { console.warn('Sorry, but the Web Audio API is not supported by your browser. Please, consider upgrading to the latest version'); } } From 45c9763092b118c799057dd32ea355e751376965 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 21 Feb 2018 14:40:22 -0800 Subject: [PATCH 81/86] Fix indentation --- src/SoundManager.ts | 73 ++++++++++++++++++++++----------------------- 1 file changed, 36 insertions(+), 37 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index dbd4f02d4..d86e5553d 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -12,50 +12,49 @@ import { ITerminal, ISoundManager } from './Types'; export const DefaultBellSound = '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 _terminal: ITerminal; + private _audioContext: AudioContext; - private _terminal: ITerminal; - private _audioContext: AudioContext; + constructor(_terminal: ITerminal) { + this._terminal = _terminal; + } - constructor(_terminal: ITerminal) { - this._terminal = _terminal; + public playBellSound(): void { + const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; + if (!this._audioContext && audioContextCtor) { + this._audioContext = new audioContextCtor(); } - public playBellSound(): void { - const audioContextCtor: typeof AudioContext = (window).AudioContext || (window).webkitAudioContext; - if (!this._audioContext && audioContextCtor) { - this._audioContext = new audioContextCtor(); - } + if (this._audioContext) { + let bellAudioSource = this._audioContext.createBufferSource(); + let context = this._audioContext; + this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), function (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'); + } + } - if (this._audioContext) { - let bellAudioSource = this._audioContext.createBufferSource(); - let context = this._audioContext; - this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), function (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'); - } + private base64ToArrayBuffer(base64: string): ArrayBuffer { + const binaryString = window.atob(base64); + const len = binaryString.length; + let bytes = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + bytes[i] = binaryString.charCodeAt(i); } - private base64ToArrayBuffer(base64: string): ArrayBuffer { - const binaryString = window.atob(base64); - const len = binaryString.length; - let bytes = new Uint8Array(len); + return bytes.buffer; + } - for (let i = 0; i < len; i++) { - bytes[i] = binaryString.charCodeAt(i); - } + private removeMimeType(dataURI: string): string { + // Split the input to get the mime-type and the data itself + const SplitURI = dataURI.split(','); - return bytes.buffer; - } - - private removeMimeType(dataURI: string): string { - // Split the input to get the mime-type and the data itself - const SplitURI = dataURI.split(','); - - // Return only the data - return SplitURI[1]; - } + // Return only the data + return SplitURI[1]; + } } From af5b3f704e8c2509fda5fad1856117eb82430ce9 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 21 Feb 2018 14:42:04 -0800 Subject: [PATCH 82/86] Fix lint --- src/SoundManager.ts | 8 ++++---- src/Terminal.ts | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index d86e5553d..89f3d6dbd 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -9,7 +9,7 @@ import { ITerminal, ISoundManager } from './Types'; // This sound is released under the Creative Commons Attribution 3.0 Unported // (CC BY 3.0) license. It was created by 'altemark'. No modifications have been // made, apart from the conversion to base64. -export const DefaultBellSound = '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 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 _terminal: ITerminal; @@ -28,7 +28,7 @@ export class SoundManager implements ISoundManager { if (this._audioContext) { let bellAudioSource = this._audioContext.createBufferSource(); let context = this._audioContext; - this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), function (buffer) { + this._audioContext.decodeAudioData(this.base64ToArrayBuffer(this.removeMimeType(this._terminal.options.bellSound)), (buffer) => { bellAudioSource.buffer = buffer; bellAudioSource.connect(context.destination); bellAudioSource.start(0); @@ -52,9 +52,9 @@ export class SoundManager implements ISoundManager { private removeMimeType(dataURI: string): string { // Split the input to get the mime-type and the data itself - const SplitURI = dataURI.split(','); + const splitUri = dataURI.split(','); // Return only the data - return SplitURI[1]; + return splitUri[1]; } } diff --git a/src/Terminal.ts b/src/Terminal.ts index 9041fb8df..721996c96 100644 --- a/src/Terminal.ts +++ b/src/Terminal.ts @@ -42,7 +42,7 @@ import * as Browser from './shared/utils/Browser'; import * as Strings from './Strings'; import { MouseHelper } from './utils/MouseHelper'; import { CHARSETS } from './Charsets'; -import { DefaultBellSound, SoundManager } from './SoundManager'; +import { DEFAULT_BELL_SOUND, SoundManager } from './SoundManager'; import { DEFAULT_ANSI_COLORS } from './renderer/ColorManager'; import { MouseZoneManager } from './input/MouseZoneManager'; import { AccessibilityManager } from './AccessibilityManager'; @@ -100,7 +100,7 @@ const DEFAULT_OPTIONS: ITerminalOptions = { termName: 'xterm', cursorBlink: false, cursorStyle: 'block', - bellSound: DefaultBellSound, + bellSound: DEFAULT_BELL_SOUND, bellStyle: 'none', enableBold: true, fontFamily: 'courier-new, courier, monospace', From a91e0e234a487658c019fe25e4822474fd33db7f Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 21 Feb 2018 14:44:48 -0800 Subject: [PATCH 83/86] Use const everywhere --- src/SoundManager.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/SoundManager.ts b/src/SoundManager.ts index 89f3d6dbd..1c50cbbc6 100644 --- a/src/SoundManager.ts +++ b/src/SoundManager.ts @@ -12,11 +12,11 @@ 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 _terminal: ITerminal; private _audioContext: AudioContext; - constructor(_terminal: ITerminal) { - this._terminal = _terminal; + constructor( + private _terminal: ITerminal + ) { } public playBellSound(): void { @@ -26,8 +26,8 @@ export class SoundManager implements ISoundManager { } if (this._audioContext) { - let bellAudioSource = this._audioContext.createBufferSource(); - let context = 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); @@ -41,7 +41,7 @@ export class SoundManager implements ISoundManager { private base64ToArrayBuffer(base64: string): ArrayBuffer { const binaryString = window.atob(base64); const len = binaryString.length; - let bytes = new Uint8Array(len); + const bytes = new Uint8Array(len); for (let i = 0; i < len; i++) { bytes[i] = binaryString.charCodeAt(i); From ad7d2262ecbcd827cb4693edc2f16351b89917c6 Mon Sep 17 00:00:00 2001 From: Daniel Imms Date: Wed, 21 Feb 2018 15:35:53 -0800 Subject: [PATCH 84/86] Undo package-lock changes --- package-lock.json | 1850 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 1831 insertions(+), 19 deletions(-) diff --git a/package-lock.json b/package-lock.json index e24785925..03bbd1155 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "xterm", - "version": "3.1.0", + "version": "3.1.0-master", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -149,7 +149,7 @@ "anymatch": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-1.3.2.tgz", - "integrity": "sha1-VT3Lj5HjyImEXf26NMd3IbkLnXo=", + "integrity": "sha512-0XNayC8lTHQ2OI8aljNCN3sSx6hsr/1+rlcDAotXJR7C1oZZHCNsfpbKwMjRA3Uqb5tF1Rae2oloTr4xpq+WjA==", "dev": true, "requires": { "micromatch": "2.3.11", @@ -177,7 +177,7 @@ "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha1-NgSLv/TntH4TZkQxbJlmnqWukfE=", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true }, "array-differ": { @@ -385,7 +385,7 @@ "base64-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.2.1.tgz", - "integrity": "sha1-qRlH2h9KUW6jjltOwOw3c2deCIY=", + "integrity": "sha512-dwVUVIXsBZXwTuwnXI9RK8sBmgq09NDHzyR9SAph9eqk76gKK2JSQmZARC2zRC81JC2QTtxD0ARU5qTS25gIGw==", "dev": true }, "bcrypt-pbkdf": { @@ -791,6 +791,7 @@ "requires": { "anymatch": "1.3.2", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", @@ -802,7 +803,7 @@ "cipher-base": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.4.tgz", - "integrity": "sha1-h2Dk7MJy9MNjUy+SbYdKriwTl94=", + "integrity": "sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q==", "dev": true, "requires": { "inherits": "2.0.3", @@ -864,7 +865,7 @@ "color-convert": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha1-wSYRB66y8pTr/+ye2eytUppgl+0=", + "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "dev": true, "requires": { "color-name": "1.1.3" @@ -899,7 +900,7 @@ "commander": { "version": "2.11.0", "resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz", - "integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM=", + "integrity": "sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ==", "dev": true }, "concat-map": { @@ -1502,7 +1503,7 @@ "exit-on-epipe": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/exit-on-epipe/-/exit-on-epipe-1.0.1.tgz", - "integrity": "sha1-C92S6H1ShdJn2qgXHQ6wYVlolpI=", + "integrity": "sha512-h2z5mrROTxce56S+pnvAV890uu7ls7f1kEvVGJbw1OlFH3/mlJ5bkXu0KRyW94v37zzHPiUd55iLn3DA7TjWpw==", "dev": true }, "expand-brackets": { @@ -2095,6 +2096,910 @@ } } }, + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + }, "function-bind": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.0.tgz", @@ -2144,7 +3049,7 @@ "glob": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha1-wZyd+aAocC1nhhI4SmVSQExjbRU=", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { "fs.realpath": "1.0.0", @@ -5777,7 +6682,7 @@ "gulp-istanbul": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/gulp-istanbul/-/gulp-istanbul-1.1.2.tgz", - "integrity": "sha1-r2X6KL/bNXbaq5Xc+qcypqJ8Wgc=", + "integrity": "sha512-53+BDhGlGNHYfeFh/mSXWhNu9wSFmE8qAEFj6ViMiWzTwI9pYxedUxMmGfigwaddsHHQxBl9TgnzUydrX84Kog==", "dev": true, "requires": { "gulp-util": "3.0.8", @@ -9045,7 +9950,7 @@ "hash.js": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.3.tgz", - "integrity": "sha1-NA3tvmKQGHFRweodd3o0SJNd+EY=", + "integrity": "sha512-/UETyP0W22QILqS+6HowevwhEFJ3MBJnwTf75Qob9Wz9t0DPuisL8kW8YZMK62dHAKE1c1p+gY1TtOLY+USEHA==", "dev": true, "requires": { "inherits": "2.0.3", @@ -10147,7 +11052,7 @@ "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha1-UWbihkV/AzBgZL5Ul+jbsMPTIIM=", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { "brace-expansion": "1.1.8" @@ -10775,12 +11680,919 @@ "requires": { "anymatch": "1.3.0", "async-each": "1.0.1", + "fsevents": "1.1.3", "glob-parent": "2.0.0", "inherits": "2.0.3", "is-binary-path": "1.0.1", "is-glob": "2.0.1", "path-is-absolute": "1.0.1", "readdirp": "2.1.0" + }, + "dependencies": { + "fsevents": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", + "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", + "dev": true, + "optional": true, + "requires": { + "nan": "2.8.0", + "node-pre-gyp": "0.6.39" + }, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "ajv": { + "version": "4.11.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "co": "4.6.0", + "json-stable-stringify": "1.0.1" + } + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "delegates": "1.0.0", + "readable-stream": "2.2.9" + } + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true, + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "tweetnacl": "0.14.5" + } + }, + "block-stream": { + "version": "0.0.9", + "bundled": true, + "dev": true, + "requires": { + "inherits": "2.0.3" + } + }, + "boom": { + "version": "2.10.1", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "brace-expansion": { + "version": "1.1.7", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "0.4.2", + "concat-map": "0.0.1" + } + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "caseless": { + "version": "0.12.0", + "bundled": true, + "dev": true, + "optional": true + }, + "co": { + "version": "4.6.0", + "bundled": true, + "dev": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "requires": { + "delayed-stream": "1.0.0" + } + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1" + } + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "debug": { + "version": "2.6.8", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "deep-extend": { + "version": "0.4.2", + "bundled": true, + "dev": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "detect-libc": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "extend": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "optional": true + }, + "form-data": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asynckit": "0.4.0", + "combined-stream": "1.0.5", + "mime-types": "2.1.15" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "fstream": { + "version": "1.0.11", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "4.1.11", + "inherits": "2.0.3", + "mkdirp": "0.5.1", + "rimraf": "2.6.1" + } + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "fstream": "1.0.11", + "inherits": "2.0.3", + "minimatch": "3.0.4" + } + }, + "gauge": { + "version": "2.7.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aproba": "1.1.1", + "console-control-strings": "1.1.0", + "has-unicode": "2.0.1", + "object-assign": "4.1.1", + "signal-exit": "3.0.2", + "string-width": "1.0.2", + "strip-ansi": "3.0.1", + "wide-align": "1.1.2" + } + }, + "getpass": { + "version": "0.1.7", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" + } + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "har-schema": { + "version": "1.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "har-validator": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "ajv": "4.11.8", + "har-schema": "1.0.5" + } + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "dev": true, + "requires": { + "boom": "2.10.1", + "cryptiles": "2.0.5", + "hoek": "2.16.3", + "sntp": "1.0.9" + } + }, + "hoek": { + "version": "2.16.3", + "bundled": true, + "dev": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "0.2.0", + "jsprim": "1.4.0", + "sshpk": "1.13.0" + } + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "1.4.0", + "wrappy": "1.0.2" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "1.0.1" + } + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsbn": "0.1.1" + } + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "dev": true, + "optional": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "jsonify": "0.0.0" + } + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "jsonify": { + "version": "0.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "jsprim": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.0.2", + "json-schema": "0.2.3", + "verror": "1.3.6" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "mime-db": { + "version": "1.27.0", + "bundled": true, + "dev": true + }, + "mime-types": { + "version": "2.1.15", + "bundled": true, + "dev": true, + "requires": { + "mime-db": "1.27.0" + } + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.39", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "detect-libc": "1.0.2", + "hawk": "3.1.3", + "mkdirp": "0.5.1", + "nopt": "4.0.1", + "npmlog": "4.1.0", + "rc": "1.2.1", + "request": "2.81.0", + "rimraf": "2.6.1", + "semver": "5.3.0", + "tar": "2.2.1", + "tar-pack": "3.4.0" + } + }, + "nopt": { + "version": "4.0.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "abbrev": "1.1.0", + "osenv": "0.1.4" + } + }, + "npmlog": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "are-we-there-yet": "1.1.4", + "console-control-strings": "1.1.0", + "gauge": "2.7.4", + "set-blocking": "2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "os-tmpdir": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "osenv": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "os-homedir": "1.0.2", + "os-tmpdir": "1.0.2" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "performance-now": { + "version": "0.2.0", + "bundled": true, + "dev": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true, + "dev": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "dev": true, + "optional": true + }, + "qs": { + "version": "6.4.0", + "bundled": true, + "dev": true, + "optional": true + }, + "rc": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "deep-extend": "0.4.2", + "ini": "1.3.4", + "minimist": "1.2.0", + "strip-json-comments": "2.0.1" + }, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.9", + "bundled": true, + "dev": true, + "requires": { + "buffer-shims": "1.0.0", + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "1.0.7", + "string_decoder": "1.0.1", + "util-deprecate": "1.0.2" + } + }, + "request": { + "version": "2.81.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "aws-sign2": "0.6.0", + "aws4": "1.6.0", + "caseless": "0.12.0", + "combined-stream": "1.0.5", + "extend": "3.0.1", + "forever-agent": "0.6.1", + "form-data": "2.1.4", + "har-validator": "4.2.1", + "hawk": "3.1.3", + "http-signature": "1.1.1", + "is-typedarray": "1.0.0", + "isstream": "0.1.2", + "json-stringify-safe": "5.0.1", + "mime-types": "2.1.15", + "oauth-sign": "0.8.2", + "performance-now": "0.2.0", + "qs": "6.4.0", + "safe-buffer": "5.0.1", + "stringstream": "0.0.5", + "tough-cookie": "2.3.2", + "tunnel-agent": "0.6.0", + "uuid": "3.0.1" + } + }, + "rimraf": { + "version": "2.6.1", + "bundled": true, + "dev": true, + "requires": { + "glob": "7.1.2" + } + }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true, + "dev": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "dev": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "dev": true, + "requires": { + "hoek": "2.16.3" + } + }, + "sshpk": { + "version": "1.13.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "asn1": "0.2.3", + "assert-plus": "1.0.0", + "bcrypt-pbkdf": "1.0.1", + "dashdash": "1.14.1", + "ecc-jsbn": "0.1.1", + "getpass": "0.1.7", + "jodid25519": "1.0.2", + "jsbn": "0.1.1", + "tweetnacl": "0.14.5" + }, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" + } + }, + "string_decoder": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "dev": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "2.1.1" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true, + "dev": true, + "requires": { + "block-stream": "0.0.9", + "fstream": "1.0.11", + "inherits": "2.0.3" + } + }, + "tar-pack": { + "version": "3.4.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "debug": "2.6.8", + "fstream": "1.0.11", + "fstream-ignore": "1.0.5", + "once": "1.4.0", + "readable-stream": "2.2.9", + "rimraf": "2.6.1", + "tar": "2.2.1", + "uid-number": "0.0.6" + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "punycode": "1.4.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "safe-buffer": "5.0.1" + } + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "dev": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "dev": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "extsprintf": "1.0.2" + } + }, + "wide-align": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "string-width": "1.0.2" + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + } + } + } } }, "concat-map": { @@ -12127,7 +13939,7 @@ "printj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/printj/-/printj-1.1.0.tgz", - "integrity": "sha1-hUh7Xo+WdjsLSiU2E7753Zs4fjw=", + "integrity": "sha512-NbiNBOQ0GioHyeD3ni8wZB7ZmfU7mxIrqhWR5XSreX3rUVvk5UOwpzxOnWqrLdCtoBbdQ40sEwC+nXxxjlUo0A==", "dev": true }, "process": { @@ -12187,7 +13999,7 @@ "randomatic": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha1-x6vpzIuHwLqodrGf3oP9RkeX44w=", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", "dev": true, "requires": { "is-number": "3.0.0", @@ -12228,7 +14040,7 @@ "randombytes": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.0.5.tgz", - "integrity": "sha1-3ACaJGuNCaF3tLegrne8Vw9LG3k=", + "integrity": "sha512-8T7Zn1AhMsQ/HI1SjcCfT/t4ii3eAqco3yOcSzS4mozsOz69lHLsoMXmF9nZgnFanYscnSlUSgs8uZyKzpE6kg==", "dev": true, "requires": { "safe-buffer": "5.1.1" @@ -12355,7 +14167,7 @@ "regex-cache": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha1-db3FiioUls7EihKDW8VMjVYjNt0=", + "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", "dev": true, "requires": { "is-equal-shallow": "0.1.3" @@ -12495,7 +14307,7 @@ "safe-buffer": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha1-iTMSr2myEj3vcfV4iQAWce6yyFM=", + "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", "dev": true }, "sax": { @@ -12848,7 +14660,7 @@ "stream-http": { "version": "2.7.2", "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-2.7.2.tgz", - "integrity": "sha1-QKBQ7I3DtTsz2ZCUFcAsC/Gr+60=", + "integrity": "sha512-c0yTD2rbQzXtSsFSVhtpvY/vS6u066PcXOX9kBB3mSO76RiUQzL340uJkGBWnlBg4/HZzqiUXtaVA7wcRcJgEw==", "dev": true, "requires": { "builtin-status-codes": "3.0.0", @@ -13667,7 +15479,7 @@ "which": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha1-/wS9/AEO5UfXgL7DjhrBwnd9JTo=", + "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", "dev": true, "requires": { "isexe": "2.0.0" From 76a3dbf883272e78831fc9b92be0fedf95c0cbd7 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Mon, 26 Feb 2018 23:27:53 +0000 Subject: [PATCH 85/86] Enforce option value via types --- typings/xterm.d.ts | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index dd08b55e7..2eb732acb 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -487,12 +487,7 @@ declare module 'xterm' { * @param key The option key. */ getOption(key: 'handler'): (data: string) => void; - /** - * Retrieves an option's value from the terminal. - * @param key The option key. - */ - getOption(key: string): any; - + /** * Sets an option on the terminal. * @param key The option key. @@ -548,12 +543,10 @@ declare module 'xterm' { */ setOption(key: 'theme', value: ITheme): void; /** - * Sets an option on the terminal. + * Retrieves an option's value from the terminal. * @param key The option key. - * @param value The option value. */ - setOption(key: string, value: any): void; - + setOption(key: 'cols' | 'rows', value: number): void; /** * Tells the renderer to refresh terminal content between two rows * (inclusive) at the next opportunity. From c51fa238ff956d2d3ad1f376bf34fdd3df1b1d24 Mon Sep 17 00:00:00 2001 From: Bruno Ribeito Date: Tue, 27 Feb 2018 23:18:33 +0000 Subject: [PATCH 86/86] Readd any overloads --- typings/xterm.d.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/typings/xterm.d.ts b/typings/xterm.d.ts index 2eb732acb..32c8b0a00 100644 --- a/typings/xterm.d.ts +++ b/typings/xterm.d.ts @@ -487,7 +487,12 @@ declare module 'xterm' { * @param key The option key. */ getOption(key: 'handler'): (data: string) => void; - + /** + * Retrieves an option's value from the terminal. + * @param key The option key. + */ + getOption(key: string): any; + /** * Sets an option on the terminal. * @param key The option key. @@ -543,10 +548,18 @@ declare module 'xterm' { */ setOption(key: 'theme', value: ITheme): void; /** - * Retrieves an option's value from the terminal. + * Sets an option on the terminal. * @param key The option key. + * @param value The option value. */ setOption(key: 'cols' | 'rows', value: number): void; + /** + * Sets an option on the terminal. + * @param key The option key. + * @param value The option value. + */ + setOption(key: string, value: any): void; + /** * Tells the renderer to refresh terminal content between two rows * (inclusive) at the next opportunity.