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 e98385c1..ea1d00a0 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 bf5144cb..87b71a12 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 5ac8a1a0..7dbdeb76 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 a9735352..86a604f2 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 9ab61ce6..a5137797 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 9d73535e..d61b22f2 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 8c6ea3e1..e229889e 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 87b71a12..1fccc8b1 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 ea1d00a0..69e7fcb2 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 1fccc8b1..3bf366ce 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 86a604f2..6814d269 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 69e7fcb2..39b81baf 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 3bf366ce..7e04119e 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 6814d269..f110ed1f 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 00000000..41931477 --- /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 512cf878..46479236 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 f90cb8c0..423f8d11 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 9e6312a5..aa9a3771 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 35e92f63..085d910a 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 41931477..4b23fcf4 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 46479236..40dde4a4 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 a1e5586f..d45b12af 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 4b23fcf4..92ce6b5c 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 92ce6b5c..2a15482d 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 2a15482d..bda634d0 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 83199e08..5ebe578e 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 bda634d0..8de8d459 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 8de8d459..20373bf9 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 20373bf9..401175f2 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 2fe3a5f5..5716ace4 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 36727eac..4cdf9c52 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 401175f2..6f80b08c 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 6f80b08c..ed190400 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 4cdf9c52..3d2a4d95 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 00000000..c494fb98 --- /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 833c18c6..dbffb8a0 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 be1b39dd..74f80a58 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 3d2a4d95..26a57033 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 80e25277..ee28bd11 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 c494fb98..5a9c93d6 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 f90cb8c0..e9a347d8 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 00000000..b95a253e --- /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 f95cc0ea..f12f42b7 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 03f36f42..00000000 --- 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 ed190400..9b790abf 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 118b3fc1..9e338c62 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 e828fbb9..dda4b568 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 9b790abf..47839a70 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 9e338c62..5fbd2b97 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 68d0fd7d..583aa907 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 47839a70..87dbd3c0 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 87dbd3c0..5a27d34f 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 5fbd2b97..619387e2 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 | 118 +++++++++++++++++++++++++----------- 1 file changed, 82 insertions(+), 36 deletions(-) diff --git a/src/AccessibilityManager.ts b/src/AccessibilityManager.ts index 5a27d34f..c854cc2b 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; + if (this._navigationMode.isActive) { + return this._navigationMode.onKeyUp(e); } - this._rowContainer.focus(); - e.preventDefault(); - e.stopPropagation(); - return true; - - // no handler - //return false; + return false; }); this._rowContainer.addEventListener('keydown', e => { - if (this._isNavigationModeActive) { - e.preventDefault(); - e.stopPropagation(); - return true; + 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 c854cc2b..76595c86 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 414eac89..7f090ac1 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 423f8d11..780a6b4e 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 00000000..d32f3790 --- /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 76595c86..ac1cdd37 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 ac1cdd37..bb6e08d4 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 bb6e08d4..d2fe7e1a 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 ee28bd11..89b03530 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 5a9c93d6..0d07b1a1 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 93399f9c..e0c355d0 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 95b15a0d..df11a27f 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 d2fe7e1a..930ae9f0 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 619387e2..62dcab8d 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 5ebe578e..85663734 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 930ae9f0..20da65e2 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 780a6b4e..4dc572d1 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 62dcab8d..92ae794f 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 89b03530..69adf3a2 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 20da65e2..a2504d3b 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 a2504d3b..fc34f41e 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 fc34f41e..e36efbec 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 e36efbec..a958d10b 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 4dc572d1..a1623a67 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 92ae794f..47fcbe77 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 946e9efb..746cdeba 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 b56caf83..4df277d5 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 4df277d5..4692f345 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 651dbd0f..179c6c64 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 a958d10b..819541ae 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 819541ae..68681f4f 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 68681f4f..988279ab 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 988279ab..281618f4 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 583aa907..fffc6b85 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 281618f4..e1bc841a 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 00000000..17d8a458 --- /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 584565eb..d293c256 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 e1bc841a..fa3a094d 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 fffc6b85..1057dadc 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 3746cb58..6a61bb4e 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 2524d234..00000000 --- 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 f3e9bb94..f78e52a5 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 a4a0b80c..00d22fb6 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 746cdeba..301ab020 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 f737adbb..ddb647da 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 676a39ed..47495295 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 9c95d06c..b051f4dd 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 179c6c64..e31c93b2 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 47495295..a5fe3367 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 17d8a458..8e782333 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 6d3f066a..e9116a22 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 91dabc26..dd912676 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 ce4684e5..c48c814b 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 a5fe3367..969a9837 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 969a9837..fdcf4887 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 fdcf4887..286c1f98 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 8e782333..86f0ebe1 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 e9116a22..4eafc7a0 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 0b877930..2500d73c 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 286c1f98..338f5a80 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 4eafc7a0..d0389048 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 338f5a80..286c1f98 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 d0389048..4eafc7a0 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 286c1f98..cb915516 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 80bc9f44..f281f9a9 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 4eafc7a0..48d00b2b 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 dd912676..0c79e98f 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 c48c814b..ea39ffcd 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 301ab020..12467d84 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 cdbda41f..88dc2b6b 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 00d22fb6..0d425a91 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 807516c7..7ac97714 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 0d425a91..c01d7b6d 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 f7c01276..03bbd115 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 ba988d61..3e14ff3f 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 da901a6e..a9617952 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 da3cc9d2..9dd7dda2 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 98377f36..79c02b5f 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 21514a53..f1398464 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 0b35edf1..2f6b4cd8 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 87ecd69e..59dc304d 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 9dd7dda2..c8433707 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 c01d7b6d..a639a75e 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 a639a75e..f571b74c 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 33853036..11856a92 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 a5d34632..740ff0aa 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 063b78f4..64562488 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 7a65d7f2..de64ee91 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 3e14ff3f..c16409dc 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 64562488..44bf043c 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 de64ee91..4353e46d 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 c16409dc..1a718fbc 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 22556f43..1c72e679 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 740ff0aa..cc1c8950 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 da348f5e..b6d0ff61 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 03e88fbc..effd975c 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 740ff0aa..4501aad4 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 b6d0ff61..a62cff0e 100644 --- a/demo/index.html +++ b/demo/index.html @@ -64,6 +64,10 @@ +
+ + +
diff --git a/demo/main.js b/demo/main.js index 1faf77d8..264a205d 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 11856a92..dc1d0682 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 c55dd924..1bc9cfbb 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 599299e3..3ea9fce6 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 59dc304d..35a2aa68 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 75e854d9..f6f593dc 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 79c02b5f..3ab86e7c 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 11c24733..3792a9da 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 d53c8c58..d48ad12c 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 b3ee50f0..3edaf3ff 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 1d14243e..cd59b81d 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 1bd4c87d..47328bc9 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 1a718fbc..079333de 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 1bc9cfbb..9f8bc433 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 f1bb422a..cdaf0d71 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 2408f808..fa1e34e6 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 dc61acbd..dbd4f02d 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 dbd4f02d..d86e5553 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 d86e5553..89f3d6db 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 9041fb8d..721996c9 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 89f3d6db..1c50cbbc 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 e2478592..03bbd115 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 dd08b55e..2eb732ac 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 2eb732ac..32c8b0a0 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.